diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..3274ec36d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 40feecf6f..cd2c3d2b8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,133 +3,135 @@ // Compatible with ESLint plugin module.exports = { - "root": true, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaFeatures": { - "impliedStrict": true, - } - }, - "env": { - "es6": true, - }, - "globals": { - // The following globals are defined within D2BS, or are actually defined in the source code - "include": true, - "print": true, - "me": true, - "td": true, - "getTickCount": true, - "delay": true, - "getParty": true, - "takeScreenshot": true, - "getUnit": true, - "quit": true, - "clickMap": true, - "getBaseStat": true, - "clickItem": true, - "getCursorType": true, - "getPresetUnits": true, - "getDistance": true, - "copyUnit": true, - "getRoom": true, - "getLocaleString": true, - "scriptBroadcast": true, - "isIncluded": true, - "showConsole": true, - "getInteractedNPC": true, - "getDialogLines": true, - "getUIFlag": true, - "sendPacket": true, - "getPacket": true, - "getPath": true, - "rand": true, - "PresetUnit": true, - "getPresetUnit": true, - "getArea": true, - "getWaypoint": true, - "getScript": true, - "Room": true, - "say": true, - "load": true, - "addEventListener": true, - "getMercHP": true, - "checkCollision": true, - "gold": true, - "getLocation": true, - "login": true, - "sendCopyData": true, - "getControl": true, - "debugLog": true, - "getCollision": true, - "transmute": true, - "submitItem": true, - "createGame": true, - "joinGame": true, - "Line": true, - "removeEventListener": true, - "Unit": true, - "Party": true, - "UtilitySystem": true, - "moveNPC": true, - "getPlayerFlag": true, - "clickParty": true, - "dopen": true, - "NTIPAliasClassID": true, - "Items": true, - "Text": true, - "File": true, - "js_strict": true, - "handler": true, - "sendKey": true, - "md5": true, - "module": true, - "require": true, - "Box": true, - "Frame": true, - "revealLevel": true, - "hideConsole": true, - }, - "rules": { - // enable additional rules - "indent": ["warn", "tab"], - "linebreak-style": ["off", "windows"], - "semi": ["error", "always"], - "comma-spacing": ["error", {"before": false, "after": true}], - "keyword-spacing": ["error", {"before": true, "after": true}], - "brace-style": ["error", "1tbs", {"allowSingleLine": true}], - "space-infix-ops": "error", - "space-unary-ops": ["error", {"words": true, "nonwords": false}], - "arrow-spacing": "error", - "arrow-body-style": ["error", "as-needed"], - "space-before-blocks": "error", - "key-spacing": ["error", {"beforeColon": false, "afterColon": true}], - "no-mixed-spaces-and-tabs": "error", - "no-trailing-spaces": ["warn", {"ignoreComments": true, "skipBlankLines": true}], - "no-whitespace-before-property": "error", - "comma-style": ["error", "last"], - "eol-last": ["error", "always"], - "block-scoped-var": "error", - "no-var": "warn", - "curly": ["error", "multi-line"], - "dot-notation": "warn", - "eqeqeq": ["error", "smart"], - "no-caller": "error", - "no-floating-decimal": "error", - "no-multi-spaces": ["error", {"ignoreEOLComments": true }], - "no-self-compare": "error", - "no-case-declarations": "off", - "no-with": "error", - "no-shadow": "off", - "no-use-before-define": "off", - "no-prototype-builtins": "off", - "quotes": ["warn", "double", { "avoidEscape": true }], - "no-constant-condition": ["error", {"checkLoops": false}], - "no-extra-label": "error", - //"no-labels": ["error", {"allowLoop": true}], // in the future no loops ;) - "no-unused-vars": ["warn", {"vars": "local"}], - "no-fallthrough": ["error", {"commentPattern": "break[\\s\\w]*omitted"}], - "no-undef": ["off", "always"], - "no-extra-boolean-cast": ["off", "always"], - } + "root": true, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaFeatures": { + "impliedStrict": true, + } + }, + "env": { + "es6": true, + }, + "globals": { + // The following globals are defined within D2BS, or are actually defined in the source code + "include": true, + "print": true, + "me": true, + "td": true, + "getTickCount": true, + "delay": true, + "getParty": true, + "takeScreenshot": true, + "getUnit": true, + "quit": true, + "clickMap": true, + "getBaseStat": true, + "clickItem": true, + "getCursorType": true, + "getPresetUnits": true, + "getDistance": true, + "copyUnit": true, + "getRoom": true, + "getLocaleString": true, + "scriptBroadcast": true, + "isIncluded": true, + "showConsole": true, + "getInteractedNPC": true, + "getDialogLines": true, + "getUIFlag": true, + "sendPacket": true, + "getPacket": true, + "getPath": true, + "rand": true, + "PresetUnit": true, + "getPresetUnit": true, + "getArea": true, + "getWaypoint": true, + "getScript": true, + "Room": true, + "say": true, + "load": true, + "addEventListener": true, + "getMercHP": true, + "checkCollision": true, + "gold": true, + "getLocation": true, + "login": true, + "sendCopyData": true, + "getControl": true, + "debugLog": true, + "getCollision": true, + "transmute": true, + "submitItem": true, + "createGame": true, + "joinGame": true, + "Line": true, + "removeEventListener": true, + "Unit": true, + "Party": true, + "UtilitySystem": true, + "moveNPC": true, + "getPlayerFlag": true, + "clickParty": true, + "dopen": true, + "Items": true, + "Text": true, + "File": true, + "js_strict": true, + "handler": true, + "sendKey": true, + "md5": true, + "module": true, + "require": true, + "Box": true, + "Frame": true, + "revealLevel": true, + "hideConsole": true, + }, + "rules": { + // enable additional rules + "indent": ["warn", 2], + "linebreak-style": ["off", "windows"], + "semi": ["error", "always"], + "comma-spacing": ["error", { "before": false, "after": true }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "object-curly-spacing": ["error", "always"], + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "space-infix-ops": "error", + "space-unary-ops": ["error", { "words": true, "nonwords": false }], + "arrow-spacing": "error", + "arrow-body-style": ["error", "as-needed"], + "space-before-blocks": "error", + "key-spacing": ["error", { "mode": "strict", "beforeColon": false, "afterColon": true }], + "no-mixed-spaces-and-tabs": "error", + "no-trailing-spaces": ["warn", { "ignoreComments": true, "skipBlankLines": true }], + "no-whitespace-before-property": "error", + "comma-style": ["error", "last"], + "eol-last": ["error", "always"], + "block-scoped-var": "error", + "no-var": "warn", + "curly": ["error", "multi-line"], + "dot-notation": "warn", + "eqeqeq": ["error", "smart"], + "no-caller": "error", + "no-floating-decimal": "error", + "no-multi-spaces": ["error", { "ignoreEOLComments": true }], + "no-self-compare": "error", + "no-case-declarations": "off", + "no-with": "error", + "no-shadow": "off", + "no-use-before-define": "off", + "no-prototype-builtins": "off", + "quotes": ["warn", "double", { "avoidEscape": true }], + "no-constant-condition": ["error", { "checkLoops": false }], + "no-extra-label": "error", + //"no-labels": ["error", {"allowLoop": true}], // in the future no loops ;) + "no-unused-vars": ["warn", { "vars": "local", "varsIgnorePattern": "^_" }], + "no-fallthrough": ["error", { "commentPattern": "break[\\s\\w]*omitted" }], + "no-undef": ["off", "always"], + "no-extra-boolean-cast": ["off", "always"], + "no-useless-escape": ["off", "always"], + "max-len": ["warn", { "code": 120, "ignoreComments": true, "ignoreUrls": true, "ignoreStrings": true }], + } }; diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 000000000..c833727d3 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,23 @@ +name: ESLint Check + +on: + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18.20.4' + + - name: Install dependencies + run: npm install + + - name: Run ESLint + run: npm run lint \ No newline at end of file diff --git a/.gitignore b/.gitignore index 613d92ffe..4ebc561d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,20 @@ +# Do not track hidden files and directories by default +.* +# Track these hidden files +!.eslintrc.js +!.gitignore +!.gitmodules +!.editorconfig + +# Track these hidden directories +!.github/ + +# Track these files from vscodes +!.vscode/settings.json +!.vscode/extensions.json + +# Do not track user generated data d2bs/kolbot/data/secure/*.txt d2bs/kolbot/data/*.json d2bs/kolbot/logs/*.json @@ -7,3 +23,12 @@ d2bs/kolbot/logs/**/** d2bs/kolbot/mules/**/*.txt d2bs/logs/*.log d2bs/kolbot/libs/manualplay/config/*.*.js +d2bs/kolbot/libs/soloplay/** +d2bs/kolbot/libs/config/*.*.js +d2bs/kolbot/D2BotSoloPlay.dbj +data/ +images/ +logs/ + +# Do not track install packages +node_modules/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..b9e9b5e6a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "thebguy.vsnip-check" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..b128d3e28 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.rulers": [ + 120 + ], + "editor.tabSize": 2, + "editor.detectIndentation": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, +} \ No newline at end of file diff --git a/README.md b/README.md index fb209736e..16ac31560 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,12 @@ * D2Bot# - manager (C#) * kolbot - script library (JS) -* use the mainline (trunk) branch +If you want to contribute to kolbot code, make sure you run `npm run lint` for final polish. -If you want to contribute to kolbot code, make sure you use [ESLint options for kolbot code](https://gist.githubusercontent.com/Nishimura-Katsuo/2d6866666c7acf10047c486a15a7fe60/raw/99ef9c2995929c492ef856772ff346e0f19709cd/.eslintrc.js) or [JSLint options for kolbot code](https://gist.githubusercontent.com/noah-/d917342e52281d54c404e0b2c18b0c6e/raw/fbade95e38b103d2654b90d85ef62a51c4295153/jslint.config) for final polish. If you want to contribute to d2bs/d2bot#, come to irc.synirc.net/d2bs and ask around. +[**Live Docs**](https://bhdocs.github.io/) + [**Documentation Repo**](https://github.com/blizzhackers/documentation#diablo-2-botting-system-d2bs) ## Install dependencies - do this first! @@ -31,17 +32,17 @@ If you want to contribute to d2bs/d2bot#, come to irc.synirc.net/d2bs and ask ar - [Microsoft .NET Framework 4.0 (or higher)](https://dotnet.microsoft.com/download/dotnet-framework) ## Getting Started -- [download kolbot](https://github.com/blizzhackers/documentation/blob/master/d2bot/Download.md#download) -- [d2bot manager setup](https://github.com/blizzhackers/documentation/blob/master/d2bot/ManagerSetup.md/#manager-setup) -- [IDE-Setup](IDES.md/#code-editors-ides): How to set up your IDE for syntax highlighting -- [FAQ](https://github.com/blizzhackers/documentation/blob/master/kolbot/FAQ.md/#faq) +- [download kolbot](https://github.com/blizzhackers/documentation/blob/restructure/d2bot/Download.md#download) +- [d2bot manager setup](https://github.com/blizzhackers/documentation/blob/restructure/d2bot/ManagerSetup.md/#manager-setup) +- [IDE-Setup](https://github.com/blizzhackers/documentation/blob/restructure/kolbot/IDES.md/#code-editors-ides): +- [FAQ](https://github.com/blizzhackers/documentation/blob/restructure/kolbot/FAQ.md/#faq) ## Guides -- [manual playing](https://github.com/blizzhackers/documentation/blob/master/kolbot/ManualPlay.md/#manual-playing) -- [multi botting](https://github.com/blizzhackers/documentation/blob/master/kolbot/MultiBotting.md/#multi-botting) +- [manual playing](https://github.com/blizzhackers/documentation/blob/restructure/kolbot/ManualPlay.md/#manual-playing) +- [multi botting](https://github.com/blizzhackers/documentation/blob/restructure/kolbot/MultiBotting.md/#multi-botting) - [kolbot-SoloPlay](https://github.com/blizzhackers/kolbot-SoloPlay) -- [character config](https://github.com/blizzhackers/documentation/blob/master/kolbot/CharacterConfig.md/#character-configuration) -- [TCP/IP Games](https://github.com/blizzhackers/documentation/blob/master/kolbot/TCP-IP%20games.md#tcpip-games) +- [character config](https://github.com/blizzhackers/documentation/blob/restructure/kolbot/CharacterConfig.md/#character-configuration) +- [TCP/IP Games](https://github.com/blizzhackers/documentation/blob/restructure/kolbot/TCP-IP%20games.md#tcpip-games) ## LimeDrop web based item manager and dropper diff --git a/d2bs/kolbot/D2BotBlank.dbj b/d2bs/kolbot/D2BotBlank.dbj index 5e45a44c2..446965655 100644 --- a/d2bs/kolbot/D2BotBlank.dbj +++ b/d2bs/kolbot/D2BotBlank.dbj @@ -1,33 +1,50 @@ /** * @filename D2BotBlank.dbj -* @author kolton +* @author kolton, theBGuy * @desc Entry script for testing * */ + + function main() { - include("json2.js"); - include("OOG.js"); - include("common/util.js"); - include("common/misc.js"); + include("critical.js"); // required - addEventListener("copydata", Starter.receiveCopyData); + if (!FileTools.exists("data/" + me.profile + ".json")) { + DataFile.create(); + } + + addEventListener("copydata", Starter.receiveCopyData); - while (!Starter.handle) { - delay(100); - } + while (!Starter.handle) { + delay(100); + } - DataFile.updateStats("handle", Starter.handle); - delay(500); - D2Bot.init(); - load("tools/heartbeat.js"); + DataFile.updateStats("handle", Starter.handle); + delay(500); + D2Bot.init(); + load("threads/heartbeat.js"); - if (!FileTools.exists("data/" + me.profile + ".json")) { - DataFile.create(); - } + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } - while (true) { - Starter.isUp = me.ingame ? "yes" : "no"; + while (true) { + delay(1000); + + if (me.gameReady) { + Starter.isUp === "no" && (Starter.isUp = "yes"); + if (me.ingame) { + D2Bot.updateStatus( + "(Char: " + me.charname + ") (Game: " + + (me.gamename || "singleplayer") + ") (Level: " + me.charlvl + ")" + ); + } + } else { + D2Bot.updateStatus("Out of Game"); + Starter.isUp = "no"; + } - delay(1000); - } + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotChannel.dbj b/d2bs/kolbot/D2BotChannel.dbj index 0a1951f74..40ecfabf1 100644 --- a/d2bs/kolbot/D2BotChannel.dbj +++ b/d2bs/kolbot/D2BotChannel.dbj @@ -3,549 +3,584 @@ * @author kolton, theBGuy * @desc Entry script for following bots using channels * +* @typedef {import("./sdk/globals")} */ -include("StarterConfig.js"); - -// D2BotChannel specific settings - for global settings see libs/StarterConfig.js -Starter.Config.Games = [""]; // List of games to look for. Example: Games: ["some baal-", "chaos run-"], -Starter.Config.Passwords = [""]; // List of game passwords. Each array in Games array should have a matching element in Passwords. Use "" for blank pw. -Starter.Config.JoinDelay = 10; // Seconds to wait between announcement and clicking join -Starter.Config.JoinRetry = 5; // Amount of times to re-attempt joining game -Starter.Config.FriendListQuery = 0; // Seconds between "/f l" retries. 0 = disable. To prevent spamming when using set time rand(80, 160) -Starter.Config.SkipMutedKey = true; -Starter.Config.MutedKeyTrigger = "Your account has had all chat privileges suspended."; -Starter.Config.Follow = []; // leader's in game character name, only use this if the leader is using announce in the chat, can be an array or names ["somename", "somename2"] +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// D2BotChannel specific settings - for global settings see libs/starter/StarterConfig.js +const { + ChannelConfig, +} = require("./libs/systems/channel/ChannelConfig"); +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds // Starter.Config.JoinChannel = ""; // Default channel. +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // -// todo: figure out a way to check if player who announced game is still in channel - -// No touchy! -include("json2.js"); -include("polyfill.js"); -include("OOG.js"); -include("automule.js"); -include("gambling.js"); -include("torchsystem.js"); -include("craftingsystem.js"); -include("common/misc.js"); -include("common/util.js"); -include("common/prototypes.js"); -let sdk = require("./modules/sdk"); -let Controls = require("./modules/Control"); -let Overrides = require("./modules/Override"); - -if (typeof AdvancedConfig[me.profile] === "object") { - Object.assign(Starter.Config, AdvancedConfig[me.profile]); +// the only things we really need from these are their oog checks +includeSystemLibs(); + +if (typeof Starter.AdvancedConfig[me.profile] === "object") { + Object.assign(Starter.Config, Starter.AdvancedConfig[me.profile]); } +delete Starter.AdvancedConfig; -let channelTick = getTickCount(); -let fListTick = 0; -let retry = 0; -let badGames = []; -let lastText; -let joinInfo = { - gameName: "", - gamePass: "", - oldGame: "", - inGame: false +/** @type {Set} */ +const badGames = new Set(); +const watch = { + player: "", + status: "", +}; +/** @type {Array<{ name: string, msg: string }>} */ +const messageQueue = []; +/** @type {Map} */ +const channelWatcher = new Map(); +const joinInfo = { + gameName: "", + gamePass: "", + oldGame: "", + inGame: false, + joinChannel: Starter.Config.JoinChannel, }; if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; + Starter.firstRun = true; } -new Overrides.Override(Starter, Starter.receiveCopyData, function (orignal, mode, msg) { - if (mode === 3) { - Starter.isUp = (me.gameReady ? "yes" : "no"); - if (!me.gameReady) { - return; - } - Starter.gameInfo.gameName = (me.gamename || ""); - Starter.gameInfo.gamePass = (me.gamepassword || ""); - } else { - orignal(mode, msg); - } -}).apply(); - -function locationAction (location) { - let i, n, string, text, regex, fullText, lines; - - MainSwitch: - switch (location) { - case sdk.game.locations.PreSplash: - ControlAction.click(); - - break; - case sdk.game.locations.Lobby: - D2Bot.updateStatus("Lobby"); - - me.blockKeys = false; - Starter.loginRetry = 0; - !Starter.firstLogin && (Starter.firstLogin = true); - Controls.LobbyEnterChat.click(); - - break; - case sdk.game.locations.LobbyChat: - D2Bot.updateStatus("Lobby Chat"); - - if (Starter.inGame) { - if (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck()) { - break; - } - - print("updating runs"); - D2Bot.updateRuns(); - - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - retry = 0; - } - - // Muted key handler - fullText = ""; - lines = Controls.LobbyChat.getText(); - - if (!lines) { - break; - } - - fullText = lines.join(" ").replace(/\s+/g, " "); - - if (fullText.match(Starter.Config.MutedKeyTrigger.replace(/\s+/g, " "), "gi")) { - D2Bot.printToConsole(Starter.gameInfo.mpq + " is muted.", sdk.colors.D2Bot.Gold); - - ControlAction.mutedKey = true; - - if (Starter.Config.SkipMutedKey) { - if (Starter.gameInfo.switchKeys) { - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } else { - D2Bot.stop(); - } - } - } - - if (!ControlAction.mutedKey && (!Starter.chatActionsDone || getTickCount() - channelTick >= 120e3)) { - if (Starter.Config.JoinChannel !== "") { - if (typeof AdvancedConfig[me.profile] === "object" && typeof AdvancedConfig[me.profile].JoinChannel === "string") { - joinInfo.joinChannel = AdvancedConfig[me.profile].JoinChannel; - } else { - joinInfo.joinChannel = Starter.Config.JoinChannel; - } - - if (joinInfo.joinChannel) { - if (ControlAction.joinChannel(joinInfo.joinChannel)) { - Starter.useChat = true; - } else { - print("Unable to join channel, chat messages disabled."); - - Starter.useChat = false; - } - } - } - - if (Starter.Config.Follow.length > 0 && !Starter.channelNotify) { - Starter.sayMsg("/d2notify"); - lines = Controls.LobbyChat.getText(); - - if (!lines) { - break; - } - - if (lines.some(line => line.match("notifications are disabled"))) { - Starter.sayMsg("/d2notify"); - lines = Controls.LobbyChat.getText(); - } - - if (lines.some(line => line.match("notifications are enabled"))) { - Starter.channelNotify = true; - } - } - - // Added !chatActionsDone condition to prevent spam - if (Starter.Config.FirstJoinMessage !== "" && !Starter.chatActionsDone) { - ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3); - Starter.sayMsg(Starter.Config.FirstJoinMessage); - delay(500); - } - - Starter.chatActionsDone = true; - channelTick = getTickCount(); - } - - if (Starter.Config.FriendListQuery > 0 && getTickCount() - fListTick >= Starter.Config.FriendListQuery * 1000) { - say("/f l"); - - fListTick = getTickCount(); - } - - switch (Starter.lastGameStatus) { - case "pending": // Most likely FTJ (can't detect it directly) - string = ""; - text = Controls.LobbyServerDown.getText(); - - if (text) { - for (i = 0; i < text.length; i += 1) { - string += text[i]; - - if (i !== text.length - 1) { - string += " "; - } - } - - // Didn't meet level restriction - if (string === getLocaleString(sdk.locale.text.DoNotMeetLevelReqForThisGame)) { - print(string); - - retry = Starter.Config.JoinRetry; - - break; - } - } - - retry += 1; - - D2Bot.updateRuns(); - - if (retry < Starter.Config.JoinRetry) { - Controls.JoinGameWindow.click(); - - break MainSwitch; - } - - break; - case "DNE": // Game didn't exist - retry += 1; - - break; - case "FULL": // Game is full - retry = Starter.Config.JoinRetry; - - break; - } - - if (retry >= Starter.Config.JoinRetry) { - D2Bot.printToConsole("Failed to join " + joinInfo.gameName + ". Aborting."); - badGames.push(joinInfo.gameName); - - Starter.lastGameStatus = "ready"; - joinInfo.oldGame = joinInfo.gameName; - retry = 0; - } - - fullText = ""; - lines = Controls.LobbyChat.getText(); - - if (!lines) { - break; - } - - fullText = lines.join(" ").replace(/\s+/g, " "); - - if (lastText === fullText) { - if (joinInfo.gameName && joinInfo.gameName !== joinInfo.oldGame && badGames.indexOf(joinInfo.gameName) === -1) { - // we have a game and nothing else has changed since last announcement so go ahead and try joining again - Controls.JoinGameWindow.click(); - } - // nothing has changed since our last check so break - break; - } - - lastText = fullText; - - // we are set to follow a specific leader, lets look for their messages - if (Starter.Config.Follow.length > 0) { - let newLines = lines - .map((line, index) => { - if (index > 0 - // eslint-disable-next-line no-useless-escape - && !line.match(/\<.*\>/, "gi") - && !line.match(" has left", "gi") - && !line.match(" has joined", "gi")) { - line = lines[index - 1] + line; - } - return line; - }) - .filter(line => line.match("Next game is", "gi")); - - if (newLines.length === 0) { - break; - } - - for (n = 0; n < Starter.Config.Follow.length; n++) { - let test = []; - - newLines.forEach(element => { - if (element.includes(Starter.Config.Follow[n])) { - test.push(element); - } - }); - - if (test.length === 0) continue; - test.reverse(); - - for (let msg = 0; msg < test.length; msg++) { - let checkName = test[msg].toString(); - let hasPass = checkName.indexOf("/") > -1; - - let gName = (checkName.slice(checkName.indexOf("is ") + 3, (hasPass ? checkName.indexOf("/") : undefined)) || "").trim(); - if (gName.length > 15) continue; // invalid game name - let gPass = (hasPass ? checkName.slice(checkName.lastIndexOf("/") + 1) : ""); - if (gPass.length > 15) continue; // invalid game pass - joinInfo.gameName = gName; - joinInfo.gamePass = gPass; - console.debug(joinInfo.gameName + " " + joinInfo.gamePass); - - if (joinInfo.gameName && joinInfo.gameName !== joinInfo.oldGame && badGames.indexOf(joinInfo.gameName) === -1) { - // wait until leader has left the channel - if (Starter.Config.JoinDelay && Starter.channelNotify) { - let wTick = getTickCount(); - - while (true) { - lines = Controls.LobbyChat.getText(); - - if (!lines || (getTickCount() - wTick > Time.minutes(1))) { - break; - } - - if (lines.some(line => line.match(Starter.Config.Follow[n] + " has left"))) { - break; - } - - delay(2000); - } - } - - Controls.JoinGameWindow.click(); - - break; - } - } - } - } - - // we are just trying to follow game names - for (let n = 0; n < Starter.Config.Games.length; n += 1) { - if (Starter.Config.Games[n] === "") continue; - regex = new RegExp("\\W+" + Starter.Config.Games[n].toLowerCase() + "\\d+", "gi"); - joinInfo.gameName = fullText.match(regex); - - if (joinInfo.gameName) { - // use last match and trim it - joinInfo.gameName = joinInfo.gameName[joinInfo.gameName.length - 1].toString().replace(/^\W*/, ""); - joinInfo.gamePass = Starter.Config.Passwords[n] || ""; - - if (joinInfo.gameName && joinInfo.gameName !== joinInfo.oldGame && badGames.indexOf(joinInfo.gameName) === -1) { - Controls.JoinGameWindow.click(); - - break; - } - } - } - - break; - case sdk.game.locations.WaitingInLine: - case sdk.game.locations.CreateGame: - Controls.CancelCreateGame.click(); - - break; - case sdk.game.locations.JoinGame: - if (joinInfo.oldGame === joinInfo.gameName || badGames.includes(joinInfo.gameName)) { - Controls.CancelJoinGame.click(); - } - - D2Bot.updateStatus("Join Game"); - - if (joinInfo.gameName !== "") { - print("ÿc2Joining ÿc0" + joinInfo.gameName); - Controls.JoinGameName.setText(joinInfo.gameName); - Controls.JoinGamePass.setText(joinInfo.gamePass); - - if (typeof AdvancedConfig[me.profile] === "object" && typeof AdvancedConfig[me.profile].AnnounceGame === "boolean" && typeof AdvancedConfig[me.profile].AnnounceMessage === "string") { - Starter.sayMsg(AdvancedConfig[me.profile].AnnounceMessage + " " + joinInfo.gameName); - } - - // Only delay on first join - the rest is handled by GameDoesNotExistTimeout. Any other case is instant fail (ie. full game). - if (retry === 0 || Starter.lastGameStatus === "pending") { - if (typeof AdvancedConfig[me.profile] === "object" && typeof AdvancedConfig[me.profile].JoinDelay === "number") { - ControlAction.timeoutDelay("Custom Join Delay", AdvancedConfig[me.profile].JoinDelay * 1e3); - } else if (Starter.Config.JoinDelay) { - ControlAction.timeoutDelay("Join Game Delay", Starter.Config.JoinDelay * 1e3); - } - } - - me.blockmouse = true; - - Controls.JoinGame.click(); - - me.blockmouse = false; - Starter.lastGameStatus = "pending"; - - Starter.locationTimeout(5000, location); - } - - break; - case sdk.game.locations.Ladder: - case sdk.game.locations.ChannelList: - break; - case sdk.game.locations.MainMenu: - case sdk.game.locations.Login: - case sdk.game.locations.CharSelect: - case sdk.game.locations.SplashScreen: - Starter.LocationEvents.login(); - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - break; - case sdk.game.locations.MainMenuConnecting: - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - case sdk.game.locations.CharSelectNoChars: - Starter.LocationEvents.charSelectError(); - - break; - case sdk.game.locations.ServerDown: - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GameNameExists: - break; - case sdk.game.locations.GatewaySelect: - Controls.GatewayCancel.click(); - - break; - case sdk.game.locations.GameDoesNotExist: - Starter.LocationEvents.gameDoesNotExist(); - Starter.lastGameStatus = "DNE"; - - break; - case sdk.game.locations.GameIsFull: - badGames.push(joinInfo.gameName); - Controls.JoinGameWindow.click(); - Controls.CancelCreateGame.click(); - Starter.lastGameStatus = "FULL"; - - break; - case sdk.game.locations.OtherMultiplayer: - Starter.LocationEvents.otherMultiplayerSelect(); - - break; - case sdk.game.locations.TcpIp: - case sdk.game.locations.TcpIpEnterIp: - Controls.TcpIpCancel.click(); - - break; - default: - if (location !== undefined) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } +/** + * @param {string} name + * @param {string} msg + * @returns {void} + */ +function ChannelChatHandler (name, msg) { + if (me.ingame) return; + if (/[joined|left] the channel/g.test(msg)) { + channelWatcher.set(name, msg.includes("joined") ? "joined" : "left"); + return; + } + messageQueue.push({ name: name, msg: msg }); } -function main() { - addEventListener("copydata", Starter.receiveCopyData); - addEventListener("scriptmsg", Starter.scriptMsgEvent); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - Starter.gameCount = (DataFile.getStats().runs + 1 || 1); - - if (Starter.gameInfo.error) { - if (!!DataFile.getStats().debugInfo) { - Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; - D2Bot.printToConsole("Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, sdk.colors.D2Bot.Gray); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } - - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); - - while (!Object.keys(Starter.profileInfo).length) { - D2Bot.getProfile(); - print("Getting Profile"); - delay(500); - } - - while (true) { - // returns true before actually in game so we can't only use this check - while (me.ingame) { - // returns false when switching acts so we can't use while - if (me.gameReady) { - joinInfo.inGame = true; - - if (!Starter.inGame) { - print("Updating Status"); - - badGames.push(joinInfo.gameName); - joinInfo.oldGame = me.gamename; - Starter.lastGameStatus = "ingame"; - Starter.inGame = true; - Starter.gameStart = getTickCount(); - - DataFile.updateStats("runs", Starter.gameCount); - } - - D2Bot.updateStatus(Starter.profileInfo.charName + " | Game: " + (me.gamename || "singleplayer") + Starter.timer(Starter.gameStart)); - } - - delay(1000); - } - - joinInfo.inGame = false; - - locationAction(getLocation()); - delay(1000); - } +const locationAction = (function () { + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations, + parseControlText + } = require("./libs/oog/Locations"); + + const pollQueue = function (timeout = Time.seconds(30)) { + const preLen = messageQueue.length; + const start = getTickCount(); + while (getTickCount() - start < timeout) { + if (messageQueue.length > preLen) return true; + delay(100); + } + return messageQueue.length > preLen; + }; + + /** @param {string} game */ + const exclude = function (game) { + // No filters + if (!ChannelConfig.excludeFilter.length) return false; + + for (let filterSet of ChannelConfig.excludeFilter) { + let conditionsMatched = true; + + for (let condition of filterSet) { + // Break the inner loop if an element didn't match or if an element is invalid + if (!condition || !game.match(condition, "gi")) { + conditionsMatched = false; + break; + } + } + + // All elements matched + if (conditionsMatched) { + return true; + } + } + + return false; + }; + + let lastText; + let channelTick = getTickCount(); + let fListTick = 0; + let retry = 0; + + locations.set(sdk.game.locations.Lobby, + function () { + D2Bot.updateStatus("Lobby"); + + me.blockKeys = false; + Starter.loginRetry = 0; + !Starter.firstLogin && (Starter.firstLogin = true); + Controls.LobbyEnterChat.click(); + } + ); + locations.set(sdk.game.locations.LobbyChat, + function () { + D2Bot.updateStatus("Lobby Chat"); + + if (Starter.inGame) { + if (AutoMule.outOfGameCheck() + || TorchSystem.outOfGameCheck() + || Gambling.outOfGameCheck() + || CraftingSystem.outOfGameCheck()) { + return; + } + + console.log("updating runs"); + D2Bot.updateRuns(); + + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + retry = 0; + } + + // Muted key handler + let lines = Controls.LobbyChat.getText(); + if (!lines) return; + + let fullText = lines.join(" ").replace(/\s+/g, " "); + + if (fullText.match(ChannelConfig.MutedKeyTrigger.replace(/\s+/g, " "), "gi")) { + D2Bot.printToConsole(Starter.gameInfo.mpq + " is muted.", sdk.colors.D2Bot.Gold); + + ControlAction.mutedKey = true; + + if (ChannelConfig.SkipMutedKey) { + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.stop(); + } + } + } + + if (!ControlAction.mutedKey && (!Starter.chatActionsDone || getTickCount() - channelTick >= 120e3)) { + if (joinInfo.joinChannel) { + if (ControlAction.joinChannel(joinInfo.joinChannel)) { + Starter.useChat = true; + } else { + console.warn("Unable to join channel, chat messages disabled."); + + Starter.useChat = false; + } + } + + // Added !chatActionsDone condition to prevent spam + if (Starter.Config.FirstJoinMessage !== "" && !Starter.chatActionsDone) { + ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3); + Starter.sayMsg(Starter.Config.FirstJoinMessage); + delay(500); + } + + Starter.chatActionsDone = true; + channelTick = getTickCount(); + } + + StatusSwitch: + switch (Starter.lastGameStatus) { + case "pending": // Most likely FTJ (can't detect it directly) + let string = parseControlText(Controls.LobbyServerDown); + + switch (string) { + case getLocaleString(sdk.locale.text.DoNotMeetLevelReqForThisGame): + case getLocaleString(sdk.locale.text.HcCannotPlayWithSc): + case getLocaleString(sdk.locale.text.ScCannotPlayWithHc): + case getLocaleString(sdk.locale.text.CannotPlayInHellClassic): + case getLocaleString(sdk.locale.text.CannotPlayInHellXpac): + case getLocaleString(sdk.locale.text.CannotPlayInNightmareClassic): + case getLocaleString(sdk.locale.text.CannotPlayInNightmareXpac): + case getLocaleString(sdk.locale.text.NonLadderCannotPlayWithLadder): + case getLocaleString(sdk.locale.text.LadderCannotPlayWithNonLadder): + console.log(string); + retry = ChannelConfig.JoinRetry; + + break StatusSwitch; + } + + retry += 1; + + D2Bot.updateRuns(); + + if (retry < ChannelConfig.JoinRetry) { + Controls.JoinGameWindow.click(); + + return; + } + + break; + case "DNE": // Game didn't exist + retry += 1; + + break; + case "FULL": // Game is full + retry = ChannelConfig.JoinRetry; + + break; + } + + if (retry >= ChannelConfig.JoinRetry) { + D2Bot.printToConsole("Failed to join " + joinInfo.gameName + ". Aborting."); + badGames.add(joinInfo.gameName); + + Starter.lastGameStatus = "ready"; + joinInfo.oldGame = joinInfo.gameName; + retry = 0; + } + + if (ChannelConfig.FriendListQuery > 0 + && getTickCount() - fListTick >= Time.seconds(ChannelConfig.FriendListQuery)) { + say("/f l"); + fListTick = getTickCount(); + pollQueue(); + // we should also include list of friends to actually follow rather than just game names + for (let chat of messageQueue) { + let { msg } = chat; + // if its not a message from us, ignore it. When we type /f l, the who is us + // if (!name.split("*")[0] !== me.charname) continue; + let match = new RegExp("^.*?(\\w+).*in the game\\s+(\\w+)(\\s+\\(private\\))?\\.", "gm").exec(msg); + if (match) { + let [,, gameName, _private] = match; + // check if this is a game we are looking for - what about just following the friend in all games that are not private? + for (let gInfo of ChannelConfig.Games) { + if (gameName.match(gInfo.game, "gi") + && !exclude(gameName) + && !String.isEqual(gameName, joinInfo.oldGame) + && !badGames.has(gameName)) { + // check if the game is private + if (_private && gInfo.password) { + // we can't join if the game is private and we don't have a password + // ugh why would bnet not actually use (private) correctly + /** @todo Figure out how we can check if game is private or not */ + // if (!gInfo.password) continue; + joinInfo.gamePass = gInfo.password; + } + joinInfo.gameName = gameName; + + // we have a match so lets try joining + Controls.JoinGameWindow.click(); + + break; + } + } + } + } + } + + fullText = ""; + lines = Controls.LobbyChat.getText(); + if (!lines) return; + + fullText = lines.join(" ").replace(/\s+/g, " "); + + if (lastText === fullText) { + if (joinInfo.gameName + && joinInfo.gameName !== joinInfo.oldGame + && !badGames.has(joinInfo.gameName)) { + // we have a game and nothing else has changed since last announcement so go ahead and try joining again + Controls.JoinGameWindow.click(); + } + // nothing has changed since our last check so break + return; + } + + lastText = fullText; + + while (messageQueue.length > 0) { + const chat = messageQueue.shift(); + // lets determine if this is a player message or internal message + if (!chat.name || chat.name.split("*")[0] === me.charname) continue; + // is this a game announcement? + if (!chat.msg.toLowerCase().includes("next game is")) continue; + // eslint-disable-next-line max-len + const gameRegex = new RegExp(/Next game is\s+([a-zA-Z0-9_-]+)(?:\/\/(\w+))?(?:\s+in\s+(\w+))?(?:\s+on\s+(\w+))?/gm); + // capture the game name and password if there is one + let match = gameRegex.exec(chat.msg); + if (!match) continue; + // extract capture groups from regex + let [, gameName, gamePass, diff, mode] = match; + + // double check that this is a valid game name + if (gameName.length > 15 || badGames.has(gameName) || exclude(gameName)) continue; + + // TODO: handle difficulty + if (diff) { + // + } + + // handle mode - TODO: handle classic vs expansion (gametype doesn't seem to be set yet in lobby) + if (mode) { + mode = mode.toLowerCase(); + // can't join nl games if we are ladder + if (mode.includes("nl") && me.ladder) { + // console.debug("Skipping NL game: " + gameName + " " + gamePass + " " + diff + " " + mode); + continue; + } + // can't join hardcore games if we are softcore + if (mode.includes("hc") && me.softcore) { + // console.debug("Skipping HC game: " + gameName + " " + gamePass + " " + diff + " " + mode); + continue; + } + // can't join softcore games if we are hardcore + if (mode.includes("sc") && me.hardcore) { + // console.debug("Skipping SC game: " + gameName + " " + gamePass + " " + diff + " " + mode); + continue; + } + } + + // handle following player names + if (ChannelConfig.Follow.length > 0) { + for (let follow of ChannelConfig.Follow) { + if (chat.name.split("*")[0].toLowerCase() === follow.toLowerCase()) { + // alright so this is a game we want to follow, lets set the game name and password + console.log("Joining game: " + gameName + "//" + gamePass); + // wait for the player to leave the channel + watch.player = chat.name; + let timeout = getTickCount() + Time.minutes(1); + console.log("Waiting for " + watch.player + " to leave the channel"); + + while (channelWatcher.get(watch.player) !== "left" + && getTickCount() < timeout) { + delay(100); + } + + joinInfo.gameName = gameName; + joinInfo.gamePass = gamePass; + // we have a game and nothing else has changed since last announcement so go ahead and try joining again + Controls.JoinGameWindow.click(); + return; + } + } + } + + // check if this is a game we want to join + for (let { game } of ChannelConfig.Games) { + if (game === "") continue; + if (!gameName.toLowerCase().includes(game.toLowerCase())) continue; + // alright so this gamename is one we were looking for, lets confirm it is not a bad game + if (badGames.has(gameName) || exclude(gameName)) continue; + // alright so this is a game we want to join, lets set the game name and password + console.log("Joining game: " + gameName + "//" + gamePass); + // wait for the player to leave the channel + watch.player = chat.name; + let timeout = getTickCount() + Time.minutes(1); + console.log("Waiting for " + watch.player + " to leave the channel"); + + while (channelWatcher.get(watch.player) !== "left" + && getTickCount() < timeout) { + delay(100); + } + if (channelWatcher.get(watch.player) !== "left") { + let pName = watch.player.split("*")[0]; + say("/whois " + pName); + pollQueue(); + if (messageQueue.length > 0) { + let inGame = messageQueue + .filter(function (chat) { + return chat.msg.includes(pName); + }) + .some(function (chat) { + return new RegExp(/in .+? game/gi).test(chat.msg); + }); + if (!inGame) { + console.log("Player left channel, but is not in game. Skipping."); + continue; + } + } + } + // player who announced has left chat so lets set the game name and password and try to join + joinInfo.gameName = gameName; + joinInfo.gamePass = gamePass || ""; + // lets click the join game button + Controls.JoinGameWindow.click(); + // break out of the main loop + return; + } + } + // maybe poll the queue here? Or delay until the next fl query tick if we are using it + } + ); + addLocations([sdk.game.locations.WaitingInLine, sdk.game.locations.CreateGame], + function () { + Controls.CancelCreateGame.click(); + } + ); + locations.set(sdk.game.locations.JoinGame, + function (location) { + if (joinInfo.oldGame === joinInfo.gameName || badGames.has(joinInfo.gameName)) { + Controls.CancelJoinGame.click(); + } + + D2Bot.updateStatus("Join Game"); + + if (joinInfo.gameName !== "") { + console.log("ÿc2Joining ÿc0" + joinInfo.gameName); + Controls.JoinGameName.setText(joinInfo.gameName); + Controls.JoinGamePass.setText(joinInfo.gamePass); + + if (Starter.Config.AnnounceGames && Starter.Config.AnnounceMessage) { + Starter.sayMsg(Starter.Config.AnnounceMessage + " " + joinInfo.gameName); + } + + // Only delay on first join - the rest is handled by GameDoesNotExistTimeout. Any other case is instant fail (ie. full game). + if (retry === 0 || Starter.lastGameStatus === "pending") { + ControlAction.timeoutDelay("Join Game Delay", ChannelConfig.JoinDelay * 1e3); + } + + me.blockMouse = true; + + Controls.JoinGame.click(); + + me.blockMouse = false; + Starter.lastGameStatus = "pending"; + + Starter.locationTimeout(5000, location); + } + } + ); + locations.set(sdk.game.locations.SelectDifficultySP, + function () { + hideConsole(); + sendKey(sdk.keys.Escape); + } + ); + locations.set(sdk.game.locations.GameNameExists, + function () { + Starter.LocationEvents.openJoinGameWindow(); + } + ); + locations.set(sdk.game.locations.GameDoesNotExist, + function () { + Starter.LocationEvents.gameDoesNotExist(); + Starter.lastGameStatus = "DNE"; + } + ); + locations.set(sdk.game.locations.GameIsFull, + function () { + badGames.add(joinInfo.gameName); + Controls.JoinGameWindow.click(); + Controls.CancelCreateGame.click(); + Starter.lastGameStatus = "FULL"; + } + ); + locations.set(sdk.game.locations.TcpIp, + function () { + Controls.TcpIpCancel.click(); + } + ); + + return { + /** @param {number} loc */ + run: function (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }, + }; +})(); + +function main () { + const Overrides = require("./modules/Override"); + new Overrides.Override(Starter, Starter.receiveCopyData, function (orignal, mode, msg) { + if (mode === 3) { + Starter.isUp = (me.gameReady ? "yes" : "no"); + if (!me.gameReady) { + return; + } + Starter.gameInfo.gameName = (me.gamename || ""); + Starter.gameInfo.gamePass = (me.gamepassword || ""); + } else { + orignal(mode, msg); + } + }).apply(); + + addEventListener("copydata", Starter.receiveCopyData); + addEventListener("scriptmsg", Starter.scriptMsgEvent); + addEventListener("chatmsg", ChannelChatHandler); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + Starter.gameCount = (DataFile.getStats().runs + 1 || 1); + + if (Starter.gameInfo.error) { + if (!!DataFile.getStats().debugInfo) { + Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; + D2Bot.printToConsole( + "Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, + sdk.colors.D2Bot.Gray + ); + } + + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } + + DataFile.updateStats("debugInfo", JSON.stringify({ currScript: "none", area: "out of game" })); + + while (!Object.keys(Starter.profileInfo).length) { + D2Bot.getProfile(); + console.log("Getting Profile"); + delay(500); + } + + while (true) { + // returns true before actually in game so we can't only use this check + while (me.ingame) { + // returns false when switching acts so we can't use while + if (me.gameReady) { + joinInfo.inGame = true; + + if (!Starter.inGame) { + console.log("Updating Status"); + + badGames.add(joinInfo.gameName); + channelWatcher.clear(); + joinInfo.oldGame = me.gamename; + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + Starter.gameStart = getTickCount(); + + DataFile.updateStats("runs", Starter.gameCount); + } + + D2Bot.updateStatus( + me.charname + " (" + me.charlvl + ") | Game: " + me.gamename + + Starter.timer(Starter.gameStart) + ); + } + + delay(1000); + } + + joinInfo.inGame = false; + + locationAction.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotCleaner.dbj b/d2bs/kolbot/D2BotCleaner.dbj index 7b571c313..57f174dd3 100644 --- a/d2bs/kolbot/D2BotCleaner.dbj +++ b/d2bs/kolbot/D2BotCleaner.dbj @@ -4,495 +4,388 @@ * @credits Whoever did the original D2BotAccountCleaner.dbj * @desc The purpose of this entryscript is to clean/remove characters and/or files easily * +* @typedef {import("./sdk/globals")} */ -include("StarterConfig.js"); - -// D2BotCleaner settings -// New Stuff - DataCleaner to delete old files associated with running kolbot or SoloPlay -// SaveFiles - to save important SoloPlay files to SoloPlay/Data/ for performance review -//***********************************************************************************************************************// -// DataCleaner and SaveFiles can both be used for cleaning/saving files without having to delete associated characters // -//***********************************************************************************************************************// -Starter.Config.DataCleaner = true; // Always run this when re-using a profile with Kolbot-SoloPlay -Starter.Config.SaveFiles = false; // NOTE: Only works on SoloPlay profiles, Highly recommened to run this if using the peformance tracking system and wish to review them later - -// Old Stuff -Starter.Config.DelayBetweenAccounts = rand(15, 30); //Seconds to wait before cleaning next account, if doing 10+ accounts recommended to increase this delay to rand(30, 60) prevent R/D +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// D2BotCleaner settings - for global settings @see libs/starter/StarterConfig.js +const { + CleanerConfig, + AccountsToClean, + CharactersToExclude, + profiles, + AdvancedProfileCleanerConfig, + AdvancedCleanerConfig, +} = require("./libs/systems/cleaner/CleanerConfig"); +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds -const AccountsToClean = { - /* Format: - "account1/password1/realm": ["charname1", "charname2"], - "account2/password2/realm": ["charnameX", "charnameY"], - "account3/password3/realm": ["all"] - - To clean a full account, put "account/password/realm": ["all"] - - realm = useast, uswest, europe, asia - - for singleplayer follow format "singleplayer": ["charname1", "charname2"] - - Individual entries are separated with a comma. - */ - - /* Example: - "MyAcc1/tempPass/useast": ["soloSorc"], - "singleplayer": ["solobarb"], - */ - - // Enter your lines under here - -}; - -const CharactersToExclude = [""]; - -// NEW STUFF - Please enter your profile name exactly as is -const profiles = [ - /* Format. Enter in profile exactly the way it appears in D2Bot# - "SCL-ZON123", - "hcnl-pal123", - */ - // Enter your lines under here - -]; - -/* - If you have a lot of profiles that are clones this can be used as an easier way to clean all of them - { - profilePrefix: this is everthing before the suffix numbers. Ex: mypal01 or sccl-pal-001, ect - profileSuffixStart: this is the suffix to start at, Ex: 01 or 001 or 1, all the profiles need have the same format. CANNOT HAVE scl-pal-1 and scl-pal-001 - end: the ending profile suffix, this is used to stop the loop. If you are doing scl-pal-001 to scl-pal-100 (that'd be alot) then 100 would go here - } -*/ -const AdvancedProfileCleanerConfig = [ - // { - // profilePrefix: "scl-sorc-", - // profileSuffixStart: "002", - // end: "009" - // }, - // Your lines under here -]; - -/* Generate accounts to entirely clean ("all") - to use this, set generateAccounts to true and setup the rest of the parameters - - it will generates accounts from start to stop range(included) : - account1/password/realm - account2/password/realm - etc... -*/ - -const AdvancedCleanerConfig = { - generateAccounts: false, - accountPrefix: "account", - accountPassword: "password", - accountRealm: "realm", - rangeStart: 1, - rangeStop: 10 -}; - -// No touchy! -include("json2.js"); -include("polyfill.js"); -include("OOG.js"); -include("common/misc.js"); -include("common/prototypes.js"); -include("common/util.js"); -let sdk = require("./modules/sdk"); -let Controls = require("./modules/Control"); +let Controls = require("./libs/modules/Control"); if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; + Starter.firstRun = true; } let currAcc, charList, realm; let firstAccount = true; -let accounts = []; -let chars = []; +let obj = {}; +const accounts = []; +const chars = []; function dataCleaner () { - if (AdvancedProfileCleanerConfig.length) { - let incrementString = function (text) { - return text.replace(/(\d*)$/, (_, t) => (+t + 1).toString().padStart(t.length, 0)); - }; - - AdvancedProfileCleanerConfig.forEach(p => { - let curr = p.profilePrefix + p.profileSuffixStart; - let end = p.profilePrefix + p.end; - profiles.push(curr); - while (curr !== end) { - curr = incrementString(curr); - profiles.push(curr); - } - }); - } - if (!profiles.length) { - D2Bot.printToConsole("D2BotCleaner: No profiles entered to clean. If this was a mistake, fill out profile information under NEW STUFF. Exiting dataCleaner and moving on to clean characters...", sdk.colors.D2Bot.Gold); - return; - } - - let charClass; - let folder, j; - let charClassMap = {"ZON": "amazon", "SOR": "sorceress", "NEC": "necromancer", "PAL": "paladin", "BAR": "barbarian", "DRU": "druid", "SIN": "assassin"}; - - for (let i = 0; i < profiles.length; i++) { - let buildCheck = profiles[i].toUpperCase().split("-"); - buildCheck[1] = buildCheck[1].toString().substring(0, 3).toUpperCase(); - let charType = buildCheck[0].includes("CC") ? "Classic" : "Expansion"; - let profileExists = false; - let soloplayProfile = false; - - // Filepaths - let dataFP = "data/" + profiles[i] + ".json"; - let gameTimeFP = "libs/SoloPlay/Data/" + profiles[i] + "/" + profiles[i] + "-GameTime" + ".json"; - let charDataFP = "libs/SoloPlay/Data/" + profiles[i] + "/" + profiles[i] + "-CharData" + ".json"; - let lvlPerfFP = "libs/SoloPlay/Data/" + profiles[i] + "/" + profiles[i] + "-LevelingPerformance" + ".csv"; - let scrPerfFP = "libs/SoloPlay/Data/" + profiles[i] + "/" + profiles[i] + "-ScriptPerformance" + ".csv"; - let savePath = "logs/"; // default value in case something goes wrong with assigning actual savePath - - if (charClassMap[buildCheck[1]]) { - charClass = charClassMap[buildCheck[1]]; - soloplayProfile = true; - } else { - //D2Bot.printToConsole("D2BotCleaner: Failed to get charClass. Please check that your profile was entered correctly under NEW STUFF.", sdk.colors.D2Bot.Gold); - //print("Invalid profile name, couldn't set character class"); - charClass = "undefined"; - } - - if (Starter.Config.SaveFiles && soloplayProfile) { - if (FileTools.exists(dataFP) || FileTools.exists(gameTimeFP) || FileTools.exists(charDataFP) || FileTools.exists(lvlPerfFP) || FileTools.exists(scrPerfFP)) { - // Create folder to copy files to - if (!FileTools.exists("libs/SoloPlay/Data/" + charType)) { - folder = dopen("libs/SoloPlay/Data"); - folder.create(charType); - } - - if (!FileTools.exists("libs/SoloPlay/Data/" + charType + "/" + charClass)) { - folder = dopen("libs/SoloPlay/Data/" + charType); - folder.create(charClass); - } - - let files = dopen("libs/SoloPlay/Data/" + charType + "/" + charClass + "/").getFolders(); - j = files.length + 1; - - // make sure folder doesn't already exist. - while (FileTools.exists("libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j.toString())) { - j++; - delay(100); - } - - if (!FileTools.exists("libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j.toString())) { - folder = dopen("libs/SoloPlay/Data/" + charType + "/" + charClass); - folder.create(j.toString()); - } - - savePath = "libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j.toString() + "/" + profiles[i]; - profileExists = true; - } - } - - if (FileTools.exists(dataFP)) { - Starter.Config.SaveFiles && FileTools.copy(dataFP, savePath + "Old.json"); - FileTools.remove(dataFP); - profileExists = true; - } - - if (FileTools.exists(gameTimeFP)) { - Starter.Config.SaveFiles && FileTools.copy(gameTimeFP, savePath + "-GameTimeOld.json"); - FileTools.remove(gameTimeFP); - } - - if (FileTools.exists(charDataFP)) { - Starter.Config.SaveFiles && FileTools.copy(charDataFP, savePath + "-CharDataOld.json"); - FileTools.remove(charDataFP); - } - - if (FileTools.exists(lvlPerfFP)) { - Starter.Config.SaveFiles && FileTools.copy(lvlPerfFP, savePath + "-LevelingPerformanceOld.csv"); - FileTools.remove(lvlPerfFP); - } - - if (FileTools.exists(scrPerfFP)) { - Starter.Config.SaveFiles && FileTools.copy(scrPerfFP, savePath + "-ScriptPerformanceOld.csv"); - FileTools.remove(scrPerfFP); - } - - if (Starter.Config.SaveFiles && profileExists && soloplayProfile) { - D2Bot.printToConsole("D2BotCleaner: Files saved to -> libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j, sdk.colors.D2Bot.Gold); - } - - if (profileExists) { - D2Bot.printToConsole("D2BotCleaner: Cleaned files for -> " + profiles[i], sdk.colors.D2Bot.Gold); - } - - delay(500); - } - - D2Bot.printToConsole("D2BotCleaner: Done cleaning files", sdk.colors.D2Bot.Gold); + if (AdvancedProfileCleanerConfig.length) { + let incrementString = function (text) { + return text.replace(/(\d*)$/, (_, t) => (+t + 1).toString().padStart(t.length, 0)); + }; + + AdvancedProfileCleanerConfig.forEach(p => { + let curr = p.profilePrefix + p.profileSuffixStart; + let end = p.profilePrefix + p.end; + profiles.push(curr); + while (curr !== end) { + curr = incrementString(curr); + profiles.push(curr); + } + }); + } + if (!profiles.length) { + D2Bot.printToConsole( + "D2BotCleaner: No profiles entered to clean. " + + "If this was a mistake, fill out profile information under NEW STUFF. " + + "Exiting dataCleaner and moving on to clean characters...", sdk.colors.D2Bot.Gold + ); + return; + } + + let folder, j; + const charClassMap = new Map([ + ["ZON", "amazon"], + ["SOR", "sorceress"], + ["NEC", "necromancer"], + ["PAL", "paladin"], + ["BAR", "barbarian"], + ["DRU", "druid"], + ["SIN", "assassin"] + ]); + + for (let profile of profiles) { + const buildCheck = profile.toUpperCase().split("-"); + const charClass = charClassMap.get(buildCheck[1].substring(0, 3)) || "undefined"; + buildCheck[1] = buildCheck[1].substring(0, 3); + const charType = buildCheck[0].includes("CC") ? "Classic" : "Expansion"; + let profileExists = false; + const soloplayProfile = charClass !== "undefined"; + + // Filepaths + const dataFP = "data/" + profile + ".json"; + const gameTimeFP = "libs/SoloPlay/Data/" + profile + "/" + profile + "-GameTime" + ".json"; + const charDataFP = "libs/SoloPlay/Data/" + profile + "/" + profile + "-CharData" + ".json"; + const lvlPerfFP = "libs/SoloPlay/Data/" + profile + "/" + profile + "-LevelingPerformance" + ".csv"; + const scrPerfFP = "libs/SoloPlay/Data/" + profile + "/" + profile + "-ScriptPerformance" + ".csv"; + let savePath = "logs/"; // default value in case something goes wrong with assigning actual savePath + + if (CleanerConfig.SaveFiles && soloplayProfile) { + if (FileTools.exists(dataFP) + || FileTools.exists(gameTimeFP) + || FileTools.exists(charDataFP) + || FileTools.exists(lvlPerfFP) + || FileTools.exists(scrPerfFP)) { + // Create folder to copy files to + if (!FileTools.exists("libs/SoloPlay/Data/" + charType)) { + folder = dopen("libs/SoloPlay/Data"); + folder.create(charType); + } + + if (!FileTools.exists("libs/SoloPlay/Data/" + charType + "/" + charClass)) { + folder = dopen("libs/SoloPlay/Data/" + charType); + folder.create(charClass); + } + + let files = dopen("libs/SoloPlay/Data/" + charType + "/" + charClass + "/").getFolders(); + j = files.length + 1; + + // make sure folder doesn't already exist. + while (FileTools.exists("libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j.toString())) { + j++; + delay(100); + } + + if (!FileTools.exists("libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j.toString())) { + folder = dopen("libs/SoloPlay/Data/" + charType + "/" + charClass); + folder.create(j.toString()); + } + + savePath = "libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j.toString() + "/" + profile; + profileExists = true; + } + } + + if (FileTools.exists(dataFP)) { + CleanerConfig.SaveFiles && FileTools.copy(dataFP, savePath + "Old.json"); + FileTools.remove(dataFP); + profileExists = true; + } + + if (FileTools.exists(gameTimeFP)) { + CleanerConfig.SaveFiles && FileTools.copy(gameTimeFP, savePath + "-GameTimeOld.json"); + FileTools.remove(gameTimeFP); + } + + if (FileTools.exists(charDataFP)) { + CleanerConfig.SaveFiles && FileTools.copy(charDataFP, savePath + "-CharDataOld.json"); + FileTools.remove(charDataFP); + } + + if (FileTools.exists(lvlPerfFP)) { + CleanerConfig.SaveFiles && FileTools.copy(lvlPerfFP, savePath + "-LevelingPerformanceOld.csv"); + FileTools.remove(lvlPerfFP); + } + + if (FileTools.exists(scrPerfFP)) { + CleanerConfig.SaveFiles && FileTools.copy(scrPerfFP, savePath + "-ScriptPerformanceOld.csv"); + FileTools.remove(scrPerfFP); + } + + if (CleanerConfig.SaveFiles && profileExists && soloplayProfile) { + D2Bot.printToConsole("D2BotCleaner: Files saved to -> libs/SoloPlay/Data/" + charType + "/" + charClass + "/" + j, sdk.colors.D2Bot.Gold); + } + + if (profileExists) { + D2Bot.printToConsole("D2BotCleaner: Cleaned files for -> " + profile, sdk.colors.D2Bot.Gold); + } + + delay(500); + } + + D2Bot.printToConsole("D2BotCleaner: Done cleaning files", sdk.colors.D2Bot.Gold); } - + function parseInfo () { - for (let i in AccountsToClean) { - if (AccountsToClean.hasOwnProperty(i) && typeof i === "string") { - accounts.push(i); - chars.push(AccountsToClean[i]); - } - } - - if (AdvancedCleanerConfig.generateAccounts) { - for (let index = rangeStart; index <= rangeStop ; index += 1) { - accounts.push(AdvancedCleanerConfig.accountPrefix + index + "/" + AdvancedCleanerConfig.accountPassword + "/" + AdvancedCleanerConfig.accountRealm); - chars.push(["all"]); - } - } - - if (!accounts.length) { - FileTools.remove("logs/D2BotCleaner.json"); - D2Bot.printToConsole("D2BotCleaner: No accounts entered. Exiting...", sdk.colors.D2Bot.Gold); - ControlAction.timeoutDelay("Exiting in: ", 3 * 1e3); - D2Bot.stop(me.profile, true); - } + for (let i in AccountsToClean) { + if (AccountsToClean.hasOwnProperty(i) && typeof i === "string") { + accounts.push(i); + chars.push(AccountsToClean[i]); + } + } + + if (AdvancedCleanerConfig.generateAccounts) { + for (let index = rangeStart; index <= rangeStop ; index += 1) { + const { accountPrefix, accountPassword, accountRealm } = AdvancedCleanerConfig; + accounts.push(accountPrefix + index + "/" + accountPassword + "/" + accountRealm); + chars.push(["all"]); + } + } + + if (!accounts.length) { + FileTools.remove("logs/D2BotCleaner.json"); + D2Bot.printToConsole("D2BotCleaner: No accounts entered. Exiting...", sdk.colors.D2Bot.Gold); + ControlAction.timeoutDelay("Exiting in: ", Time.seconds(3)); + D2Bot.stop(me.profile, true); + } } - + function deleteAllCharacters () { - let characters = ControlAction.getCharacters(); - for (let character of characters) { - let info = {charName: character}; - if (CharactersToExclude.includes(character)) continue; - if (!ControlAction.deleteCharacter(info)) { - print("failed to delete character " + character); - return false; - } - delay(500); - } - return true; + let characters = ControlAction.getCharacters(); + for (let character of characters) { + let info = { charName: character }; + if (CharactersToExclude.includes(character)) continue; + if (!ControlAction.deleteCharacter(info)) { + print("failed to delete character " + character); + return false; + } + delay(500); + } + return true; } -function locationAction (location) { - let i, currChar, - obj = {}; - - switch (location) { - case sdk.game.locations.PreSplash: - ControlAction.click(); - - break; - case sdk.game.locations.WaitingInLine: - Controls.CancelCreateGame.click(); - - break; - case sdk.game.locations.Lobby: - case sdk.game.locations.LobbyChat: - case sdk.game.locations.CreateGame: - case sdk.game.locations.JoinGame: - case sdk.game.locations.Ladder: - case sdk.game.locations.ChannelList: - case sdk.game.locations.GameNameExists: - case sdk.game.locations.GameDoesNotExist: - case sdk.game.locations.GameIsFull: - Controls.LobbyQuit.click(); - - break; - case sdk.game.locations.MainMenu: - case sdk.game.locations.Login: - case sdk.game.locations.SplashScreen: - if (!accounts.length) { - FileTools.remove("logs/D2BotCleaner.json"); - D2Bot.printToConsole("D2BotCleaner: Done cleaning accounts!", sdk.colors.D2Bot.Gold); - D2Bot.stop(me.profile, true); - } - - if (!firstAccount) { - for (i = 0 ; i < Starter.Config.DelayBetweenAccounts; i += 1) { - D2Bot.updateStatus("Waiting " + (Starter.Config.DelayBetweenAccounts - i) + "s for next account"); - delay(1e3); - } - } - - firstAccount = false; - - if (FileTools.exists("logs/D2BotCleaner.json")) { - obj = JSON.parse(FileTools.readText("logs/D2BotCleaner.json")); - - if (obj.currAcc) { - for (i = 0; i < accounts.length; i += 1) { - if (accounts[i].split("/")[0] === obj.currAcc) { - accounts.splice(0, i); - chars.splice(0, i); - - i -= 1; - - break; - } - } - } - } - - let currAccInfo = accounts[0].split("/"); - currAcc = currAccInfo[0]; - obj.currAcc = currAccInfo[0]; - charList = chars[0]; - - D2Bot.printToConsole("D2BotCleaner: Cleaning account:" + currAcc + " , Character list: " + charList, sdk.colors.D2Bot.Gold); - FileTools.writeText("logs/D2BotCleaner.json", JSON.stringify(obj)); - - if (currAcc.toLowerCase() === "singleplayer") { - Controls.SinglePlayer.click(); - } else if (currAccInfo.length === 3) { - realm = currAccInfo[2].toLowerCase(); - ControlAction.loginAccount({account: currAcc, password: currAccInfo[1], realm: realm}); - } - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.CharSelect: - case sdk.game.locations.CharSelectNoChars: - // Single Player screen fix - if (currAcc.toLowerCase() !== "singleplayer") { - if (getLocation() === sdk.game.locations.CharSelect && !Controls.CharSelectCurrentRealm.control) { - Controls.CharSelectExit.click(); - - break; - } - } - - if (!charList.length) { - Controls.CharSelectExit.click(); - - break; - } - - if (charList[0] === "all") { - deleteAllCharacters(); - } else { - if (FileTools.exists("logs/D2BotCleaner.json")) { - obj = JSON.parse(FileTools.readText("logs/D2BotCleaner.json")); - - if (obj.currChar) { - for (i = 0; i < charList.length; i += 1) { - if (charList[i] === obj.currChar) { - // Remove the previous currChar as well - charList.splice(0, i + 1); - - break; - } - } - } - } - - let charInfo = {charName: charList[0]}; - CharactersToExclude.indexOf(charInfo) === -1 && ControlAction.deleteCharacter(charInfo); - delay(500); - } - - currChar = charList.shift(); - obj.currChar = currChar; - - // last char in acc = trigger next acc - if (!charList.length) { - accounts.shift(); - chars.shift(); - Controls.CharSelectExit.click(); - } - - FileTools.writeText("logs/D2BotCleaner.json", JSON.stringify(obj)); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.CharacterCreate: - case sdk.game.locations.NewCharSelected: - Controls.CharSelectExit.click(); - - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - break; - case sdk.game.locations.MainMenuConnecting: // Main Menu - Connecting - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - Starter.LocationEvents.charSelectError(); - - break; - case sdk.game.locations.ServerDown: // Server Down - not much to do but wait.. - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GatewaySelect: - Controls.GatewayCancel.click(); - - break; - default: - if (location !== undefined) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } +const { + locations, + addLocations +} = require("./libs/oog/Locations"); + +locations.set(sdk.game.locations.WaitingInLine, + function () { + Controls.CancelCreateGame.click(); + } +); +addLocations( + [ + sdk.game.locations.Lobby, + sdk.game.locations.LobbyChat, + sdk.game.locations.CreateGame, + sdk.game.locations.JoinGame, + sdk.game.locations.Ladder, + sdk.game.locations.ChannelList, + sdk.game.locations.GameNameExists, + sdk.game.locations.GameDoesNotExist, + sdk.game.locations.GameIsFull, + ], + function () { + Controls.LobbyQuit.click(); + } +); +addLocations( + [ + sdk.game.locations.MainMenu, + sdk.game.locations.Login, + sdk.game.locations.SplashScreen, + ], + function () { + if (!accounts.length) { + FileTools.remove("logs/D2BotCleaner.json"); + D2Bot.printToConsole("D2BotCleaner: Done cleaning accounts!", sdk.colors.D2Bot.Gold); + D2Bot.stop(me.profile, true); + } + + if (!firstAccount) { + ControlAction.timeoutDelay("Waiting for next account in: ", Time.seconds(CleanerConfig.DelayBetweenAccounts)); + } + + firstAccount = false; + + if (FileTools.exists("logs/D2BotCleaner.json")) { + obj = JSON.parse(FileTools.readText("logs/D2BotCleaner.json")); + + if (obj.currAcc) { + for (let i = 0; i < accounts.length; i += 1) { + if (accounts[i].split("/")[0] === obj.currAcc) { + accounts.splice(0, i); + chars.splice(0, i); + + i -= 1; + + break; + } + } + } + } + + let currAccInfo = accounts[0].split("/"); + currAcc = currAccInfo[0]; + obj.currAcc = currAccInfo[0]; + charList = chars[0]; + + D2Bot.printToConsole("D2BotCleaner: Cleaning account:" + currAcc + " , Character list: " + charList, sdk.colors.D2Bot.Gold); + FileTools.writeText("logs/D2BotCleaner.json", JSON.stringify(obj)); + + if (currAcc.toLowerCase() === "singleplayer") { + Controls.SinglePlayer.click(); + } else if (currAccInfo.length === 3) { + realm = currAccInfo[2].toLowerCase(); + ControlAction.loginAccount({ account: currAcc, password: currAccInfo[1], realm: realm }); + } + } +); +addLocations( + [ + sdk.game.locations.CharSelect, + sdk.game.locations.CharSelectNoChars, + ], + function () { + // Single Player screen fix + if (currAcc.toLowerCase() !== "singleplayer") { + if (getLocation() === sdk.game.locations.CharSelect && !Controls.CharSelectCurrentRealm.control) { + Controls.BottomLeftExit.click(); + + return; + } + } + + if (!charList.length) { + Controls.BottomLeftExit.click(); + + return; + } + + if (charList[0] === "all") { + deleteAllCharacters(); + } else { + if (FileTools.exists("logs/D2BotCleaner.json")) { + obj = JSON.parse(FileTools.readText("logs/D2BotCleaner.json")); + + if (obj.currChar) { + for (let i = 0; i < charList.length; i += 1) { + if (charList[i] === obj.currChar) { + // Remove the previous currChar as well + charList.splice(0, i + 1); + + break; + } + } + } + } + + let charInfo = { charName: charList[0] }; + CharactersToExclude.indexOf(charInfo) === -1 && ControlAction.deleteCharacter(charInfo); + delay(500); + } + + let currChar = charList.shift(); + obj.currChar = currChar; + + // last char in acc = trigger next acc + if (!charList.length) { + accounts.shift(); + chars.shift(); + Controls.BottomLeftExit.click(); + } + + FileTools.writeText("logs/D2BotCleaner.json", JSON.stringify(obj)); + } +); + +/** @param {number} loc */ +function locationAction (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } } function main () { - addEventListener("copydata", Starter.receiveCopyData); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - if (Starter.gameInfo.error) { - if (!!DataFile.getStats().debugInfo) { - Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; - D2Bot.printToConsole("Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, sdk.colors.D2Bot.Gray); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } - - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); - - Starter.Config.DataCleaner && dataCleaner(); - !accounts.length && parseInfo(); - - while (true) { - locationAction(getLocation()); - delay(1000); - } + addEventListener("copydata", Starter.receiveCopyData); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + if (Starter.gameInfo.error) { + if (!!DataFile.getStats().debugInfo) { + Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; + D2Bot.printToConsole( + "Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, sdk.colors.D2Bot.Gray + ); + } + + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } + + DataFile.updateStats("debugInfo", JSON.stringify({ currScript: "none", area: "out of game" })); + + CleanerConfig.DataCleaner && dataCleaner(); + !accounts.length && parseInfo(); + + while (true) { + locationAction(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotFollow.dbj b/d2bs/kolbot/D2BotFollow.dbj index 1d41eaec7..59364cfe6 100644 --- a/d2bs/kolbot/D2BotFollow.dbj +++ b/d2bs/kolbot/D2BotFollow.dbj @@ -3,439 +3,400 @@ * @author kolton, theBGuy * @desc Entry script for following bots running on the same pc * +* @typedef {import("./sdk/globals")} */ -include("StarterConfig.js"); -// D2BotFollow specific settings - for global settings see libs/StarterConfig.js +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// D2BotFollow specific settings - for global settings see libs/starter/StarterConfig.js Starter.Config.JoinRetryDelay = 5; // Time in seconds to wait before next join attempt +const { + JoinSettings +} = require("./libs/systems/follow/FollowConfig"); // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds -/* Join game settings - Format: "leader's profile": ["leecher 1 profile", "leecher 2 profile", ...] - If you want everyone to join the same leader, use "leader's profile": ["all"] - NOTE: Use PROFILE names (profile matches window title), NOT character/account names - leader:leecher groups need to be divided by a comma - example: - let JoinSettings = { - "lead1": ["follow1", "follow2"], - "lead2": ["follow3", "follow4"] - }; -*/ +// the only things we really need from these are their oog checks +includeSystemLibs(); -const JoinSettings = { - "Leader": ["Leecher"], -}; - -// No touchy! -include("json2.js"); -include("polyfill.js"); -include("OOG.js"); -include("automule.js"); -include("gambling.js"); -include("craftingsystem.js"); -include("torchsystem.js"); -include("common/misc.js"); -include("common/util.js"); -let sdk = require("./modules/sdk"); -let Controls = require("./modules/Control"); -let Overrides = require("./modules/Override"); - -if (typeof AdvancedConfig[me.profile] === "object") { - Object.assign(Starter.Config, AdvancedConfig[me.profile]); -} +const Overrides = require("./libs/modules/Override"); -let lastGameTick, leader = "", - announced = false, - lastGame = []; +if (typeof Starter.AdvancedConfig[me.profile] === "object") { + Object.assign(Starter.Config, Starter.AdvancedConfig[me.profile]); +} +delete Starter.AdvancedConfig; if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; + Starter.firstRun = true; } new Overrides.Override(Starter, Starter.receiveCopyData, function (orignal, mode, msg) { - if (mode === 3) { - Starter.isUp = (me.gameReady ? "yes" : "no"); - if (!me.gameReady) { - return; - } - Starter.gameInfo.gameName = (me.gamename || ""); - Starter.gameInfo.gamePass = (me.gamepassword || ""); - } else { - orignal(mode, msg); - } + if (mode === 3) { + Starter.isUp = (me.gameReady ? "yes" : "no"); + if (!me.gameReady) { + return; + } + Starter.gameInfo.gameName = (me.gamename || ""); + Starter.gameInfo.gamePass = (me.gamepassword || ""); + } else { + orignal(mode, msg); + } }).apply(); -function joinCheck (leader) { - D2Bot.requestGame(leader); - delay(500); - - if (!Starter.joinInfo.inGame || (lastGame.length && lastGame.indexOf(Starter.joinInfo.gameName) === -1)) { - D2Bot.printToConsole("Game is finished. Stopping join delay."); - - return true; - } - - return false; -} - -function locationAction (location) { - if (me.ingame || location === undefined) { - return; - } - - switch (location) { - case sdk.game.locations.PreSplash: - ControlAction.click(); - - break; - case sdk.game.locations.Lobby: - D2Bot.updateStatus("Lobby"); - - me.blockKeys = false; - Starter.loginRetry = 0; - !Starter.firstLogin && (Starter.firstLogin = true); - - if (Starter.Config.JoinChannel !== "") { - Controls.LobbyEnterChat.click(); - - break; - } - - if (Starter.inGame) { - if (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck()) { - break; - } - - print("updating runs"); - D2Bot.updateRuns(); - - lastGameTick = getTickCount(); - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - } - - Starter.LocationEvents.openJoinGameWindow(); - - break; - case sdk.game.locations.WaitingInLine: - case sdk.game.locations.CreateGame: - Controls.CancelCreateGame.click(); - Controls.JoinGameWindow.click(); - - break; - case sdk.game.locations.LobbyChat: - D2Bot.updateStatus("Lobby Chat"); - - if (Starter.inGame) { - if (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck()) { - break; - } - - print("updating runs"); - D2Bot.updateRuns(); - - lastGameTick = getTickCount(); - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - } - - if (!Starter.chatActionsDone) { - Starter.chatActionsDone = true; - - ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3); - say("/j " + Starter.Config.JoinChannel); - delay(1000); - - if (Starter.Config.FirstJoinMessage !== "") { - say(Starter.Config.FirstJoinMessage); - delay(500); - } - } - - Starter.LocationEvents.openJoinGameWindow(); - - break; - case sdk.game.locations.JoinGame: - D2Bot.updateStatus("Join Game"); - - if (!leader) { - leader = []; - - for (let i in JoinSettings) { - if (JoinSettings.hasOwnProperty(i) && typeof i === "string") { - for (let j = 0; j < JoinSettings[i].length; j += 1) { - if (JoinSettings[i][j] === me.profile || JoinSettings[i][j] === "all") { - leader.push(i); - } - } - } - } - } - - if (!leader || !leader.length && !announced) { - print("No leader"); - D2Bot.printToConsole("No leader"); - announced = true; - - break; - } - - JoinLoop2: - for (let i = 0; i < 5; i += 1) { - for (let j = 0; j < leader.length; j += 1) { - Starter.joinInfo = {}; - D2Bot.requestGame(leader[j]); - delay(100); - - if (Object.keys(Starter.joinInfo).length && Starter.joinInfo.gameName !== "" && (lastGame.indexOf(Starter.joinInfo.gameName) === -1 || Starter.lastGameStatus === "pending")) { - Controls.JoinGameName.setText(Starter.joinInfo.gameName); - Controls.JoinGamePass.setText(Starter.joinInfo.gamePass); - - if (Starter.lastGameStatus === "pending" || (Starter.gameInfo.error && DataFile.getStats().gameName === Starter.joinInfo.gameName)) { - D2Bot.printToConsole("Failed to join game"); - ControlAction.timeoutDelay("Join Delay", Starter.Config.JoinRetryDelay * 1000, joinCheck(leader[j])); - D2Bot.updateRuns(); - D2Bot.requestGame(leader[j]); - delay(200); - - if (!Starter.joinInfo.inGame) { - Starter.lastGameStatus = "ready"; - - break; - } - } - - if (!Starter.joinInfo.inGame) { - continue; - } - - // Don't join immediately after previous game to avoid FTJ - if (getTickCount() - lastGameTick < 5000) { - ControlAction.timeoutDelay("Game Delay", (lastGameTick - getTickCount() + 5000)); - } - - print("joining game " + Starter.joinInfo.gameName); - - if (typeof AdvancedConfig[me.profile] === "object" && typeof AdvancedConfig[me.profile].JoinDelay === "number") { - ControlAction.timeoutDelay("Custom Join Delay", AdvancedConfig[me.profile].JoinDelay * 1e3); - } - - me.blockMouse = true; - - DataFile.updateStats("gameName", Starter.joinInfo.gameName); - Controls.JoinGame.click(); - - me.blockMouse = false; - - lastGame.push(Starter.joinInfo.gameName); - - // Might need a fixed number. Right now it stores 1 game per leader. - lastGame.length > leader.length && lastGame.shift(); - - Starter.lastGameStatus = "pending"; - Starter.locationTimeout(15000, location); - - break JoinLoop2; - } - } - } - - break; - case sdk.game.locations.Ladder: - break; - case sdk.game.locations.ChannelList: - break; - case sdk.game.locations.MainMenu: - case sdk.game.locations.Login: - case sdk.game.locations.CharSelect: - case sdk.game.locations.SplashScreen: - Starter.LocationEvents.login([sdk.game.gametype.TcpIpJoin, sdk.game.profiletype.OpenBattlenet].includes(Profile().type)); - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - Starter.LocationEvents.selectDifficultySP(); - - break; - case sdk.game.locations.MainMenuConnecting: - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - case sdk.game.locations.CharSelectNoChars: - Starter.LocationEvents.charSelectError(); - - break; - case sdk.game.locations.ServerDown: - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GameNameExists: - break; - case sdk.game.locations.GatewaySelect: - Controls.GatewayCancel.click(); - - break; - case sdk.game.locations.GameDoesNotExist: - Starter.LocationEvents.gameDoesNotExist(); - - break; - case sdk.game.locations.GameIsFull: - D2Bot.printToConsole("Game is full"); - Controls.JoinGameWindow.click(); - lastGame.push(Starter.joinInfo.gameName); - Starter.lastGameStatus = "ready"; - - break; - case sdk.game.locations.OtherMultiplayer: - Profile().type === sdk.game.profiletype.TcpIpJoin ? Controls.TcpIp.click() : Controls.OtherMultiplayerCancel.click(); - - break; - case sdk.game.locations.TcpIp: - Profile().type === sdk.game.profiletype.TcpIpJoin ? Controls.TcpIpJoin.click() : Controls.TcpIpCancel.click(); - - break; - case sdk.game.locations.TcpIpEnterIp: - try { - if (!leader) { - leader = []; - - for (let i in JoinSettings) { - if (JoinSettings.hasOwnProperty(i) && typeof i === "string") { - for (let j = 0; j < JoinSettings[i].length; j += 1) { - if (JoinSettings[i][j] === me.profile || JoinSettings[i][j] === "all") { - leader.push(i); - } - } - } - } - } - - mainLoop: - for (let i = 0; i < 3; i++) { - for (let j = 0; j < leader.length; j++) { - D2Bot.requestGame(leader[j]); - - if (Object.keys(Starter.joinInfo).length && Starter.joinInfo.gameName !== "") { - break mainLoop; - } - } - } - - if (Controls.IPAdress.setText(Object.keys(Starter.joinInfo).length ? Starter.joinInfo.gameName : "localhost") - && Controls.IPAdressOk.click() - && Starter.locationTimeout(2e3, sdk.game.locations.TcpIpEnterIp)) { - getLocation() === sdk.game.locations.CharSelect && login(me.profile); - } - } catch (e) { - print(e); - } - - break; - default: - if (location !== undefined) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } -} +const locationAction = (function () { + let announced = false; + let lastGameTick; + + const lastGame = []; + /** + * @type {Map} + */ + const tracker = new Map(); + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations + } = require("./libs/oog/Locations"); + + /** @param {string} leader */ + const joinCheck = function (leader) { + D2Bot.requestGame(leader); + delay(500); + + if (!Starter.joinInfo.inGame || (lastGame.length && lastGame.indexOf(Starter.joinInfo.gameName) === -1)) { + D2Bot.printToConsole("Game is finished. Stopping join delay."); + Starter.gameInfo.gameName = ""; + Starter.gameInfo.gamePass = ""; + + return true; + } + + return false; + }; + + locations.set(sdk.game.locations.Lobby, + function () { + D2Bot.updateStatus("Lobby"); + + me.blockKeys = false; + Starter.loginRetry = 0; + !Starter.firstLogin && (Starter.firstLogin = true); + + if (Starter.Config.JoinChannel !== "") { + Controls.LobbyEnterChat.click(); + + return; + } + + if (Starter.inGame) { + if (AutoMule.outOfGameCheck() + || TorchSystem.outOfGameCheck() + || Gambling.outOfGameCheck() + || CraftingSystem.outOfGameCheck()) { + return; + } + + console.log("updating runs"); + D2Bot.updateRuns(); + + lastGameTick = getTickCount(); + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + } + + Starter.LocationEvents.openJoinGameWindow(); + } + ); + addLocations([sdk.game.locations.WaitingInLine, sdk.game.locations.CreateGame], + function () { + Controls.CancelCreateGame.click(); + Controls.JoinGameWindow.click(); + } + ); + locations.set(sdk.game.locations.LobbyChat, + function () { + D2Bot.updateStatus("Lobby Chat"); + + if (Starter.inGame) { + if (AutoMule.outOfGameCheck() + || TorchSystem.outOfGameCheck() + || Gambling.outOfGameCheck() + || CraftingSystem.outOfGameCheck()) { + return; + } + + console.log("updating runs"); + D2Bot.updateRuns(); + + lastGameTick = getTickCount(); + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + } + + if (!Starter.chatActionsDone) { + Starter.chatActionsDone = true; + + ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3); + say("/j " + Starter.Config.JoinChannel); + delay(1000); + + if (Starter.Config.FirstJoinMessage !== "") { + say(Starter.Config.FirstJoinMessage); + delay(500); + } + } + + Starter.LocationEvents.openJoinGameWindow(); + } + ); + locations.set(sdk.game.locations.JoinGame, + function (location) { + D2Bot.updateStatus("Join Game"); + + if (!tracker.size) { + for (let i in JoinSettings) { + if (JoinSettings.hasOwnProperty(i) && typeof i === "string") { + for (let profile of JoinSettings[i]) { + if (profile === me.profile || profile === "all") { + tracker.set(i, { lastAsked: 0, game: "" }); + } + } + } + } + } + + if (!tracker.size && !announced) { + console.log("No leader"); + D2Bot.printToConsole("No leader"); + announced = true; + + return; + } + + JoinLoop2: + for (let i = 0; i < 5; i += 1) { + for (let [leader, _check] of tracker) { + Starter.joinInfo = {}; + D2Bot.requestGame(leader); + delay(100); + + if (!Starter.joinInfo.hasOwnProperty("gameName") || Starter.joinInfo.gameName === "") { + delay(500); + continue; + } + + /** + * @todo handle rejoin, need to keep track of game averages and when requesting game from a + * leader who's game we left get the current game time + * and see if there is x amount of time left that makes it worth it vs waiting for next. + */ + tracker.set(leader, { lastAsked: getTickCount(), game: Starter.joinInfo.gameName }); + if (lastGame.indexOf(Starter.joinInfo.gameName) === -1 + || Starter.lastGameStatus === "pending") { + Controls.JoinGameName.setText(Starter.joinInfo.gameName); + Controls.JoinGamePass.setText(Starter.joinInfo.gamePass); + + if (Starter.lastGameStatus === "pending" + || (Starter.gameInfo.error && DataFile.getStats().gameName === Starter.joinInfo.gameName)) { + D2Bot.printToConsole("Failed to join game"); + ControlAction.timeoutDelay("Join Delay", Starter.Config.JoinRetryDelay * 1000, joinCheck(leader)); + D2Bot.updateRuns(); + D2Bot.requestGame(leader); + delay(200); + + if (!Starter.joinInfo.inGame) { + Starter.lastGameStatus = "ready"; + + break; + } + } + + if (!Starter.joinInfo.inGame) { + ControlAction.timeoutDelay("Leader Delay", (Starter.joinInfo.delay || 1000) + Time.seconds(3)); + continue; + } + + // Don't join immediately after previous game to avoid FTJ + if (getTickCount() - lastGameTick < 5000) { + ControlAction.timeoutDelay("Game Delay", (lastGameTick - getTickCount() + 5000)); + } + + console.log("joining game " + Starter.joinInfo.gameName); + + if (typeof Starter.Config.JoinDelay === "number") { + ControlAction.timeoutDelay("Custom Join Delay", Starter.Config.JoinDelay * 1e3); + } + + me.blockMouse = true; + + DataFile.updateStats("gameName", Starter.joinInfo.gameName); + Controls.JoinGame.click(); + + me.blockMouse = false; + + lastGame.push(Starter.joinInfo.gameName); + + // Might need a fixed number. Right now it stores 1 game per leader. + lastGame.length > tracker.size && lastGame.shift(); + + Starter.lastGameStatus = "pending"; + Starter.locationTimeout(15000, location); + + break JoinLoop2; + } else { + // for now, if leader is in game and it's the last game we were in. delay to prevent copyData spam + if (lastGame.includes(Starter.joinInfo.gameName)) { + ControlAction.timeoutDelay( + "Waiting for new game from " + leader, + Time.seconds((Starter.joinInfo.inGame ? 5 : 2) * (i + 1)) + ); + } + } + } + } + } + ); + locations.set(sdk.game.locations.GameIsFull, + function () { + D2Bot.printToConsole("Game is full"); + Controls.JoinGameWindow.click(); + lastGame.push(Starter.joinInfo.gameName); + Starter.lastGameStatus = "ready"; + } + ); + locations.set(sdk.game.locations.TcpIp, + function () { + Profile().type === sdk.game.profiletype.TcpIpJoin + ? Controls.TcpIpJoin.click() + : Controls.TcpIpCancel.click(); + } + ); + locations.set(sdk.game.locations.TcpIpEnterIp, + function () { + try { + if (!tracker.size) { + for (let i in JoinSettings) { + if (JoinSettings.hasOwnProperty(i) && typeof i === "string") { + for (let profile of JoinSettings[i]) { + if (profile === me.profile || profile === "all") { + tracker.set(i, { lastAsked: 0, game: "" }); + } + } + } + } + } + + mainLoop: + for (let i = 0; i < 3; i++) { + for (let [leader] of tracker) { + D2Bot.requestGame(leader); + + if (Object.keys(Starter.joinInfo).length && Starter.joinInfo.gameName !== "") { + break mainLoop; + } + } + } + + if (Controls.IPAdress.setText(Object.keys(Starter.joinInfo).length ? Starter.joinInfo.gameName : "localhost") + && Controls.IPAdressOk.click() + && Starter.locationTimeout(2e3, sdk.game.locations.TcpIpEnterIp)) { + getLocation() === sdk.game.locations.CharSelect && login(me.profile); + } + } catch (e) { + console.error(e); + } + } + ); + + return { + /** @param {number} loc */ + run: function (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }, + }; +})(); function main () { - debugLog(me.profile); - addEventListener("copydata", Starter.receiveCopyData); - addEventListener("scriptmsg", Starter.scriptMsgEvent); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - Starter.gameCount = (DataFile.getStats().runs + 1 || 1); - - if (Starter.gameInfo.error) { - delay(200); - - if (!!DataFile.getStats().debugInfo) { - Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; - - D2Bot.printToConsole("Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, sdk.colors.D2Bot.Gray); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } - - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); - - while (!Object.keys(Starter.profileInfo).length) { - D2Bot.getProfile(); - print("Getting Profile"); - delay(500); - } - - while (true) { - // returns true before actually in game so we can't only use this check - while (me.ingame) { - // returns false when switching acts so we can't use while - if (me.gameReady) { - if (!Starter.inGame) { - print("ÿc4Updating Status"); - Starter.lastGameStatus = "ingame"; - Starter.inGame = true; - Starter.gameStart = getTickCount(); - - DataFile.updateStats("runs", Starter.gameCount); - } - - D2Bot.updateStatus(Starter.profileInfo.charName + " | Game: " + (me.gamename || "singleplayer") + Starter.timer(Starter.gameStart)); - } - - delay(1000); - } - - locationAction(getLocation()); - delay(1000); - } + debugLog(me.profile); + addEventListener("copydata", Starter.receiveCopyData); + addEventListener("scriptmsg", Starter.scriptMsgEvent); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + Starter.gameCount = (DataFile.getStats().runs + 1 || 1); + + if (Starter.gameInfo.error) { + delay(200); + + if (!!DataFile.getStats().debugInfo) { + Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; + + D2Bot.printToConsole( + "Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, + sdk.colors.D2Bot.Gray + ); + } + + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } + + DataFile.updateStats("debugInfo", JSON.stringify({ currScript: "none", area: "out of game" })); + + while (!Object.keys(Starter.profileInfo).length) { + D2Bot.getProfile(); + console.log("Getting Profile"); + delay(500); + } + + while (true) { + // returns true before actually in game so we can't only use this check + while (me.ingame) { + // returns false when switching acts so we can't use while + if (me.gameReady) { + if (!Starter.inGame) { + console.log("ÿc4Updating Status"); + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + Starter.gameStart = getTickCount(); + + DataFile.updateStats("runs", Starter.gameCount); + } + + D2Bot.updateStatus( + me.charname + " (" + me.charlvl + ") | Game: " + (me.gamename || "singleplayer") + + Starter.timer(Starter.gameStart) + ); + } + + delay(1000); + } + + locationAction.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotGameAction.dbj b/d2bs/kolbot/D2BotGameAction.dbj index 4044ee662..88b65d683 100644 --- a/d2bs/kolbot/D2BotGameAction.dbj +++ b/d2bs/kolbot/D2BotGameAction.dbj @@ -3,10 +3,14 @@ * @author noah, theBGuy * @desc Entry script for limedrop * +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/mulelogger/MuleLogger")} +* @typedef {import("./libs/systems/gameaction/GameAction")} */ -include("StarterConfig.js"); -// D2BotGameAction specific settings - for global settings see libs/StarterConfig.js +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// D2BotGameAction specific settings - for global settings see libs/starter/StarterConfig.js Starter.Config.MinGameTime = 0; // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby Starter.Config.CreateGameDelay = 5; // Seconds to wait before creating a new game Starter.Config.SwitchKeyDelay = 0; // Seconds to wait before switching a used/banned key or after realm down @@ -14,390 +18,351 @@ Starter.Config.SwitchKeyDelay = 0; // Seconds to wait before switching a used/ba // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds -// No touchy! -include("json2.js"); -include("polyfill.js"); -include("OOG.js"); -include("GameAction.js"); -include("MuleLogger.js"); -include("common/misc.js"); -include("common/util.js"); -let sdk = require("./modules/sdk"); -let Controls = require("./modules/Control"); -let Overrides = require("./modules/Override"); +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +const Overrides = require("./modules/Override"); if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; + Starter.firstRun = true; } -let tag, charList, - ftj = 0, - creatingActions = ["doMule"]; - -new Overrides.Override(Starter, Starter.receiveCopyData, function(orignal, mode, msg) { - if (mode === 3) return; - if (mode === 1638) { - print("Recieved Profile Info"); - tag = JSON.parse(msg).Tag; - } - orignal(mode, msg); +let tag, charList; +let ftj = 0; +let creatingActions = ["doMule"]; + +new Overrides.Override(Starter, Starter.receiveCopyData, function (orignal, mode, msg) { + if (mode === 3) return; + if (mode === 1638) { + console.log("Recieved Profile Info"); + tag = JSON.parse(msg).Tag; + } + orignal(mode, msg); }).apply(); -function locationAction (location) { - let i, string, text, currChar; - - switch (location) { - case sdk.game.locations.PreSplash: - break; - case sdk.game.locations.Lobby: - case sdk.game.locations.LobbyChat: - D2Bot.updateStatus("Lobby"); - - if (Starter.inGame) { - if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { - ControlAction.timeoutDelay("Min game time wait", Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount()); - } - - print("updating runs"); - D2Bot.updateRuns(); - delay(1000); - - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - - Controls.LobbyQuit.click(); - - break; - } - - // a game name was specified - if (GameAction.gameInfo() !== null) { - if (++ftj > 5) { - GameAction.update("done", "GameAction failed to join game!"); - D2Bot.stop(me.profile, true); - break; - } - - if (!Starter.LocationEvents.openCreateGameWindow()) { - break; - } - - Starter.LocationEvents.openJoinGameWindow(); - } else { - if (++ftj > 5) { - GameAction.update("done", "GameAction failed to create game!"); - D2Bot.stop(me.profile, true); - break; - } - - Starter.LocationEvents.openCreateGameWindow(); - } - - break; - case sdk.game.locations.WaitingInLine: - Starter.LocationEvents.waitingInLine(); - - break; - case sdk.game.locations.CreateGame: - if (creatingActions.indexOf(JSON.parse(tag).action) < 0) { - GameAction.update("done", "GameAction failed to create game!"); - D2Bot.stop(me.profile, true); - break; - } - - D2Bot.updateStatus("Creating Game"); - - // remove level restriction - Controls.CharacterDifference.disabled === 5 && Controls.CharacterDifferenceButton.click(); - - // Max number of players - Controls.MaxPlayerCount.setText("8"); - - if (Starter.gameCount >= 99) { - Starter.gameCount = 1; - - DataFile.updateStats("runs", Starter.gameCount); - } - - if (Starter.lastGameStatus === "pending") { - D2Bot.printToConsole("Failed to create game"); - - Starter.gameCount += 1; - } - - ControlAction.timeoutDelay("Make Game Delay", Starter.Config.CreateGameDelay * 1e3); - ControlAction.createGame(Starter.gameInfo.gameName + Starter.gameCount, Starter.gameInfo.gamePass, 0); - Starter.locationTimeout(5000, location); - - Starter.lastGameStatus = "pending"; - - break; - case sdk.game.locations.JoinGame: // Join Game - D2Bot.updateStatus("Join Game"); - let joinInfo = GameAction.gameInfo(); - - joinGame(joinInfo.gameName, joinInfo.gamePass); - Starter.locationTimeout(5000, location); - - break; - case sdk.game.locations.Ladder: - case sdk.game.locations.ChannelList: - Controls.LobbyChannelCancel.click(); - - break; - case sdk.game.locations.MainMenu: // Main Menu - case sdk.game.locations.Login: // Login - case sdk.game.locations.SplashScreen: // D2 Splash - !charList && (charList = GameAction.getCharacters()); - - // last char in list - if (!charList || !charList.length) { - GameAction.update("done", "GameAction has completed task"); - D2Bot.stop(me.profile, true); - delay(5000); - break; - } - - ControlAction.loginAccount(GameAction.getLogin()); - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.CharSelect: // Character Select - // Reset ftj counter - ftj = 0; - - // Single Player screen fix - if (getLocation() === sdk.game.locations.CharSelect && !Controls.CharSelectCurrentRealm.control) { - Controls.CharSelectExit.click(); - - break; - } - - // last char in list - if (!charList || !charList.length) { - GameAction.update("done", "GameAction has completed task"); - D2Bot.stop(me.profile, true); - delay(5000); - break; - } - - // "" empty string means all characters - if (charList[0].length === 0) { - charList = ControlAction.getCharacters(); - - // empty account - if (!charList || !charList.length) { - GameAction.update("done", "Account has no chars!"); - D2Bot.stop(me.profile, true); - delay(5000); - break; - } - } - - currChar = charList.shift(); - - print("ÿc4Game Actionÿc2: Login character: " + currChar); - ControlAction.loginCharacter({charName: currChar}); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.NewCharSelected: // New Character - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - break; - case sdk.game.locations.MainMenuConnecting: - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - Starter.LocationEvents.charSelectError(); - - break; - case sdk.game.locations.ServerDown: // Server Down - not much to do but wait.. - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GameNameExists: // Lobby - Game Name Exists - if (++ftj > 5) { - GameAction.update("done", "GameAction failed to create game!"); - D2Bot.stop(me.profile, true); - break; - } - ControlAction.timeoutDelay("Game Already Exists", 5e3); - Controls.CreateGameWindow.click(); - - break; - case sdk.game.locations.GatewaySelect: // Gateway Select - Controls.GatewayCancel.click(); - - break; - case sdk.game.locations.GameDoesNotExist: // Lobby - Game Does Not Exist - if (++ftj > 5) { - GameAction.update("done", "GameAction failed to join game!"); - D2Bot.stop(me.profile, true); - break; - } - ControlAction.timeoutDelay("Game Doesn't Exist", 5e3); - Controls.JoinGameWindow.click(); - - break; - case sdk.game.locations.GameIsFull: // Game is full - D2Bot.printToConsole("Game is full"); - Starter.lastGameStatus = "ready"; - delay(500); - Controls.JoinGameWindow.click(); - - break; - case sdk.game.locations.CharSelectNoChars: // Empty character screen - // TODO: see if this is needed in case 12 too - string = ""; - text = Controls.CharSelectError.getText(); - - if (text) { - for (i = 0; i < text.length; i += 1) { - string += text[i]; - - if (i !== text.length - 1) { - string += " "; - } - } - - if (string === getLocaleString(sdk.locale.text.CdKeyDisabledFromRealm)) { // CDKey disabled from realm play - D2Bot.updateStatus("Realm Disabled CDKey"); - D2Bot.printToConsole("Realm Disabled CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); - D2Bot.CDKeyDisabled(); - - if (Starter.gameInfo.switchKeys) { - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } else { - GameAction.update("done", "GameAction has failed in location 42"); - D2Bot.stop(me.profile, true); - } - } - } - - if (!Starter.locationTimeout(5000, location)) { - GameAction.update("done", "Account has no chars! location 42"); - D2Bot.stop(me.profile, true); - } - - break; - case sdk.game.locations.OtherMultiplayer: - Controls.OtherMultiplayerCancel.click(); - - break; - case sdk.game.locations.TcpIp: - case sdk.game.locations.TcpIpEnterIp: - Controls.TcpIpCancel.click(); - - break; - default: - if (location !== undefined) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } -} +const locationAction = (function () { + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations, + parseControlText + } = require("./libs/oog/Locations"); + + addLocations([sdk.game.locations.Lobby, sdk.game.locations.LobbyChat], + function () { + D2Bot.updateStatus("Lobby"); + + if (Starter.inGame) { + if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { + ControlAction.timeoutDelay( + "Min game time wait", + Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount() + ); + } + + console.log("updating runs"); + D2Bot.updateRuns(); + delay(1000); + + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + + Controls.LobbyQuit.click(); + + return; + } + + // a game name was specified + if (GameAction.gameInfo() !== null) { + if (++ftj > 5) { + GameAction.update("done", "GameAction failed to join game!"); + D2Bot.stop(me.profile, true); + return; + } + + if (!Starter.LocationEvents.openCreateGameWindow()) { + return; + } + + Starter.LocationEvents.openJoinGameWindow(); + } else { + if (++ftj > 5) { + GameAction.update("done", "GameAction failed to create game!"); + D2Bot.stop(me.profile, true); + return; + } + + Starter.LocationEvents.openCreateGameWindow(); + } + } + ); + locations.set(sdk.game.locations.CreateGame, + function (location) { + if (creatingActions.indexOf(JSON.parse(tag).action) < 0) { + GameAction.update("done", "GameAction failed to create game!"); + D2Bot.stop(me.profile, true); + return; + } + + D2Bot.updateStatus("Creating Game"); + + // remove level restriction + if (Controls.CharacterDifference.disabled === 5) { + Controls.CharacterDifferenceButton.click(); + } + + // Max number of players + Controls.MaxPlayerCount.setText("8"); + + if (Starter.gameCount >= 99) { + Starter.gameCount = 1; + + DataFile.updateStats("runs", Starter.gameCount); + } + + if (Starter.lastGameStatus === "pending") { + D2Bot.printToConsole("Failed to create game"); + + Starter.gameCount += 1; + } + + ControlAction.timeoutDelay("Make Game Delay", Starter.Config.CreateGameDelay * 1e3); + ControlAction.createGame(Starter.gameInfo.gameName + Starter.gameCount, Starter.gameInfo.gamePass, 0); + Starter.locationTimeout(5000, location); + + Starter.lastGameStatus = "pending"; + } + ); + locations.set(sdk.game.locations.JoinGame, + function (location) { + D2Bot.updateStatus("Join Game"); + let joinInfo = GameAction.gameInfo(); + + joinGame(joinInfo.gameName, joinInfo.gamePass); + Starter.locationTimeout(5000, location); + } + ); + addLocations([sdk.game.locations.Ladder, sdk.game.locations.ChannelList], + function () { + Controls.LobbyChannelCancel.click(); + } + ); + addLocations([sdk.game.locations.MainMenu, sdk.game.locations.Login, sdk.game.locations.SplashScreen], + function () { + !charList && (charList = GameAction.getCharacters()); + + // last char in list + if (!charList || !charList.length) { + GameAction.update("done", "GameAction has completed task"); + D2Bot.stop(me.profile, true); + delay(5000); + return; + } + + ControlAction.loginAccount(GameAction.getLogin()); + } + ); + locations.set(sdk.game.locations.CharSelect, + function () { + // Reset ftj counter + ftj = 0; + + // Single Player screen fix + if (getLocation() === sdk.game.locations.CharSelect + && !Controls.CharSelectCurrentRealm.control) { + Controls.BottomLeftExit.click(); + + return; + } + + // last char in list + if (!charList || !charList.length) { + GameAction.update("done", "GameAction has completed task"); + D2Bot.stop(me.profile, true); + delay(5000); + return; + } + + // "" empty string means all characters + if (charList[0].length === 0) { + charList = ControlAction.getCharacters(); + + // empty account + if (!charList || !charList.length) { + GameAction.update("done", "Account has no chars!"); + D2Bot.stop(me.profile, true); + delay(5000); + return; + } + } + + let currChar = charList.shift(); + + console.log("ÿc4Game Actionÿc2: Login character: " + currChar); + ControlAction.loginCharacter({ charName: currChar }); + } + ); + locations.set(sdk.game.locations.SelectDifficultySP, + function () { + hideConsole(); + sendKey(sdk.keys.Escape); + } + ); + locations.set(sdk.game.locations.GameNameExists, + function () { + if (++ftj > 5) { + GameAction.update("done", "GameAction failed to create game!"); + D2Bot.stop(me.profile, true); + return; + } + ControlAction.timeoutDelay("Game Already Exists", 5e3); + Controls.CreateGameWindow.click(); + } + ); + locations.set(sdk.game.locations.GameDoesNotExist, + function () { + if (++ftj > 5) { + GameAction.update("done", "GameAction failed to join game!"); + D2Bot.stop(me.profile, true); + return; + } + ControlAction.timeoutDelay("Game Doesn't Exist", 5e3); + Controls.JoinGameWindow.click(); + } + ); + locations.set(sdk.game.locations.GameIsFull, + function () { + D2Bot.printToConsole("Game is full"); + Starter.lastGameStatus = "ready"; + delay(500); + Controls.JoinGameWindow.click(); + } + ); + locations.set(sdk.game.locations.CharSelectNoChars, + function (location) { + // TODO: see if this is needed in case 12 too + let string = parseControlText(Controls.CharSelectError); + + if (string) { + if (string === getLocaleString(sdk.locale.text.CdKeyDisabledFromRealm)) { // CDKey disabled from realm play + D2Bot.updateStatus("Realm Disabled CDKey"); + D2Bot.printToConsole("Realm Disabled CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); + D2Bot.CDKeyDisabled(); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + GameAction.update("done", "GameAction has failed in location 42"); + D2Bot.stop(me.profile, true); + } + } + } + + if (!Starter.locationTimeout(5000, location)) { + GameAction.update("done", "Account has no chars! location 42"); + D2Bot.stop(me.profile, true); + } + } + ); + locations.set(sdk.game.locations.TcpIp, + function () { + Controls.TcpIpCancel.click(); + } + ); + + return { + /** @param {number} loc */ + run: function (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }, + }; +})(); function main () { - addEventListener("copydata", Starter.receiveCopyData); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - delay(500); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - Starter.gameCount = (DataFile.getStats().runs + 1 || 1); - - while (!tag) { - D2Bot.getProfile(); - delay(500); - } - - if (Starter.gameInfo.rdBlocker) { - D2Bot.printToConsole("You must disable RD Blocker for Mule Logger to work properly. Stopping."); - GameAction.update("done", "GameAction has failed, please disable RD Blocker"); - D2Bot.stop(me.profile, true); - - return; - } - - GameAction.init(tag); - - if (Starter.gameInfo.error) { - if (!!DataFile.getStats().debugInfo) { - Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; - - D2Bot.printToConsole("Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, sdk.colors.D2Bot.Gray); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } - - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); - - while (true) { - // returns true before actually in game so we can't only use this check - while (me.ingame) { - // returns false when switching acts so we can't use while - if (me.gameReady) { - if (!Starter.inGame) { - print("Updating Status"); - D2Bot.updateStatus("Game: " + me.gamename); - - Starter.lastGameStatus = "ingame"; - Starter.inGame = true; - Starter.gameStart = getTickCount(); - - DataFile.updateStats("runs", Starter.gameCount); - } - } - - delay(1000); - } - - locationAction(getLocation()); - delay(1000); - } + addEventListener("copydata", Starter.receiveCopyData); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + delay(500); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + Starter.gameCount = (DataFile.getStats().runs + 1 || 1); + + while (!tag) { + D2Bot.getProfile(); + delay(500); + } + + if (Starter.gameInfo.rdBlocker) { + D2Bot.printToConsole("You must disable RD Blocker for Mule Logger to work properly. Stopping."); + GameAction.update("done", "GameAction has failed, please disable RD Blocker"); + D2Bot.stop(me.profile, true); + + return; + } + + GameAction.init(tag); + + if (Starter.gameInfo.error) { + if (!!DataFile.getStats().debugInfo) { + Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; + + D2Bot.printToConsole( + "Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, + sdk.colors.D2Bot.Gray + ); + } + + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } + + DataFile.updateStats("debugInfo", JSON.stringify({ currScript: "none", area: "out of game" })); + + while (true) { + // returns true before actually in game so we can't only use this check + while (me.ingame) { + // returns false when switching acts so we can't use while + if (me.gameReady) { + if (!Starter.inGame) { + console.log("Updating Status"); + D2Bot.updateStatus("Game: " + me.gamename); + + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + Starter.gameStart = getTickCount(); + + DataFile.updateStats("runs", Starter.gameCount); + } + } + + delay(1000); + } + + locationAction.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotLead.dbj b/d2bs/kolbot/D2BotLead.dbj index 051da986b..17ea78a17 100644 --- a/d2bs/kolbot/D2BotLead.dbj +++ b/d2bs/kolbot/D2BotLead.dbj @@ -4,398 +4,141 @@ * @author kolton, theBGuy * @desc Entry script for leader * +* @typedef {import("./sdk/globals")} */ -include("StarterConfig.js"); -// - for global settings see libs/StarterConfig.js +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// - for global settings @see libs/starter/StarterConfig.js // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds -// No touchy! -include("json2.js"); -include("OOG.js"); -include("automule.js"); -include("gambling.js"); -include("craftingsystem.js"); -include("torchsystem.js"); -include("Polyfill.js"); -include("common/misc.js"); -include("common/util.js"); -let sdk = require("./modules/sdk"); -let Controls = require("./modules/Control"); +// the only things we really need from these are their oog checks +includeSystemLibs(); -if (typeof AdvancedConfig[me.profile] === "object") { - Object.assign(Starter.Config, AdvancedConfig[me.profile]); +const Controls = require("./libs/modules/Control"); + +if (typeof Starter.AdvancedConfig[me.profile] === "object") { + Object.assign(Starter.Config, Starter.AdvancedConfig[me.profile]); } +delete Starter.AdvancedConfig; if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; + Starter.firstRun = true; } -function locationAction (location) { - switch (location) { - case sdk.game.locations.PreSplash: - ControlAction.click(); - Starter.locationTimeout(5000, location); - getLocation() === sdk.game.locations.PreSplash && sendKey(0x0D); - - break; - case sdk.game.locations.Lobby: - D2Bot.updateStatus("Lobby"); - - me.blockKeys = false; - Starter.loginRetry = 0; - !Starter.firstLogin && (Starter.firstLogin = true); - Starter.lastGameStatus === "pending" && (Starter.gameCount += 1); - - if (Starter.Config.PingQuitDelay && Starter.pingQuit) { - ControlAction.timeoutDelay("Ping Delay", Starter.Config.PingQuitDelay * 1e3); - Starter.pingQuit = false; - } - - if (Starter.Config.JoinChannel !== "" || (AdvancedConfig[me.profile] && AdvancedConfig[me.profile].JoinChannel !== "")) { - Controls.LobbyEnterChat.click(); - - break; - } - - if (Starter.inGame || Starter.gameInfo.error) { - !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick); - - if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { - ControlAction.timeoutDelay("Min game time wait", Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount()); - } - } - - if (Starter.inGame) { - if (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck()) { - break; - } - - print("updating runs"); - D2Bot.updateRuns(); - - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - - if (Starter.Config.ResetCount && Starter.gameCount > Starter.Config.ResetCount) { - Starter.gameCount = 1; - DataFile.updateStats("runs", Starter.gameCount); - } - } - - Starter.LocationEvents.openCreateGameWindow(); - - break; - case sdk.game.locations.WaitingInLine: - Starter.LocationEvents.waitingInLine(); - - break; - case sdk.game.locations.LobbyChat: - D2Bot.updateStatus("Lobby Chat"); - Starter.lastGameStatus === "pending" && (Starter.gameCount += 1); - - if (Starter.inGame || Starter.gameInfo.error) { - !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick); - - if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { - ControlAction.timeoutDelay("Min game time wait", Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount()); - } - } - - if (Starter.inGame) { - if (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck()) { - break; - } - - print("updating runs"); - D2Bot.updateRuns(); - - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - - if (Starter.Config.ResetCount && Starter.gameCount > Starter.Config.ResetCount) { - Starter.gameCount = 1; - DataFile.updateStats("runs", Starter.gameCount); - } - - if (AdvancedConfig[me.profile] && AdvancedConfig[me.profile].hasOwnProperty("AfterGameMessage")) { - Starter.chanInfo.afterMsg = AdvancedConfig[me.profile].AfterGameMessage; - } else { - Starter.chanInfo.afterMsg = Starter.Config.AfterGameMessage; - } - - // check that we are in the channel we are supposed to be in - if (Starter.chanInfo.joinChannel.length) { - let chanName = Controls.LobbyChannelName.getText(); - chanName && (chanName = chanName.toString()); - chanName && (chanName = chanName.slice(0, chanName.indexOf("(") - 1)); - Starter.chanInfo.joinChannel.indexOf(chanName) === -1 && (Starter.chatActionsDone = false); - } - - if (Starter.chanInfo.afterMsg) { - if (typeof Starter.chanInfo.afterMsg === "string") { - Starter.chanInfo.afterMsg = [Starter.chanInfo.afterMsg]; - } - - for (let i = 0; i < Starter.chanInfo.afterMsg.length; i += 1) { - Starter.sayMsg(Starter.chanInfo.afterMsg[i]); - delay(500); - } - } - } - - if (!Starter.chatActionsDone) { - Starter.chatActionsDone = true; - - if (AdvancedConfig[me.profile] && AdvancedConfig[me.profile].hasOwnProperty("JoinChannel")) { - Starter.chanInfo.joinChannel = AdvancedConfig[me.profile].JoinChannel; - } else { - Starter.chanInfo.joinChannel = Starter.Config.JoinChannel; - } - - if (AdvancedConfig[me.profile] && AdvancedConfig[me.profile].hasOwnProperty("FirstJoinMessage")) { - Starter.chanInfo.firstMsg = AdvancedConfig[me.profile].FirstJoinMessage; - } else { - Starter.chanInfo.firstMsg = Starter.Config.FirstJoinMessage; - } - - if (Starter.chanInfo.joinChannel) { - typeof Starter.chanInfo.joinChannel === "string" && (Starter.chanInfo.joinChannel = [Starter.chanInfo.joinChannel]); - typeof Starter.chanInfo.firstMsg === "string" && (Starter.chanInfo.firstMsg = [Starter.chanInfo.firstMsg]); - - for (let i = 0; i < Starter.chanInfo.joinChannel.length; i += 1) { - ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3); - - if (ControlAction.joinChannel(Starter.chanInfo.joinChannel[i])) { - Starter.useChat = true; - } else { - print("ÿc1Unable to join channel, disabling chat messages."); - Starter.useChat = false; - } - - if (Starter.chanInfo.firstMsg[i] !== "") { - Starter.sayMsg(Starter.chanInfo.firstMsg[i]); - delay(500); - } - } - } - } - - // Announce game - if (AdvancedConfig[me.profile] && AdvancedConfig[me.profile].hasOwnProperty("AnnounceGames")) { - Starter.chanInfo.announce = AdvancedConfig[me.profile].AnnounceGames; - } else { - Starter.chanInfo.announce = Starter.Config.AnnounceGames; - } - - Starter.LocationEvents.openCreateGameWindow(); - - break; - case sdk.game.locations.CreateGame: - D2Bot.updateStatus("Creating Game"); - - if (typeof Starter.Config.CharacterDifference === "number") { - Controls.CharacterDifference.disabled === sdk.game.controls.Disabled && Controls.CharacterDifferenceButton.click(); - Controls.CharacterDifference.setText(Starter.Config.CharacterDifference.toString()); - } else if (!Starter.Config.CharacterDifference && Controls.CharacterDifference.disabled === 5) { - Controls.CharacterDifferenceButton.click(); - } - - typeof Starter.Config.MaxPlayerCount === "number" && Controls.MaxPlayerCount.setText(Starter.Config.MaxPlayerCount.toString()); - - // Get game name if there is none - while (!Starter.gameInfo.gameName) { - D2Bot.requestGameInfo(); - delay(500); - } - - // FTJ handler - if (Starter.lastGameStatus === "pending") { - Starter.isUp = "no"; - D2Bot.printToConsole("Failed to create game"); - ControlAction.timeoutDelay("FTJ delay", Starter.Config.FTJDelay * 1e3); - D2Bot.updateRuns(); - } - - let gameName = (Starter.gameInfo.gameName === "?" ? Starter.randomString(null, true) : Starter.gameInfo.gameName + Starter.gameCount); - let gamePass = (Starter.gameInfo.gamePass === "?" ? Starter.randomString(null, true) : Starter.gameInfo.gamePass); - - ControlAction.createGame(gameName, gamePass, Starter.gameInfo.difficulty, Starter.Config.CreateGameDelay * 1000); - - Starter.lastGameStatus = "pending"; - Starter.setNextGame(Starter.gameInfo); - Starter.locationTimeout(10000, location); - - break; - case sdk.game.locations.JoinGame: - case sdk.game.locations.Ladder: - case sdk.game.locations.ChannelList: - Starter.LocationEvents.openCreateGameWindow(); - - break; - case sdk.game.locations.MainMenu: - case sdk.game.locations.SplashScreen: - case sdk.game.locations.Login: - case sdk.game.locations.CharSelect: - Starter.LocationEvents.login([sdk.game.gametype.TcpIpHost, sdk.game.profiletype.OpenBattlenet].includes(Profile().type)); - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - Starter.LocationEvents.selectDifficultySP(); - - break; - case sdk.game.locations.MainMenuConnecting: - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - case sdk.game.locations.CharSelectNoChars: - Starter.LocationEvents.charSelectError(); - - break; - case sdk.game.locations.ServerDown: - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GameNameExists: - case sdk.game.locations.GameIsFull: - Controls.CreateGameWindow.click(); - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - - break; - case sdk.game.locations.GatewaySelect: - Controls.GatewayCancel.click(); - - break; - case sdk.game.locations.GameDoesNotExist: - Starter.LocationEvents.gameDoesNotExist(); - - break; - case sdk.game.locations.CharacterCreate: - Controls.CharSelectExit.click(); - - break; - case sdk.game.locations.OtherMultiplayer: - Starter.LocationEvents.otherMultiplayerSelect(); - - break; - case sdk.game.locations.TcpIp: - Profile().type === sdk.game.profiletype.TcpIpHost ? Controls.TcpIpHost.click() : Controls.TcpIpCancel.click(); - - break; - case sdk.game.locations.TcpIpEnterIp: - Controls.TcpIpCancel.click(); - - break; - default: - if (location !== undefined) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } -} +const locationAction = (function () { + const { + locations, + } = require("./libs/oog/Locations"); + + return { + /** @param {number} loc */ + run: function (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }, + }; +})(); function main () { - debugLog(me.profile); - addEventListener("copydata", Starter.receiveCopyData); - addEventListener("scriptmsg", Starter.scriptMsgEvent); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - delay(500); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - Starter.gameCount = (DataFile.getStats().runs + 1 || 1); - - if (Starter.gameInfo.error) { - delay(200); - - if (!!DataFile.getStats().debugInfo) { - Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; - D2Bot.printToConsole("Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, sdk.colors.D2Bot.Gray); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } - - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); - - while (!Object.keys(Starter.profileInfo).length) { - D2Bot.getProfile(); - print("Getting Profile"); - delay(500); - } - - while (true) { - // returns true before actually in game so we can't only use this check - while (me.ingame) { - // returns false when switching acts so we can't use while - if (me.gameReady) { - Starter.isUp = "yes"; - - if (!Starter.inGame) { - Starter.gameStart = getTickCount(); - Starter.lastGameStatus = "ingame"; - Starter.inGame = true; - - DataFile.updateStats("runs", Starter.gameCount); - DataFile.updateStats("ingameTick"); - } - - D2Bot.updateStatus(Starter.profileInfo.charName + " | Game: " + (me.gamename || "singleplayer") + Starter.timer(Starter.gameStart)); - } - - delay(1000); - } - - Starter.isUp = "no"; - - locationAction(getLocation()); - delay(1000); - } + debugLog(me.profile); + + const Worker = require("./libs/modules/Worker"); + Worker.runInBackground.copyData = (function () { + const workBench = []; + addEventListener("copydata", function (mode, msg) { + workBench.push({ mode: mode, msg: msg }); + }); + + return function () { + if (!workBench.length) return true; + + while (workBench.length) { + const { mode, msg } = workBench.shift(); + Starter.receiveCopyData(mode, msg); + } + + return true; + }; + })(); + // addEventListener("copydata", Starter.receiveCopyData); + addEventListener("scriptmsg", Starter.scriptMsgEvent); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + delay(500); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + while (!Object.keys(Starter.profileInfo).length) { + D2Bot.getProfile(); + console.log("Getting Profile"); + delay(500); + } + + Starter.gameCount = (DataFile.getStats().runs + 1 || 1); + + if (Starter.gameInfo.error) { + delay(200); + + if (!!DataFile.getStats().debugInfo) { + Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; + D2Bot.printToConsole( + "Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, + sdk.colors.D2Bot.Gray + ); + } + + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } + + DataFile.updateStats("debugInfo", JSON.stringify({ currScript: "none", area: "out of game" })); + while (true) { + // returns true before actually in game so we can't only use this check + while (me.ingame) { + // returns false when switching acts so we can't use while + if (me.gameReady) { + Starter.isUp = "yes"; + + if (!Starter.inGame) { + Starter.gameStart = getTickCount(); + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + + DataFile.updateStats(["runs", "ingameTick", "currentGame"], Starter.gameCount); + } + + D2Bot.updateStatus( + me.charname + " (" + me.charlvl + ") | Game: " + (me.gamename || "singleplayer") + + Starter.timer(Starter.gameStart) + ); + } + + delay(1000); + } + + Starter.isUp = "no"; + + locationAction.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotMap.dbj b/d2bs/kolbot/D2BotMap.dbj index 280669538..58efe4d53 100644 --- a/d2bs/kolbot/D2BotMap.dbj +++ b/d2bs/kolbot/D2BotMap.dbj @@ -4,40 +4,46 @@ * @desc Entry script for running map mode * */ -include("json2.js"); -include("polyfill.js"); -include("OOG.js"); -include("common/misc.js"); -include("common/util.js"); function main () { - addEventListener("copydata", Starter.receiveCopyData); + include("critical.js"); // required + + if (!FileTools.exists("data/" + me.profile + ".json")) { + DataFile.create(); + } - while (!Starter.handle) { - delay(100); - } + addEventListener("copydata", Starter.receiveCopyData); - DataFile.updateStats("handle", Starter.handle); - delay(500); - D2Bot.init(); - load("tools/heartbeat.js"); + while (!Starter.handle) { + delay(100); + } - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } + DataFile.updateStats("handle", Starter.handle); + delay(500); + D2Bot.init(); + load("threads/heartbeat.js"); - while (true) { - delay(1000); - - if (me.gameReady) { - Starter.isUp === "no" && (Starter.isUp = "yes"); - me.ingame && D2Bot.updateStatus("(Char: " + me.charname + ") (Game: " + (me.gamename || "singleplayer") + ") (Level: " + me.charlvl + ")"); - } else { - D2Bot.updateStatus("Out of Game"); - Starter.isUp = "no"; - } + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } - delay(1000); - } + while (true) { + delay(1000); + + if (me.gameReady) { + Starter.isUp === "no" && (Starter.isUp = "yes"); + if (me.ingame) { + D2Bot.updateStatus( + me.charname + " (" + me.charlvl + ") | Game: " + (me.gamename || "singleplayer") + + Starter.timer(Starter.gameStart) + ); + } + } else { + D2Bot.updateStatus("Out of Game"); + Starter.isUp = "no"; + } + + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotMule.dbj b/d2bs/kolbot/D2BotMule.dbj index ba76e9b69..8896f8cf1 100644 --- a/d2bs/kolbot/D2BotMule.dbj +++ b/d2bs/kolbot/D2BotMule.dbj @@ -3,14 +3,13 @@ * @author kolton, theBGuy * @desc Entry script for AutoMule.js * +* @typedef {import("./sdk/globals")} */ -include("StarterConfig.js"); +js_strict(true); +include("critical.js"); // required -/** - * @todo redo how muleing is handled, really dislike how it's handled here - */ - -// D2BotMule specific settings - for global settings see libs/StarterConfig.js +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// D2BotMule specific settings - for global settings see libs/starter/StarterConfig.js Starter.Config.MinGameTime = 30; // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby Starter.Config.MaxGameTime = 60; // Maximum game length in minutes, only for continuous muling Starter.Config.CreateGameDelay = 5; // Seconds to wait before creating a new game @@ -21,992 +20,583 @@ Starter.Config.MakeAccountOnFailure = true; // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds - -// No touchy! -include("json2.js"); -include("oog.js"); -include("automule.js"); -include("mulelogger.js"); -include("torchsystem.js"); -include("NTItemParser.dbl"); -include("NTItemAlias.dbl"); -include("common/util.js"); -includeCommonLibs(); - -let Controls = require("./modules/Control"); -let Overrides = require("./modules/Override"); -const Worker = require("./modules/Worker"); - -/** @global */ -let master, muleMode, muleFilename, muleObj, maxCharCount; -let [checkOnJoin, makeNext] = [false, false]; -let status = "loading"; -let masterStatus = { status: "" }; - -/** - * Mule Data object manipulates external mule datafile - */ -const MuleData = { - _default: { - account: "", - accNum: 0, - character: "", - charNum: 0, - fullChars: [], - torchChars: [] - }, - // create a new mule datafile - create: function () { - let string = JSON.stringify(this._default); - FileTools.writeText(muleFilename, string); - }, - - // read data from the mule datafile and return the data object - read: function () { - let string = FileTools.readText(muleFilename); - let obj = JSON.parse(string); - - return obj; - }, - - // write a data object to the mule datafile - write: function (obj) { - let string = JSON.stringify(obj); - FileTools.writeText(muleFilename, string); - }, - - // set next account - increase account number in mule datafile - nextAccount: function () { - let obj = MuleData.read(); - - obj.accNum += 1; - obj.account = muleObj.accountPrefix + obj.accNum; - - MuleData.write(Object.assign(this._default, { accNum: obj.accNum, account: obj.account })); - - return obj.account; - }, - - nextChar: function () { - checkOnJoin = true; - let charSuffix = ""; - let charNumbers = "abcdefghijklmnopqrstuvwxyz"; - let obj = MuleData.read(); - - // dirty - obj.charNum > 25 && (obj.charNum = 0); - let num = obj.accNum.toString(); - - for (let i = 0; i < num.length; i++) { - charSuffix += charNumbers[parseInt(num[i], 10)]; - } - - charSuffix += charNumbers[obj.charNum]; - obj.charNum = obj.charNum + 1; - obj.character = muleObj.charPrefix + charSuffix; - - MuleData.write(obj); - - return obj.character; - }, -}; - -const Mule = { - continuousMule: false, - clearedJunk: false, - startTick: 0, - idleTick: 0, - areaTick: 0, - recheckTick: 0, - statusString: "", - - /** - * @description background worker to prevent idle disconnect - */ - antiIdle: function () { - if (!Starter.inGame) return true; - if (!me.ingame || getTickCount() - me.gamestarttime < Time.minutes(1) || !me.gameReady) return true; - if (Mule.idleTick === 0) { - Mule.idleTick = getTickCount() + Time.seconds(rand(1200, 1500)); - console.log("Game start, anti-idle refresh in: (" + Time.format(Mule.idleTick - getTickCount()) + ")"); - } - if (me.gameReady) { - if (getTickCount() - Mule.idleTick > 0) { - Packet.questRefresh(); - Mule.idleTick += Time.seconds(rand(1200, 1500)); - console.log("Sent anti-idle packet, next refresh in: (" + Time.format(Mule.idleTick - getTickCount()) + ")"); - } - } else if (getLocation() !== null) { - Mule.idleTick = 0; - } - - return true; - }, - - /** - * @description background worker to prevent suicide walks - * @todo figure out why this bugs out when used - */ - areaWatcher: function () { - if (!Starter.inGame) return true; - // run area check every half second - if (getTickCount() - Mule.areaTick < 500) return true; - Mule.areaTick = getTickCount(); - // check that we are actually in game and that we've been there longer than a minute - if (getLocation() !== null || getTickCount() - me.gamestarttime < Time.minutes(1)) return true; - console.debug("Check Area: " + me.area); - - if (me.ingame && me.gameReady && me.area > 0) { - if (me.area !== sdk.areas.RogueEncampment) { - console.warn("Preventing Suicide Walk! Current Area: " + me.area); - console.trace(); - - Mule.quit(); - } - } - - return true; - }, - - init: function () { - Mule.startTick = getTickCount(); - - while ((getLocation() !== null) && !!me.area && getTickCount() - Mule.startTick < Time.seconds(10)) { - delay(200); - } - - if (getLocation() !== null || !me.ingame || !me.gameReady || !me.inTown) { - return false; - } - - Starter.firstLogin ? (Starter.firstLogin = false) : (status = "begin"); - status !== "begin" && (status = "ready"); - Mule.statusString = "In " + (muleMode === 2 ? "anni " : muleMode === 1 ? "torch " : "") + "mule game."; - - D2Bot.updateStatus(Mule.statusString + " Status: " + status); - D2Bot.printToConsole(Mule.statusString, sdk.colors.D2Bot.DarkGold); - Mule.recheckTick = getTickCount(); - - Town.goToTown(1); - Town.move("stash"); - - // Move away from stash so we don't block muler - let coord = {}; - do { - delay(1000); - coord = CollMap.getRandCoordinate(me.x, -6, 6, me.y, -6, 6); - } while (!Attack.validSpot(coord.x, coord.y)); - - Pather.moveToUnit(coord); - - Storage.Init(); - checkOnJoin && (status = "begin"); - Starter.inGame = true; - Mule.continuousMule && !muleObj.onlyLogWhenFull && MuleLogger.logChar(); - // Worker.runInBackground.areaWatcher = Mule.areaWatcher; // bugs for some reason, quits game then on re-join d2bot spams In mule game for ~30seconds - if (Worker.runInBackground.antiIdle === undefined) { - Worker.runInBackground.antiIdle = Mule.antiIdle; - } - - return true; - }, - - waitForMaster: function () { - console.log("Waiting for muler"); - // forever alone check? - Misc.poll(() => status === "begin", Time.minutes(3), 100); - - if (status !== "begin") { - D2Bot.printToConsole("Nobody joined - stopping.", sdk.colors.D2Bot.Red); - D2Bot.stop(me.profile, true); - } - - me.overhead("begin"); - }, - - /** - * @todo check if there are any other profiles that need to mule while we are already in game? - */ - done: function () { - !muleObj.onlyLogWhenFull && MuleLogger.logChar(); - - let obj = MuleData.read(); - - if (Mule.checkAnniTorch() && obj.torchChars.indexOf(me.name) === -1) { - obj.torchChars.push(me.name); - } - - MuleData.write(obj); - D2Bot.printToConsole("Done muling.", sdk.colors.D2Bot.DarkGold); - sendCopyData(null, master, 10, JSON.stringify({ status: "quit" })); - D2Bot.stop(me.profile, true); - }, - - next: function () { - MuleLogger.logChar(); - delay(500); - - [makeNext, checkOnJoin] = [true, true]; - let obj = MuleData.read(); - - if (Mule.checkAnniTorch() && obj.torchChars.indexOf(me.name) === -1) { - obj.torchChars.push(me.name); - } - - obj.fullChars.push(me.name); - MuleData.write(obj); - MuleData.nextChar(); - D2Bot.printToConsole("Mule full, getting next character.", sdk.colors.D2Bot.DarkGold); - - if (Starter.Config.MinGameTime && getTickCount() - Mule.startTick < Starter.Config.MinGameTime * 1000) { - while (getTickCount() - Mule.startTick < Starter.Config.MinGameTime * 1000) { - me.overhead("Stalling for " + Math.round(((Mule.startTick + (Starter.Config.MinGameTime * 1000)) - getTickCount()) / 1000) + " Seconds"); - delay(1000); - } - } - - Mule.quit(); - }, - - quit: function () { - ["default.dbj", "tools/AntiIdle.js", "tools/AreaWatcher.js"].forEach(thread => { - let script = getScript(thread); - if (script && script.running && script.stop()) { - delay(100); - } - }); - Mule.cursorCheck(); - console.log("ÿc8Mule game duration ÿc2" + (Time.format(getTickCount() - me.gamestarttime))); - - try { - quit(); - } finally { - while (me.ingame) { - delay(100); - } - } - - return true; - }, - - /** - * @todo handle when a bot wants to mule while we are refreshing the game - */ - gameRefresh: function () { - if (!this.continuousMule) return this.quit(); - console.log("MaxGameTime Reached: "); - Mule.quit(); - - Starter.firstLogin = true; - console.log("updating runs"); - D2Bot.updateRuns(); - status = "ready"; - Starter.inGame = false; - - delay(1000); - Controls.LobbyQuit.click(); // Quit from Lobby - ControlAction.timeoutDelay("Refresh game", 330 * 1000); // 5.5 minutes - - return true; - }, - - /** - * @param {number} time - */ - ingameTimeout: function (time) { - let tick = getTickCount(); - - while (getTickCount() - tick < time) { - if (me.ingame && me.gameReady && !!me.area) break; - - // game doesn't exist, might need more locs - if (getLocation() === sdk.game.locations.GameDoesNotExist) { - break; - } - - delay(100); - } - - return (me.ingame && me.gameReady && !!me.area); - }, - - foreverAlone: function () { - let party = getParty(); - - if (party) { - do { - if (party.name !== me.name) return false; - } while (party.getNext()); - } - - return true; - }, - - checkAnniTorch: function () { - while (!me.gameReady) { - delay(500); - } - - return me.getItemsEx() - .filter(i => i.isInStorage && i.unique && [sdk.items.SmallCharm, sdk.items.LargeCharm].includes(i.classid)) - .some(i => [sdk.items.SmallCharm, sdk.items.LargeCharm].includes(i.classid)); - }, - - stashItems: function () { - me.getItemsEx() - .filter(item => item.isInInventory) - .sort((a, b) => (b.sizex * b.sizey - a.sizex * a.sizey)) - .forEach(item => { - Storage.Stash.CanFit(item) && Storage.Stash.MoveTo(item); - }); - - return true; - }, - - cursorCheck: function () { - let cursorItem = Game.getCursorUnit(); - - if (cursorItem) { - if (!Storage.Inventory.CanFit(cursorItem) || !Storage.Inventory.MoveTo(cursorItem)) { - console.warn("Can't place " + cursorItem.prettyPrint + " in inventory"); - cursorItem.drop(); - } - } - - return true; - }, - - pickItems: function () { - let waitTick = getTickCount(); - let rval = "fail"; - let list = []; - - while (!me.name || !me.gameReady) { - if (!me.ingame) return rval; - delay(100); - } - - if (!Mule.clearedJunk) { - me.getItemsEx() - .filter(item => item.isInInventory && Town.ignoreType(item.itemType) && (muleMode === 0 || item.classid !== sdk.items.ScrollofIdentify)) - .forEach(item => { - try { - item.drop(); - } catch (e) { - console.warn("Failed to drop an item."); - } - }); - Mule.clearedJunk = true; // only do this once - } - - while (me.gameReady) { - if (masterStatus.status === "done" || Mule.continuousMule || checkOnJoin) { - checkOnJoin && (checkOnJoin = false); - let item = Game.getItem(); - - if (item) { - do { - // don't pick up trash - if (item.distance < 20 && item.onGroundOrDropping && !Town.ignoreType(item.itemType)) { - list.push(copyUnit(item)); - } - } while (item.getNext()); - } - - // If and only if there is nothing left are we "done" - if (list.length === 0) { - rval = Mule.continuousMule ? "ready" : "done"; - - break; - } - - // pick large items first by sorting items by size in descending order and move gheed's charm to the end of the list - list.sort(function(a, b) { - if (a.isGheeds && !Pickit.canPick(a)) return 1; - if (b.isGheeds && !Pickit.canPick(b)) return -1; - - return (b.sizex * b.sizey - a.sizex * a.sizey); - }); - - while (list.length > 0) { - item = list.shift(); - let canFit = Storage.Inventory.CanFit(item); - - // Torch and Anni handling - if (muleMode > 0 && item.unique && [sdk.items.SmallCharm, sdk.items.LargeCharm].includes(item.classid) && !Pickit.canPick(item)) { - let msg = item.classid === sdk.items.LargeCharm ? "Mule already has a Torch." : "Mule already has a Anni."; - D2Bot.printToConsole(msg, sdk.colors.D2Bot.DarkGold); - rval = "next"; - } - - // Gheed's Fortune handling - if (item.isGheeds && !Pickit.canPick(item)) { - D2Bot.printToConsole("Mule already has Gheed's.", sdk.colors.D2Bot.DarkGold); - rval = "next"; - } - - if (!canFit && Mule.stashItems()) { - canFit = Storage.Inventory.CanFit(item); - } - - if (canFit) { - Pickit.pickItem(item); - } else { - rval = "next"; - } - } - - if (rval === "next") { - break; - } - } else { - if (!Mule.continuousMule) { - sendCopyData(null, master, 10, JSON.stringify({ status: "report" })); - } else { - if (getTickCount() - waitTick > Time.minutes(10)) { - break; - } - } - } - - delay(500); - } - - return rval; - }, -}; - -new Overrides.Override (Starter, Starter.receiveCopyData, function (orignal, mode, msg) { - if (mode === 3) return; - // master/mule communication function - switch (mode) { - case 10: // mule request - let obj = JSON.parse(msg); - - if (Mule.continuousMule && me.ingame) { - sendCopyData(null, obj.profile, 10, JSON.stringify({ status: "ready" })); - } else { - if (!master) { - let masterInfo = AutoMule.getMaster(obj); - - if (masterInfo) { - master = masterInfo.profile; - muleMode = masterInfo.mode; - } - } else { - if (obj.profile === master) { - sendCopyData(null, master, 10, JSON.stringify({ status: status })); - } else { - sendCopyData(null, obj.profile, 10, JSON.stringify({ status: "busy" })); - } - } - } - - break; - case 11: // begin item pickup - status = "begin"; - - break; - case 12: // get master's status - masterStatus = JSON.parse(msg); - - break; - default: - orignal(mode, msg); - } -}).apply(); - -new Overrides.Override (Starter, Starter.updateCount, function () { - D2Bot.updateCount(); - delay(1000); - Controls.BattleNet.click(); - - let obj = MuleData.read(); - let info = { - realm: muleObj.realm, - account: obj.account, - password: muleObj.accountPassword - }; - - MuleLogger.save(md5(info.realm.toLowerCase() + info.account.toLowerCase()), info.password); - ControlAction.loginAccount(info); - delay(1000); - Controls.CharSelectExit.click(); -}).apply(); - -function locationAction (location) { - let obj, info, string, text; - - switch (location) { - case sdk.game.locations.PreSplash: - case sdk.game.locations.SplashScreen: - ControlAction.click(); - - break; - case sdk.game.locations.Lobby: - case sdk.game.locations.LobbyChat: - D2Bot.updateStatus("Lobby"); - - if (Starter.inGame) { - print("updating runs"); - D2Bot.updateRuns(); - status = "ready"; - Starter.inGame = false; - } - - if (makeNext) { - Controls.LobbyQuit.click(); - } else { - Starter.LocationEvents.openJoinGameWindow(); - } - - break; - case sdk.game.locations.CreateGame: - D2Bot.updateStatus("Creating Game"); - - // remove level restriction - Controls.CharacterDifference.disabled === 5 && Controls.CharacterDifferenceButton.click(); - - // Max number of players - Controls.MaxPlayerCount.setText("8"); - - delay(2000); - - // FTJ handler - if (status === "pending") { - D2Bot.printToConsole("Failed to create game"); - ControlAction.timeoutDelay("FTJ delay", Starter.Config.FTJDelay * 1e3); - D2Bot.updateRuns(); - } - - createGame(muleObj.muleGameName[0], muleObj.muleGameName[1]); - if (!Mule.ingameTimeout(Time.minutes(1))) { - console.debug("Failed to get in game, current location: " + getLocation() + " inGame? " + me.ingame + " area? " + me.area); - break; - } - - status = "pending"; - - break; - case sdk.game.locations.WaitingInLine: - Starter.LocationEvents.waitingInLine(); - - break; - case sdk.game.locations.JoinGame: - D2Bot.updateStatus("Join Game"); - - if (status === "pending") { - D2Bot.printToConsole("Failed to join game"); - ControlAction.timeoutDelay("Join Delay", Starter.Config.FTJDelay * 1000); - D2Bot.updateRuns(); - } - - if (!Mule.continuousMule) { - D2Bot.requestGame(master); - delay(100); - } - - if (Starter.inGame) { - print("updating runs"); - D2Bot.updateRuns(); - status = "ready"; - Starter.inGame = false; - } - - delay(2000); - - if (Object.keys(Starter.joinInfo).length && Starter.joinInfo.gameName !== "" && Starter.joinInfo.inGame) { - joinGame(Starter.joinInfo.gameName, Starter.joinInfo.gamePass); - } else { - joinGame(muleObj.muleGameName[0], muleObj.muleGameName[1]); - } - - !Starter.firstLogin && (status = "pending"); - - if (Mule.ingameTimeout(Time.minutes(1))) { - console.debug("Ingame timeout done."); - } - - // could not join game - getLocation() === sdk.game.locations.Lobby && !me.ingame && Controls.CreateGameWindow.click(); - - break; - case sdk.game.locations.Ladder: - case sdk.game.locations.ChannelList: - break; - case sdk.game.locations.MainMenu: - case sdk.game.locations.Login: - makeNext && (makeNext = false); - - obj = MuleData.read(); - - if (!obj.account || obj.account.indexOf(muleObj.accountPrefix) < 0) { - MuleData.nextAccount(); - obj = MuleData.read(); - } - - info = { - realm: muleObj.realm, - account: obj.account, - password: muleObj.accountPassword - }; - - if (Starter.makeAccount) { - ControlAction.makeAccount(info); - D2Bot.printToConsole("Made account: " + info.account, sdk.colors.D2Bot.DarkGold); - Starter.makeAccount = false; - - break; - } - - MuleLogger.save(md5(info.realm.toLowerCase() + info.account.toLowerCase()), info.password); - !ControlAction.loginAccount(info) && (Starter.makeAccount = true); - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.CharSelect: - case sdk.game.locations.NewCharSelected: - case sdk.game.locations.CharacterCreate: - case sdk.game.locations.CharSelectNoChars: - string = ""; - text = Controls.CharSelectError.getText(); - - if (text) { - for (let i = 0; i < text.length; i++) { - string += text[i]; - - if (i !== text.length - 1) { - string += " "; - } - } - - if (string === getLocaleString(sdk.locale.text.CdKeyDisabledFromRealm)) { // CDKey disabled from realm play - D2Bot.updateStatus("Realm Disabled CDKey"); - D2Bot.printToConsole("Realm Disabled CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); - D2Bot.CDKeyDisabled(); - - if (Starter.gameInfo.switchKeys) { - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } else { - D2Bot.stop(me.profile, true); - } - } - } - - // Single Player screen fix - // TODO: see if this is still needed. d2bs doesn't load scripts twice anymore - if (getLocation() === sdk.game.locations.CharSelect && !Controls.CharSelectCurrentRealm.control) { - Controls.CharSelectExit.click(); - - break; - } - - // Can't create character, button greyed out = high likelyhood of realm down - if (getLocation() === sdk.game.locations.CharSelectNoChars && Controls.CharSelectCreate.disabled === sdk.game.controls.Disabled) { - D2Bot.updateStatus("Realm Down"); - delay(1000); - - if (!Controls.CharSelectExit.click()) { - break; - } - - Starter.updateCount(); - ControlAction.timeoutDelay("Realm Down", Starter.Config.RealmDownDelay * 6e4); - D2Bot.CDKeyRD(); - - if (Starter.gameInfo.switchKeys) { - D2Bot.printToConsole("Realm Down - Changing CD-Key"); - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } else { - D2Bot.restart(); - } - } - - obj = MuleData.read(); - maxCharCount = (muleObj.charsPerAcc > 0 ? Math.min(muleObj.charsPerAcc, 18) : 8); - - if (makeNext) { - if (obj.fullChars.length >= maxCharCount || (muleMode > 0 && obj.torchChars.length >= maxCharCount)) { - Controls.CharSelectExit.click(); - MuleData.nextAccount(); - - break; - } - - makeNext = false; - } - - if (!obj.character || obj.character.indexOf(muleObj.charPrefix) < 0) { - MuleData.nextChar(); - - obj = MuleData.read(); - } - - info = { - account: obj.account, - charName: obj.character, - ladder: muleObj.ladder, - hardcore: muleObj.hardcore, - expansion: muleObj.expansion, - charClass: "amazon" - }; - - if (muleMode > 0 && obj.torchChars.includes(info.charName)) { - MuleData.nextChar(); - - break; - } - - if (ControlAction.findCharacter(info)) { - ControlAction.loginCharacter(info, false); - } else { - // premade account that's already full - if (ControlAction.getCharacters().length >= maxCharCount) { - Controls.CharSelectExit.click(); - MuleData.nextAccount(); - - break; - } - - if (!ControlAction.makeCharacter(info)) { - // TODO: check if acc is full and cancel location 15 and 29 if true - MuleData.nextChar(); - - break; - } - - D2Bot.printToConsole("Made character: " + info.charName, sdk.colors.D2Bot.DarkGold); - } - - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - break; - case sdk.game.locations.MainMenuConnecting: - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - Starter.LocationEvents.charSelectError(); - - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GameNameExists: - Controls.JoinGameWindow.click(); - - break; - case sdk.game.locations.GatewaySelect: - Controls.GatewayCancel.click(); - - break; - case sdk.game.locations.GameDoesNotExist: - Controls.CreateGameWindow.click(); - - break; - case sdk.game.locations.OkCenteredErrorPopUp: - Controls.OkCentered.click(); - Controls.CharSelectExit.click(); - - break; - case sdk.game.locations.ServerDown: - case sdk.game.locations.GameIsFull: - break; - case sdk.game.locations.OtherMultiplayer: - // probably should implement way to use open-bnet - Controls.OtherMultiplayerCancel.click(); - - break; - case sdk.game.locations.TcpIp: - case sdk.game.locations.TcpIpEnterIp: - Controls.TcpIpCancel.click(); - - break; - default: - if (location !== undefined && location !== null) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } -} - -// item event check instead? -// eslint-disable-next-line no-unused-vars -function gameEvent (mode, param1, param2, name1, name2) { - if (!me.ingame || !me.gameReady || !me.name) { - return; - } - - switch (mode) { - case 0x00: // "%Name1(%Name2) dropped due to time out." - case 0x01: // "%Name1(%Name2) dropped due to errors." - case 0x03: // "%Name1(%Name2) left our world. Diablo's minions weaken." - if (Mule.foreverAlone()) { - console.log("Waiting"); - status = "ready"; - } - - break; - case 0x02: // "%Name1(%Name2) joined our world. Diablo's minions grow stronger." - if (name1.trim() !== me.name.trim()) { - console.log("begin"); - status = "begin"; - } - - break; - } +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// system libs +includeSystemLibs(); +include("systems/automule/Mule.js"); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +if (!FileTools.exists("data/" + me.profile + ".json") + && DataFile.create()) { + Starter.firstRun = true; } function main () { - // basics -- don't touch - addEventListener("copydata", Starter.receiveCopyData); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - D2Bot.updateRuns(); // we need the mule to swap keys somehow after all - delay(1000); - - // mule/master data initialization block - Mule.continuousMule = AutoMule.isContinousMule(); - - if (Mule.continuousMule) { - console.log("Continuous Mule Mode Started"); - muleMode = AutoMule.getMuleMode(); - muleObj = AutoMule.getMuleObject(muleMode, "", true); - muleFilename = AutoMule.getMuleFilename(muleMode, "", true); - addEventListener("gameevent", gameEvent); - } else { - // Wait for master before login = give room to determine muling mode (normal or torch) - while (!master) { - delay(100); - } - - console.log("Master found: " + master); - - muleObj = AutoMule.getMuleObject(muleMode, master); - muleFilename = AutoMule.getMuleFilename(muleMode, master); - } - - console.log("Mule filename: " + muleFilename); - - try { - // ugly solution to uglier problem - pickItem area update - !FileTools.exists("data/" + me.profile + ".json") && DataFile.create(); - - // create mule datafile if it doesn't exist - !FileTools.exists(muleFilename) && MuleData.create(); - - let obj = MuleData.read(); - obj.account && obj.account.indexOf(muleObj.accountPrefix) < 0 && MuleData.create(); - } catch (e) { - // probably should try again if fails to make file or shut down instead of continuing loop - console.warn("Caught exception creating data files."); - console.error(e); - D2Bot.printToConsole("DataFileException: " + e.message + " (" + e.fileName.substring(e.fileName.lastIndexOf("\\") + 1, e.fileName.length) + " #" + e.lineNumber + ")"); - } - - // begin - MainLoop: - while (true) { - try { - if (me.ingame && me.gameReady) { - if (!Starter.inGame) { - if (!Mule.init()) { - console.debug("Failed to init. Trying again. Ingame and ready? " + (me.ingame && me.gameReady && !!me.area)); - delay(1000); - continue; - } - } - - if (!Mule.continuousMule) { - Mule.waitForMaster(); - } - - D2Bot.updateStatus(Mule.statusString + " Status: " + status + Starter.timer(me.gamestarttime)); - - if (status === "begin") { - switch (Mule.pickItems()) { - // done picking, tell the master to leave game and kill mule profile - case "done": - Mule.done(); - - return; - // can't fit more items, get to next character or account - case "next": - Mule.next(); - - // should fix hitting gameRefresh when making a new character - continue MainLoop; - case "ready": - Mule.recheckTick = getTickCount(); - - break; - case "fail": - // Try again - break; - } - } - - if (Mule.continuousMule) { - if (Starter.Config.MaxGameTime > 0 && getTickCount() - me.gamestarttime > Time.minutes(Starter.Config.MaxGameTime) && Mule.foreverAlone()) { - Mule.gameRefresh(); - } else if (getTickCount() - Mule.recheckTick > Time.minutes(10)) { - // recheck every 10 minutes? - status = "begin"; - } - } - } else if (!me.ingame) { - delay(1000); - locationAction(getLocation()); - } - } catch (e2) { - console.warn("Caught an exception in the main loop."); - console.error(e2); - D2Bot.printToConsole("MainLoopException: " + e2.message + " (" + e2.fileName.substring(e2.fileName.lastIndexOf("\\") + 1, e2.fileName.length) + " #" + e2.lineNumber + ")"); - } - - delay(100); - } + const inGameThread = "libs/systems/automule/main.js"; + const locationAction = (function () { + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations, + parseControlText + } = require("./libs/oog/Locations"); + + locations.set(sdk.game.locations.OtherMultiplayer, + function () { + Controls.OtherMultiplayerCancel.click(); + } + ); + locations.set(sdk.game.locations.TcpIpEnterIp, + function () { + Controls.PopupNo.click(); + } + ); + locations.set(sdk.game.locations.MainMenu, + function () { + if (!Mule.obj) return; // don't do anything if we don't have a mule profile + if (Mule.obj.realm) { + ControlAction.clickRealm(ControlAction.realms[Mule.obj.realm]); + } + Controls.BattleNet.click(); + } + ); + locations.set(sdk.game.locations.Login, + function () { + if (Mule.makeNext) { + // why? + Mule.makeNext = false; + } + let obj = MuleData.read(); + + if (!obj.account || obj.account.indexOf(Mule.obj.accountPrefix) < 0) { + MuleData.nextAccount(); + obj = MuleData.read(); + } + + let info = { + realm: Mule.obj.realm, + account: obj.account, + password: Mule.obj.accountPassword + }; + + if (Starter.makeAccount) { + if (ControlAction.makeAccount(info)) { + D2Bot.printToConsole("Made account: " + info.account, sdk.colors.D2Bot.DarkGold); + Starter.makeAccount = false; + } else { + MuleData.nextAccount(); + } + + return; + } + + MuleLogger.save(md5(info.realm.toLowerCase() + info.account.toLowerCase()), info.password); + ControlAction.loginAccount(info); + } + ); + locations.set(sdk.game.locations.CreateNewAccount, + function () { + if (Starter.makeAccount) { + let obj = MuleData.read(); + let info = { + realm: Mule.obj.realm, + account: obj.account, + password: Mule.obj.accountPassword + }; + + if (ControlAction.makeAccount(info)) { + D2Bot.printToConsole("Made account: " + info.account, sdk.colors.D2Bot.DarkGold); + Starter.makeAccount = false; + } else { + MuleData.nextAccount(); + } + } + } + ); + locations.set(sdk.game.locations.CharSelectNoChars, + function () { + if (!Controls.CharSelectCurrentRealm.control) { + Controls.BottomLeftExit.click(); + } else if (Controls.CharSelectCreate.disabled === sdk.game.controls.Disabled) { + D2Bot.updateStatus("Realm Down"); + delay(1000); + + if (!Controls.BottomLeftExit.click()) { + return; + } + + Starter.updateCount(); + ControlAction.timeoutDelay("Realm Down", Starter.Config.RealmDownDelay * 6e4); + D2Bot.CDKeyRD(); + + if (Starter.gameInfo.switchKeys) { + D2Bot.printToConsole("Realm Down - Changing CD-Key"); + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.restart(); + } + } + } + ); + locations.set(sdk.game.locations.SelectDifficultySP, + function () { + hideConsole(); + sendKey(sdk.keys.Escape); + } + ); + locations.set(sdk.game.locations.GameNameExists, + function () { + Controls.JoinGameWindow.click(); + } + ); + locations.set(sdk.game.locations.GameDoesNotExist, + function () { + Controls.CreateGameWindow.click(); + } + ); + locations.set(sdk.game.locations.CreateGame, + function () { + D2Bot.updateStatus("Creating Game"); + + // remove level restriction + if (Controls.CharacterDifference.disabled === 5) { + Controls.CharacterDifferenceButton.click(); + } + // Max number of players + Controls.MaxPlayerCount.setText("8"); + + delay(2000); + + // FTJ handler + if (Mule.status === "pending") { + D2Bot.printToConsole("Failed to create game"); + ControlAction.timeoutDelay("FTJ delay", Starter.Config.FTJDelay * 1e3); + D2Bot.updateRuns(); + } + + createGame(Mule.obj.muleGameName[0], Mule.obj.muleGameName[1]); + if (!Mule.ingameTimeout(Time.minutes(1))) { + console.debug( + "Failed to get in game, current location: " + getLocation() + + " inGame? " + me.ingame + " area? " + me.area + ); + return; + } + + Mule.status = "pending"; + } + ); + locations.set(sdk.game.locations.JoinGame, + function () { + D2Bot.updateStatus("Join Game"); + + if (Mule.status === "pending") { + D2Bot.printToConsole("Failed to join game"); + ControlAction.timeoutDelay("Join Delay", Starter.Config.FTJDelay * 1000); + D2Bot.updateRuns(); + } + + if (Starter.inGame) { + console.log("updating runs"); + D2Bot.updateRuns(); + Mule.status = "ready"; + Starter.inGame = false; + } + + if (Mule.refresh + || (Mule.makeNext && !String.isEqual(me.charname, MuleData.read().character))) { + Controls.LobbyQuit.click(); // Quit from Lobby + if (Mule.makeNext) { + console.debug("Next mule: " + MuleData.read().character + " current: " + me.charname); + } + if (Mule.refresh) { + ControlAction.timeoutDelay("Refresh game", 330 * 1000); // 5.5 minutes + Mule.refresh = false; + } + return; + } + + if (!Mule.continuous) { + D2Bot.requestGame(Mule.master); + delay(100); + } + + delay(2000); + + if (Object.keys(Starter.joinInfo).length + && Starter.joinInfo.gameName !== "" + && Starter.joinInfo.inGame) { + joinGame(Starter.joinInfo.gameName, Starter.joinInfo.gamePass); + } else { + joinGame(Mule.obj.muleGameName[0], Mule.obj.muleGameName[1]); + } + + !Starter.firstLogin && (Mule.status = "pending"); + + if (Mule.ingameTimeout(Time.minutes(1))) { + console.debug("Ingame timeout done."); + } + + // could not join game + if (getLocation() === sdk.game.locations.Lobby && !me.ingame) { + Controls.CreateGameWindow.click(); + } + } + ); + addLocations([sdk.game.locations.TcpIp, sdk.game.locations.NewCharSelected], + function () { + Controls.BottomLeftExit.click(); + } + ); + addLocations([sdk.game.locations.CharSelect, sdk.game.locations.CharSelectNoChars], + function () { + let string = parseControlText(Controls.CharSelectError.control); + + if (string) { + if (string === getLocaleString(sdk.locale.text.CdKeyDisabledFromRealm)) { + D2Bot.updateStatus("Realm Disabled CDKey"); + D2Bot.printToConsole("Realm Disabled CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); + D2Bot.CDKeyDisabled(); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.stop(me.profile, true); + } + } + } + + // Single Player screen fix + // TODO: see if this is still needed. d2bs doesn't load scripts twice anymore + if (!Controls.CharSelectCurrentRealm.control) { + Controls.BottomLeftExit.click(); + + return; + } + + // Can't create character, button greyed out = high likelyhood of realm down + if (getLocation() === sdk.game.locations.CharSelectNoChars + && Controls.CharSelectCreate.disabled === sdk.game.controls.Disabled) { + D2Bot.updateStatus("Realm Down"); + delay(1000); + + if (!Controls.BottomLeftExit.click()) { + return; + } + + Starter.updateCount(); + ControlAction.timeoutDelay("Realm Down", Starter.Config.RealmDownDelay * 6e4); + D2Bot.CDKeyRD(); + + if (Starter.gameInfo.switchKeys) { + D2Bot.printToConsole("Realm Down - Changing CD-Key"); + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.restart(); + } + } + + let obj = MuleData.read(); + const maxCharCount = (Mule.obj.charsPerAcc > 0 ? Math.min(Mule.obj.charsPerAcc, 18) : 8); + + if (Mule.makeNext) { + if (obj.fullChars.length >= maxCharCount + || (Mule.mode > 0 && obj.torchChars.length >= maxCharCount)) { + Controls.BottomLeftExit.click(); + MuleData.nextAccount(); + + return; + } + + Mule.makeNext = false; + } + + if (!obj.character || obj.character.indexOf(Mule.obj.charPrefix) < 0) { + MuleData.nextChar(); + + obj = MuleData.read(); + } + + const info = { + account: obj.account, + charName: obj.character, + ladder: Mule.obj.ladder, + hardcore: Mule.obj.hardcore, + expansion: Mule.obj.expansion, + charClass: "amazon" + }; + + if (Mule.mode > 0 && obj.torchChars.includes(info.charName)) { + MuleData.nextChar(); + + return; + } + + if (ControlAction.findCharacter(info)) { + ControlAction.loginCharacter(info, false); + } else { + // premade account that's already full + if (ControlAction.getCharacters().length >= maxCharCount) { + Controls.BottomLeftExit.click(); + MuleData.nextAccount(); + + return; + } + + if (!ControlAction.makeCharacter(info)) { + // TODO: check if acc is full and cancel location 15 and 29 if true + MuleData.nextChar(); + + return; + } + + D2Bot.printToConsole("Made character: " + info.charName, sdk.colors.D2Bot.DarkGold); + } + } + ); + addLocations([sdk.game.locations.Lobby, sdk.game.locations.LobbyChat], + function () { + D2Bot.updateStatus("Lobby"); + + if (Starter.inGame) { + if (Mule.refresh) { + Controls.LobbyQuit.click(); // Quit from Lobby + ControlAction.timeoutDelay("Refresh game", 330 * 1000); // 5.5 minutes + Mule.refresh = false; + + return; + } + console.log("updating runs"); + D2Bot.updateRuns(); + Mule.status = "ready"; + Starter.inGame = false; + } + Mule.makeNext + ? Controls.LobbyQuit.click() + : Starter.LocationEvents.openJoinGameWindow(); + } + ); + + return { + /** @param {number} loc */ + run: function (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }, + }; + })(); + + const Overrides = require("./libs/modules/Override"); + new Overrides.Override (Starter, Starter.receiveCopyData, function (orignal, mode, msg) { + if (mode === 3) return; + // master/mule communication function + switch (mode) { + case 10: // mule request + if (me.ingame) return; + let obj = JSON.parse(msg); + + if (Mule.continuous && me.ingame) { + sendCopyData(null, obj.profile, 10, JSON.stringify({ status: "ready" })); + } else { + if (!Mule.master) { + let masterInfo = Mule.getMaster(obj); + + if (masterInfo) { + Mule.master = masterInfo.profile; + Mule.mode = masterInfo.mode; + } + } else { + // come back to this to allow multiple mulers + if (obj.profile === Mule.master) { + sendCopyData(null, Mule.master, 10, JSON.stringify({ status: Mule.status })); + } else { + sendCopyData(null, obj.profile, 10, JSON.stringify({ status: "busy" })); + } + } + } + + break; + case 11: // begin item pickup + case 12: // get master's status + break; + default: + orignal(mode, msg); + } + }).apply(); + + new Overrides.Override (Starter, Starter.updateCount, function () { + D2Bot.updateCount(); + delay(1000); + Controls.BattleNet.click(); + + let obj = MuleData.read(); + let info = { + realm: Mule.obj.realm, + account: obj.account, + password: Mule.obj.accountPassword + }; + + MuleLogger.save(md5(info.realm.toLowerCase() + info.account.toLowerCase()), info.password); + ControlAction.loginAccount(info); + delay(1000); + Controls.BottomLeftExit.click(); + }).apply(); + + addEventListener("copydata", Starter.receiveCopyData); + addEventListener("scriptmsg", function (msg) { + if (typeof msg === "string") { + if (msg === "mule_init") { + getScript(inGameThread).send({ + obj: Mule.obj, + mode: Mule.mode, + master: Mule.master, + fileName: MuleData.fileName, + next: Mule.next, + minGameTime: Starter.Config.MinGameTime, + maxGameTime: Starter.Config.MaxGameTime + }); + Mule.refresh = false; + Mule.next = false; + } else if (msg === "refresh") { + Mule.refresh = true; + } else if (msg === "next") { + Mule.makeNext = true; + // maybe hacky? I want to let the newly created mule know that it's a next mule instead of first join + Mule.next = true; + } + } else if (typeof msg === "object") { + if (msg.hasOwnProperty("status")) { + Mule.status = msg.status; + } + } + }); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + D2Bot.updateRuns(); // we need the mule to swap keys somehow after all + delay(1000); + + const muleObjs = Mule.getMuleInfo(); + + if (muleObjs.length === 1) { + // we can assign our info directly + Mule.obj = muleObjs[0].obj; + Mule.mode = muleObjs[0].mode; + Mule.continuous = muleObjs[0].obj.continuousMule; + // we can use any of the enabled profiles + MuleData.fileName = Mule.getMuleFilename(Mule.mode, Mule.obj.enabledProfiles[0], Mule.continuous); + } else if (muleObjs.some(muleObj => muleObj.obj.continuousMule)) { + // continuous mule doesn't wait for master profiles + // find the obj and we can assign our info directly + let _muleObj = muleObjs.find(function (muleObj) { + return muleObj.obj.continuousMule; + }); + Mule.obj = _muleObj.obj; + Mule.mode = _muleObj.mode; + Mule.continuous = _muleObj.obj.continuousMule; + // we can use any of the enabled profiles + MuleData.fileName = Mule.getMuleFilename(Mule.mode, Mule.obj.enabledProfiles[0], Mule.continuous); + } else { + // we need to wait for confirmation from the master + while (!Mule.master) { + delay(100); + } + console.log("Master found: " + Mule.master + " Mode: " + Mule.mode); + Mule.obj = muleObjs.find(function (muleObj) { + return muleObj.obj.enabledProfiles.includes(Mule.master) || muleObj.obj.enabledProfiles.includes("all"); + }).obj; + Mule.continuous = Mule.obj.continuousMule; + MuleData.fileName = Mule.getMuleFilename(Mule.mode, Mule.master, Mule.continuous); + } + + console.log("Mule filename: " + MuleData.fileName); + + try { + // create mule datafile if it doesn't exist + !FileTools.exists(MuleData.fileName) && MuleData.create(); + + let obj = MuleData.read(); + if (obj.account && obj.account.indexOf(Mule.obj.accountPrefix) < 0) { + MuleData.create(); + } + } catch (e) { + // probably should try again if fails to make file or shut down instead of continuing loop + console.warn("Caught exception creating data files."); + console.error(e); + D2Bot.printToConsole( + "DataFileException: " + e.message + + " (" + e.fileName.substring(e.fileName.lastIndexOf("\\") + 1, e.fileName.length) + + " #" + e.lineNumber + ")" + ); + } + + // begin + while (true) { + try { + while (me.ingame) { + if (me.gameReady) { + Starter.isUp = "yes"; + + if (!Starter.inGame) { + Starter.gameStart = getTickCount(); + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + Mule.statusString = ( + "In " + + (Mule.mode === 2 ? "anni " : Mule.mode === 1 ? "torch " : "") + + "mule game." + ); + D2Bot.printToConsole(Mule.statusString, sdk.colors.D2Bot.DarkGold); + + DataFile.updateStats("runs", Starter.gameCount); + DataFile.updateStats("ingameTick"); + } + D2Bot.updateStatus( + me.charname + " | Game: " + me.gamename + + " | " + Mule.statusString + " Status: " + Mule.status + + Starter.timer(me.gamestarttime) + ); + } + + delay(1000); + } + + Starter.isUp = "no"; + + locationAction.run(getLocation()); + delay(1000); + } catch (e) { + console.error(e); + D2Bot.printToConsole( + "MainLoopException: " + e.message + + " (" + e.fileName.substring(e2.fileName.lastIndexOf("\\") + 1, e.fileName.length) + + " #" + e.lineNumber + ")" + ); + } + + delay(100); + } } diff --git a/d2bs/kolbot/D2BotMuleLog.dbj b/d2bs/kolbot/D2BotMuleLog.dbj index b17c3fa9e..61d19001c 100644 --- a/d2bs/kolbot/D2BotMuleLog.dbj +++ b/d2bs/kolbot/D2BotMuleLog.dbj @@ -3,10 +3,13 @@ * @author kolton, theBGuy * @desc Entry script for Mulelogger.js * +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/mulelogger/MuleLogger")} */ -include("StarterConfig.js"); -// D2BotMuleLog specific settings - for global settings see libs/StarterConfig.js +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// D2BotMuleLog specific settings - for global settings see libs/starter/StarterConfig.js Starter.Config.MinGameTime = rand(150, 180); // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby Starter.Config.CreateGameDelay = 5; // Seconds to wait before creating a new game Starter.Config.SwitchKeyDelay = 0; // Seconds to wait before switching a used/banned key or after realm down @@ -14,349 +17,328 @@ Starter.Config.SwitchKeyDelay = 0; // Seconds to wait before switching a used/ba // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds -// No touchy! -include("json2.js"); -include("polyfill.js"); -include("OOG.js"); -include("MuleLogger.js"); -include("DropperSetup.js"); -include("common/misc.js"); -include("common/util.js"); -include("common/prototypes.js"); -let Controls = require("./modules/Control"); +// only libs we should need here as te rest of the actions are performed from default.dbj thread +include("systems/dropper/DropperSetup.js"); +include("systems/mulelogger/MuleLogger.js"); +include("systems/automule/AutoMule.js"); if (!FileTools.exists("data/" + me.profile + ".json")) { - DataFile.create(); + DataFile.create(); } -let currAcc, - usingDroper = isIncluded("DropperSetup.js"), - charList = [], - accounts = [], - chars = []; - -function parseInfo() { - usingDroper && parseDropperAccounts(accounts, chars); - - for (let i in MuleLogger.LogAccounts) { - if (MuleLogger.LogAccounts.hasOwnProperty(i) && typeof i === "string") { - accounts.push(i); - chars.push(MuleLogger.LogAccounts[i]); - } - } -} - -function locationAction (location) { - let i, currChar, - obj = {}; - - switch (location) { - case sdk.game.locations.PreSplash: - ControlAction.click(); - - break; - case sdk.game.locations.Lobby: - case sdk.game.locations.LobbyChat: - D2Bot.updateStatus("Lobby"); - - if (Starter.inGame) { - if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { - ControlAction.timeoutDelay("Min game time wait", Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount()); - } - - print("updating runs"); - D2Bot.updateRuns(); - delay(1000); - - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - Controls.LobbyQuit.click(); - - break; - } - - Starter.LocationEvents.openCreateGameWindow(); - - break; - case sdk.game.locations.WaitingInLine: - Starter.LocationEvents.waitingInLine(); - - break; - case sdk.game.locations.CreateGame: - D2Bot.updateStatus("Creating Game"); - - // remove level restriction - Controls.CharacterDifference.disabled === 5 && Controls.CharacterDifferenceButton.click(); - - // Max number of players - Controls.MaxPlayerCount.setText("8"); - - if (Starter.gameCount >= 99) { - Starter.gameCount = 1; - - DataFile.updateStats("runs", Starter.gameCount); - } - - if (Starter.lastGameStatus === "pending") { - D2Bot.printToConsole("Failed to create game"); - - Starter.gameCount += 1; - } - - ControlAction.timeoutDelay("Make Game Delay", Starter.Config.CreateGameDelay * 1e3); - createGame(MuleLogger.LogGame[0] + Starter.gameCount, MuleLogger.LogGame[1], 0); - Starter.locationTimeout(5000, location); - Starter.lastGameStatus = "pending"; - - break; - case sdk.game.locations.JoinGame: - case sdk.game.locations.Ladder: - case sdk.game.locations.ChannelList: - Starter.LocationEvents.openCreateGameWindow(); - - break; - case sdk.game.locations.MainMenu: - case sdk.game.locations.Login: - case sdk.game.locations.SplashScreen: - if (!accounts.length) { - MuleLogger.remove(); - D2Bot.printToConsole("Done logging mules!"); - D2Bot.stop(); - - break; - } - - if (FileTools.exists("logs/MuleLog.json")) { - obj = JSON.parse(FileTools.readText("logs/MuleLog.json")); - - if (obj.currAcc) { - for (i = 0; i < accounts.length; i += 1) { - if (accounts[i].split("/")[0] === obj.currAcc) { - accounts.splice(0, i); - chars.splice(0, i); - i -= 1; - - break; - } - } - } - } - - currAcc = accounts[0]; - currAcc = currAcc.split("/"); - charList = chars[0]; - obj.currAcc = currAcc[0]; - - print("ÿc4Mule Loggerÿc2: Login account: " + currAcc[0]); - MuleLogger.save(md5(currAcc[2].toLowerCase() + currAcc[0].toLowerCase()), currAcc[1]); - - if (ControlAction.loginAccount({account: currAcc[0], password: currAcc[1], realm: currAcc[2]})) { - accounts.shift(); // remove current account from the list - } - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.CharSelect: - // Single Player screen fix - if (getLocation() === sdk.game.locations.CharSelect && !Controls.CharSelectCurrentRealm.control && Controls.CharSelectExit.click()) { - break; - } - - if (!charList.length && Controls.CharSelectExit.click()) { - break; - } - - charList[0] === "all" && (charList = ControlAction.getCharacters()); - - if (FileTools.exists("logs/MuleLog.json")) { - obj = JSON.parse(FileTools.readText("logs/MuleLog.json")); - - if (obj.currChar) { - for (i = 0; i < charList.length; i += 1) { - if (charList[i] === obj.currChar) { - // Remove the previous currChar as well - charList.splice(0, i + 1); - - break; - } - } - } - } - - // last char in acc = trigger next acc - if (!charList.length) { - print("No more characters"); - accounts.shift(); // remove current account from the list - chars.shift(); - - break; - } - - currChar = charList.shift(); - obj.currChar = currChar; - - print("ÿc4Mule Loggerÿc2: Login character: " + currChar); - FileTools.writeText("logs/MuleLog.json", JSON.stringify(obj)); - - if (MuleLogger.AutoPerm) { - let characterStatus = { - charname: currChar, - perm: ControlAction.getPermStatus({charName: currChar}) - }; - MuleLogger.savePermedStatus(characterStatus); - } - - ControlAction.loginCharacter({charName: currChar}); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.NewCharSelected: - Controls.CharSelectExit.click(); - - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - Starter.LocationEvents.selectDifficultySP(); - - break; - case sdk.game.locations.MainMenuConnecting: - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - case sdk.game.locations.CharSelectNoChars: - if (!Starter.LocationEvents.charSelectError()) { - accounts.shift(); // remove current account from the list - chars.shift(); - } - - break; - case sdk.game.locations.ServerDown: - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GameNameExists: - case sdk.game.locations.GameDoesNotExist: - Controls.CreateGameWindow.click(); - - break; - case sdk.game.locations.GatewaySelect: - Controls.GatewayCancel.click(); - - break; - case sdk.game.locations.GameIsFull: - D2Bot.printToConsole("Game is full"); - Starter.lastGameStatus = "ready"; - delay(500); - Controls.JoinGameWindow.click(); - - break; - case sdk.game.locations.OtherMultiplayer: - // probably should implement way to use open bnet - Controls.OtherMultiplayerCancel.click(); - - break; - case sdk.game.locations.TcpIp: - case sdk.game.locations.TcpIpEnterIp: - Controls.TcpIpCancel.click(); - - break; - default: - if (location !== undefined) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } -} - -function main() { - addEventListener("copydata", Starter.receiveCopyData); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - delay(500); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - if (Starter.gameInfo.rdBlocker) { - D2Bot.printToConsole("You must disable RD Blocker for Mule Logger to work properly. Stopping."); - D2Bot.stop(); - - return; - } - - parseInfo(); - - if (Starter.gameInfo.error) { - if (!!DataFile.getStats().debugInfo) { - Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; - - D2Bot.printToConsole("Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, sdk.colors.D2Bot.Gray); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } - - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); - - while (true) { - // returns true before actually in game so we can't only use this check - while (me.ingame) { - // returns false when switching acts so we can't use while - if (me.gameReady) { - if (!Starter.inGame) { - print("Updating Status"); - Starter.lastGameStatus = "ingame"; - Starter.inGame = true; - Starter.gameStart = getTickCount(); - DataFile.updateStats("runs", Starter.gameCount); - } - - D2Bot.updateStatus("Game: " + me.gamename + Starter.timer(Starter.gameStart)); - } - - delay(1000); - } - - locationAction(getLocation()); - delay(1000); - } +const usingDropper = isIncluded("systems/dropper/DropperSetup.js"); +const accounts = []; +const chars = []; +const parseInfo = function () { + usingDropper && parseDropperAccounts(accounts, chars); + + for (let i in MuleLogger.LogAccounts) { + if (MuleLogger.LogAccounts.hasOwnProperty(i) && typeof i === "string") { + accounts.push(i); + chars.push(MuleLogger.LogAccounts[i]); + } + } +}; +const locationAction = (function () { + let currAcc; + /** @type {string[]} */ + let charList = []; + /** @type {{ currAcc: string, currChar: string }} */ + let obj = {}; + let ftjRetry = 0; + + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations + } = require("./libs/oog/Locations"); + + addLocations([sdk.game.locations.Lobby, sdk.game.locations.LobbyChat], + function () { + D2Bot.updateStatus("Lobby"); + + if (Starter.inGame) { + if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { + ControlAction.timeoutDelay( + "Min game time wait", + Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount() + ); + } + + console.log("updating runs"); + D2Bot.updateRuns(); + delay(1000); + + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + Controls.LobbyQuit.click(); + + return; + } + + Starter.LocationEvents.openCreateGameWindow(); + } + ); + locations.set(sdk.game.locations.CreateGame, + function (location) { + D2Bot.updateStatus("Creating Game"); + + // remove level restriction + if (Controls.CharacterDifference.disabled === 5) { + Controls.CharacterDifferenceButton.click(); + } + + // Max number of players + Controls.MaxPlayerCount.setText("8"); + + if (Starter.gameCount >= 99) { + Starter.gameCount = 1; + + DataFile.updateStats("runs", Starter.gameCount); + } + + if (Starter.lastGameStatus === "pending") { + Starter.gameCount += 1; + ftjRetry++; + D2Bot.printToConsole("Failed to create game"); + ControlAction.timeoutDelay("FTJ delay", Time.minutes(ftjRetry)); + if (ftjRetry > 5) { + ftjRetry = 0; + D2Bot.printToConsole("FTJ limit reached, failed to log: " + me.name); + Controls.LobbyQuit.click(); + + return; + } + } + + ControlAction.timeoutDelay("Make Game Delay", Starter.Config.CreateGameDelay * 1e3); + createGame(MuleLogger.LogGame[0] + Starter.gameCount, MuleLogger.LogGame[1], 0); + Starter.locationTimeout(5000, location); + Starter.lastGameStatus = "pending"; + } + ); + addLocations( + [ + sdk.game.locations.JoinGame, + sdk.game.locations.Ladder, + sdk.game.locations.ChannelList, + ], + function () { + Starter.LocationEvents.openCreateGameWindow(); + } + ); + addLocations( + [ + sdk.game.locations.MainMenu, + sdk.game.locations.Login, + sdk.game.locations.SplashScreen, + ], + function () { + if (!accounts.length) { + MuleLogger.remove(); + D2Bot.printToConsole("Done logging mules!"); + D2Bot.stop(me.profile, true); + + return; + } + + if (FileTools.exists("logs/MuleLog.json")) { + /** @type {{ currAcc: string, currChar: string }} */ + obj = JSON.parse(FileTools.readText("logs/MuleLog.json")); + + if (obj.currAcc) { + for (let i = 0; i < accounts.length; i += 1) { + if (accounts[i].split("/")[0] === obj.currAcc) { + accounts.splice(0, i); + chars.splice(0, i); + i -= 1; + + break; + } + } + } + } + + currAcc = accounts[0]; + currAcc = currAcc.split("/"); + charList = chars[0]; + obj.currAcc = currAcc[0]; + + console.log("ÿc4Mule Loggerÿc2: Login account: " + currAcc[0]); + MuleLogger.save(md5(currAcc[2].toLowerCase() + currAcc[0].toLowerCase()), currAcc[1]); + + if (ControlAction.loginAccount({ account: currAcc[0], password: currAcc[1], realm: currAcc[2] })) { + FileTools.writeText("logs/MuleLog.json", JSON.stringify(obj)); + accounts.shift(); // remove current account from the list + } + } + ); + locations.set(sdk.game.locations.CharSelect, + function () { + // Single Player screen fix + if (getLocation() === sdk.game.locations.CharSelect + && !Controls.CharSelectCurrentRealm.control + && Controls.BottomLeftExit.click()) { + return; + } + + if (!charList.length && Controls.BottomLeftExit.click()) { + return; + } + + charList[0] === "all" && (charList = ControlAction.getCharacters()); + charList[0] === "first" && (charList = ControlAction.getCharacters().slice(0, 1)); + + if (FileTools.exists("logs/MuleLog.json")) { + /** @type {{ currAcc: string, currChar: string }} */ + obj = JSON.parse(FileTools.readText("logs/MuleLog.json")); + + if (obj.currChar) { + for (let i = 0; i < charList.length; i += 1) { + if (charList[i] === obj.currChar) { + // Remove the previous currChar as well + charList.splice(0, i + 1); + + break; + } + } + } + } + + // last char in acc = trigger next acc + if (!charList.length) { + console.log("No more characters"); + accounts.shift(); // remove current account from the list + chars.shift(); + + return; + } + + let currChar = charList.shift(); + obj.currChar = currChar; + + console.log("ÿc4Mule Loggerÿc2: Login character: " + currChar); + FileTools.writeText("logs/MuleLog.json", JSON.stringify(obj)); + + if (MuleLogger.AutoPerm) { + let characterStatus = { + charname: currChar, + perm: ControlAction.getPermStatus({ charName: currChar }) + }; + MuleLogger.savePermedStatus(characterStatus); + } + + ControlAction.loginCharacter({ charName: currChar }); + } + ); + addLocations([sdk.game.locations.CharSelectConnecting, sdk.game.locations.CharSelectNoChars], + function () { + if (!Starter.LocationEvents.charSelectError()) { + accounts.shift(); // remove current account from the list + chars.shift(); + } + } + ); + locations.set(sdk.game.locations.GameIsFull, + function () { + D2Bot.printToConsole("Game is full"); + Starter.lastGameStatus = "ready"; + delay(500); + Controls.JoinGameWindow.click(); + } + ); + locations.set(sdk.game.locations.OtherMultiplayer, + function () { + Controls.OtherMultiplayerCancel.click(); + } + ); + locations.set(sdk.game.locations.TcpIp, + function () { + Controls.TcpIpCancel.click(); + } + ); + + return { + /** @param {number} loc */ + run: function (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }, + }; +})(); + +function main () { + addEventListener("copydata", Starter.receiveCopyData); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + delay(500); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + if (Starter.gameInfo.rdBlocker) { + D2Bot.printToConsole("You must disable RD Blocker for Mule Logger to work properly. Stopping."); + D2Bot.stop(me.profile, true); + + return; + } + + parseInfo(); + + if (Starter.gameInfo.error) { + if (DataFile.getStats().debugInfo) { + Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; + + D2Bot.printToConsole( + "Crash Info: Script: " + JSON.parse(Starter.gameInfo.crashInfo).currScript + + " Area: " + JSON.parse(Starter.gameInfo.crashInfo).area, + sdk.colors.D2Bot.Gray + ); + } + + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } + + DataFile.updateStats("debugInfo", JSON.stringify({ currScript: "none", area: "out of game" })); + + while (true) { + // returns true before actually in game so we can't only use this check + while (me.ingame) { + // returns false when switching acts so we can't use while + if (me.gameReady) { + if (!Starter.inGame) { + console.log("Updating Status"); + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + Starter.gameStart = getTickCount(); + DataFile.updateStats("runs", Starter.gameCount); + } + + D2Bot.updateStatus("Game: " + me.gamename + Starter.timer(Starter.gameStart)); + } + + delay(1000); + } + + locationAction.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotPubJoin.dbj b/d2bs/kolbot/D2BotPubJoin.dbj index b5546ff98..7a89f5091 100644 --- a/d2bs/kolbot/D2BotPubJoin.dbj +++ b/d2bs/kolbot/D2BotPubJoin.dbj @@ -3,442 +3,400 @@ * @author kolton, theBGuy * @desc Entry script for following public games * +* @typedef {import("./sdk/globals")} */ -include("StarterConfig.js"); -// D2BotPubJoin specific settings - for global settings see libs/StarterConfig.js +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// D2BotPubJoin specific settings - for global settings see libs/starter/StarterConfig.js Starter.Config.MinGameTime = 0; // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby Starter.Config.ResetCount = 0; // Reset game count back to 1 every X games. Starter.Config.JoinDelay = 10; // Seconds to wait between join attempts Starter.Config.AttemptNextGame = true; // after joining a game, attempt incrementing game count and joining next game rather than looking for it in game list Starter.Config.AttemptNextGameRetrys = 5; +Starter.Config.MinPlayers = 1; // Minimum players in game to join +const { + includeFilter, + excludeFilter +} = require("./libs/systems/pubjoin/PubJoinConfig"); // Override default values for StarterConfig under here by following format // Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds -/** - IncludeFilter config: - - Multiple entries in the same array mean AND: - ["baal", "-"] = game has to contain "baal" and "-" - - Different entries mean OR: - ["baal"], - ["diablo"] - will join games with either "baal" or "diablo" in their name - - Similar rules apply to ExcludeFilter: - - ["somebaal", "-"] ignores games with "somebaal" and "-" in the game name - - ["somebaal"], - ["somediablo"] - this will ignore all games with "somebaal" or "somediablo" in their names -*/ - -const IncludeFilter = [ - [""] -]; - -const ExcludeFilter = [ - [""] -]; - // ############################################################################### -function includeCheck (game) { - // No filters - if (!IncludeFilter.length) return true; - - for (let i = 0; i < IncludeFilter.length; i += 1) { - let j; - for (j = 0; j < IncludeFilter[i].length; j += 1) { - // Break the inner loop if an element didn't match or if an element is invalid - if (!IncludeFilter[i][j] || !game.match(IncludeFilter[i][j], "gi")) { - break; - } - } - - // All elements matched - if (j === IncludeFilter[i].length) { - return true; - } - } - - return false; -} +// the only things we really need from these are their oog checks +includeSystemLibs(); -function excludeCheck (game) { - // No filters - if (!ExcludeFilter.length) return true; - - for (let i = 0; i < ExcludeFilter.length; i += 1) { - let j; - for (j = 0; j < ExcludeFilter[i].length; j += 1) { - // Break the inner loop if an element didn't match or if an element is invalid - if (!ExcludeFilter[i][j] || !game.match(ExcludeFilter[i][j], "gi")) { - break; - } - } - - // All elements matched - if (j === ExcludeFilter[i].length) { - return false; - } - } - - return true; -} +const Overrides = require("./modules/Override"); -// No touchy! -include("json2.js"); -include("polyfill.js"); -include("OOG.js"); -include("automule.js"); -include("gambling.js"); -include("craftingsystem.js"); -include("torchsystem.js"); -include("common/misc.js"); -include("common/util.js"); -let sdk = require("./modules/sdk"); -let Controls = require("./modules/Control"); -let Overrides = require("./modules/Override"); - -if (typeof AdvancedConfig[me.profile] === "object") { - Object.assign(Starter.Config, AdvancedConfig[me.profile]); +if (typeof Starter.AdvancedConfig[me.profile] === "object") { + Object.assign(Starter.Config, Starter.AdvancedConfig[me.profile]); } +delete Starter.AdvancedConfig; new Overrides.Override(Starter, Starter.setNextGame, function (orignal, gameName) { - function incrementString (text) { - return text.replace(/(\d*)$/, (_, t) => (+t + 1).toString().padStart(t.length, 0)); - } + function incrementString (text) { + return text.replace(/(\d*)$/, (_, t) => (+t + 1).toString().padStart(t.length, 0)); + } - let nextGame = (gameName || this.randomString(null, true)); - nextGame = incrementString(nextGame); + let nextGame = (gameName || Starter.randomString(null, true)); + nextGame = incrementString(nextGame); - DataFile.updateStats("nextGame", nextGame); + DataFile.updateStats("nextGame", nextGame); }).apply(); if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; + Starter.firstRun = true; } let lastGameTick, retry = 0; let retryTick = 0; -function locationAction (location) { - let gameToJoin, doneGames, gameList; - - switch (location) { - case sdk.game.locations.PreSplash: - ControlAction.click(); - - break; - case sdk.game.locations.Lobby: - D2Bot.updateStatus("Lobby"); - - me.blockKeys = false; - Starter.loginRetry = 0; - !Starter.firstLogin && (Starter.firstLogin = true); - Starter.lastGameStatus === "pending" && (Starter.gameCount += 1); - Starter.loginFail = 0; - - if (Starter.Config.PingQuitDelay && Starter.pingQuit) { - ControlAction.timeoutDelay("Ping Delay", Starter.Config.PingQuitDelay * 1e3); - - Starter.pingQuit = false; - } - - if (Starter.inGame || Starter.gameInfo.error) { - !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick); - - if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { - ControlAction.timeoutDelay("Min game time wait", Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount()); - } - } - - if (Starter.inGame) { - if (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck()) { - break; - } - - print("updating runs"); - D2Bot.updateRuns(); - - lastGameTick = getTickCount(); - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - Starter.inGame = false; - - if (Starter.Config.ResetCount && Starter.gameCount >= Starter.Config.ResetCount) { - Starter.gameCount = 1; - DataFile.updateStats("runs", Starter.gameCount); - } - } - - Starter.LocationEvents.openJoinGameWindow(); - - break; - case sdk.game.locations.WaitingInLine: - Controls.CancelCreateGame.click(); - Controls.JoinGameWindow.click(); - - break; - case sdk.game.locations.LobbyChat: - case sdk.game.locations.CreateGame: - case sdk.game.locations.Ladder: - case sdk.game.locations.ChannelList: - Starter.LocationEvents.openJoinGameWindow(); - - break; - case sdk.game.locations.JoinGame: - // Don't join immediately after previous game to avoid FTJ - if (getTickCount() - lastGameTick < 5000) { - ControlAction.timeoutDelay("Game Delay", (lastGameTick - getTickCount() + 5000)); - } - - if (Starter.Config.AttemptNextGame && retry < Starter.Config.AttemptNextGameRetrys) { - let ng = DataFile.getStats().nextGame; - - if (ng && (retry === 0 || (getTickCount() - retryTick > Starter.Config.JoinDelay * 1e3))) { - gameToJoin = ng; - console.debug(gameToJoin); - - me.blockMouse = true; - - try { - joinGame(gameToJoin, ""); - } catch (joinErr) { - print(joinErr); - } - - retry++; - retryTick = getTickCount(); - me.blockMouse = false; - - Starter.locationTimeout(5000, location); - - if (getLocation() === sdk.game.locations.GameDoesNotExist) { - Starter.LocationEvents.openJoinGameWindow(); - } - } - } - - for (let i = 0; i < 5; i += 1) { - gameList = ControlAction.getGameList(); - - if (gameList && gameList.length > 0) { - break; - } - - delay(1000); - } - - console.debug(gameList); - - if (gameList) { - doneGames = []; - gameToJoin = false; - FileTools.exists("logs/doneGames.json") && (doneGames = JSON.parse(Misc.fileAction("logs/doneGames.json", 0))); - - gameList.sort(function (a, b) { - return b.players - a.players; - }); - - for (let i = 0; i < gameList.length; i += 1) { - if (doneGames.indexOf(gameList[i].gameName) === -1 && includeCheck(gameList[i].gameName) && excludeCheck(gameList[i].gameName)) { - console.log("ÿc7Game: " + gameList[i].gameName + ", Players: " + gameList[i].players); - gameToJoin = gameList[i].gameName; - - break; - } - } - - if (gameToJoin) { - doneGames.length >= 20 && doneGames.shift(); - doneGames.push(gameToJoin); - Misc.fileAction("logs/doneGames.json", 1, JSON.stringify(doneGames)); - - me.blockMouse = true; - - try { - joinGame(gameToJoin, ""); - } catch (joinErr) { - print(joinErr); - } - - me.blockMouse = false; - - Starter.locationTimeout(5000, location); - } - } - - break; - case sdk.game.locations.MainMenu: - case sdk.game.locations.SplashScreen: - case sdk.game.locations.Login: - case sdk.game.locations.CharSelect: - Starter.LocationEvents.login(); - - break; - case sdk.game.locations.LoginError: - case sdk.game.locations.InvalidCdKey: - case sdk.game.locations.CdKeyInUse: - Starter.LocationEvents.loginError(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.RealmDown: - Starter.LocationEvents.realmDown(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.LobbyLostConnection: - D2Bot.updateStatus("Disconnected/LostConnection"); - delay(1000); - Controls.OkCentered.click(); - - break; - case sdk.game.locations.CharSelectPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.SelectDifficultySP: - break; - case sdk.game.locations.MainMenuConnecting: - !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location) && Controls.LoginCancelWait.click(); - - break; - case sdk.game.locations.CharSelectConnecting: - case sdk.game.locations.CharSelectNoChars: - Starter.LocationEvents.charSelectError(); - - break; - case sdk.game.locations.ServerDown: - break; - case sdk.game.locations.LobbyPleaseWait: - !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location) && Controls.OkCentered.click(); - - break; - case sdk.game.locations.GameNameExists: - case sdk.game.locations.GameIsFull: - Controls.CreateGameWindow.click(); - Starter.gameCount += 1; - Starter.lastGameStatus = "ready"; - - break; - case sdk.game.locations.GatewaySelect: - Controls.GatewayCancel.click(); - - break; - case sdk.game.locations.GameDoesNotExist: - Starter.LocationEvents.gameDoesNotExist(); - - break; - case sdk.game.locations.CharacterCreate: - Controls.CharSelectExit.click(); - - break; - case sdk.game.locations.OtherMultiplayer: - Starter.LocationEvents.otherMultiplayerSelect(); - - break; - case sdk.game.locations.TcpIp: - Profile().type === sdk.game.profiletype.TcpIpHost ? Controls.TcpIpHost.click() : Controls.TcpIpCancel.click(); - - break; - case sdk.game.locations.TcpIpEnterIp: - Controls.TcpIpCancel.click(); - - break; - default: - if (location !== undefined) { - D2Bot.printToConsole("Unhandled location " + location); - delay(500); - D2Bot.restart(); - } - - break; - } -} - -function main() { - debugLog(me.profile); - addEventListener("copydata", Starter.receiveCopyData); - addEventListener("scriptmsg", Starter.scriptMsgEvent); - - while (!Starter.handle) { - delay(100); - } - - DataFile.updateStats("handle", Starter.handle); - delay(500); - D2Bot.init(); - load("tools/heartbeat.js"); - - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } - - Starter.gameCount = (DataFile.getStats().runs + 1 || 1); - DataFile.updateStats("nextGame", ""); - - if (Starter.gameInfo.error) { - D2Bot.retrieve(); - delay(200); - - if (Starter.gameInfo.crashInfo) { - D2Bot.printToConsole("Crash Info: Script: " + Starter.gameInfo.crashInfo.currScript + " Area: " + Starter.gameInfo.crashInfo.area, sdk.colors.D2Bot.Gray); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } - - D2Bot.store(JSON.stringify({currScript: "none", area: "out of game"})); - - while (!Object.keys(Starter.profileInfo).length) { - D2Bot.getProfile(); - print("Getting Profile"); - delay(500); - } - - while (true) { - // returns true before actually in game so we can't only use this check - while (me.ingame) { - // returns false when switching acts so we can't use while - if (me.gameReady) { - Starter.isUp = "yes"; - - if (!Starter.inGame) { - Starter.gameStart = getTickCount(); - - print("Updating Status"); - - Starter.lastGameStatus = "ingame"; - Starter.inGame = true; - retry = 0; - retryTick = 0; - - DataFile.updateStats("runs", Starter.gameCount); - DataFile.updateStats("ingameTick"); - Starter.setNextGame(me.gamename); - } - - D2Bot.updateStatus(Starter.profileInfo.charName + " | Game: " + (me.gamename || "singleplayer") + Starter.timer(Starter.gameStart)); - } - - delay(1000); - } - - Starter.isUp = "no"; - - locationAction(getLocation()); - delay(1000); - } +const locationAction = (function () { + let gameToJoin, doneGames, gameList; + + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations + } = require("./libs/oog/Locations"); + + /** @param {string} game */ + const includeCheck = function (game) { + // No filters + if (!includeFilter.length) return true; + + for (let filterSet of includeFilter) { + let conditionsMatched = true; + + for (let condition of filterSet) { + // Break the inner loop if an element didn't match or if an element is invalid + if (!condition || !game.match(condition, "gi")) { + conditionsMatched = false; + break; + } + } + + // All elements matched + if (conditionsMatched) { + return true; + } + } + + return false; + }; + + /** @param {string} game */ + const excludeCheck = function (game) { + // No filters + if (!excludeFilter.length) return true; + + for (let filterSet of excludeFilter) { + let conditionsMatched = true; + + for (let condition of filterSet) { + // Break the inner loop if an element didn't match or if an element is invalid + if (!condition || !game.match(condition, "gi")) { + conditionsMatched = false; + break; + } + } + + // All elements matched + if (conditionsMatched) { + return false; + } + } + + return true; + }; + + locations.set(sdk.game.locations.Lobby, + function () { + D2Bot.updateStatus("Lobby"); + + me.blockKeys = false; + Starter.loginRetry = 0; + !Starter.firstLogin && (Starter.firstLogin = true); + Starter.lastGameStatus === "pending" && (Starter.gameCount += 1); + Starter.loginFail = 0; + + if (Starter.Config.PingQuitDelay && Starter.pingQuit) { + ControlAction.timeoutDelay("Ping Delay", Starter.Config.PingQuitDelay * 1e3); + + Starter.pingQuit = false; + } + + if (Starter.inGame || Starter.gameInfo.error) { + !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick); + + if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { + ControlAction.timeoutDelay( + "Min game time wait", + Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount() + ); + } + } + + if (Starter.inGame) { + if (AutoMule.outOfGameCheck() + || TorchSystem.outOfGameCheck() + || Gambling.outOfGameCheck() + || CraftingSystem.outOfGameCheck()) { + return; + } + + console.log("updating runs"); + D2Bot.updateRuns(); + + lastGameTick = getTickCount(); + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + + if (Starter.Config.ResetCount && Starter.gameCount >= Starter.Config.ResetCount) { + Starter.gameCount = 1; + DataFile.updateStats("runs", Starter.gameCount); + } + } + + Starter.LocationEvents.openJoinGameWindow(); + } + ); + addLocations([sdk.game.locations.WaitingInLine, sdk.game.locations.CreateGame], + function () { + Controls.CancelCreateGame.click(); + Controls.JoinGameWindow.click(); + } + ); + addLocations( + [ + sdk.game.locations.LobbyChat, sdk.game.locations.CreateGame, + sdk.game.locations.Ladder, sdk.game.locations.ChannelList + ], + function () { + Starter.LocationEvents.openJoinGameWindow(); + } + ); + locations.set(sdk.game.locations.JoinGame, + function (location) { + // Don't join immediately after previous game to avoid FTJ + if (getTickCount() - lastGameTick < 5000) { + ControlAction.timeoutDelay("Game Delay", (lastGameTick - getTickCount() + 5000)); + } + + if (Starter.Config.AttemptNextGame && retry < Starter.Config.AttemptNextGameRetrys) { + let ng = DataFile.getStats().nextGame; + + if (ng && (retry === 0 || (getTickCount() - retryTick > Starter.Config.JoinDelay * 1e3))) { + gameToJoin = ng; + console.debug(gameToJoin); + + me.blockMouse = true; + + try { + joinGame(gameToJoin, ""); + } catch (joinErr) { + console.log(joinErr); + } + + retry++; + retryTick = getTickCount(); + me.blockMouse = false; + + Starter.locationTimeout(5000, location); + + if (getLocation() === sdk.game.locations.GameDoesNotExist) { + Starter.LocationEvents.openJoinGameWindow(); + } else { + return; + } + } + } + + for (let i = 0; i < 5; i += 1) { + gameList = ControlAction.getGameList(); + + if (gameList && gameList.length > 0) { + break; + } + + delay(1000); + } + + if (gameList) { + doneGames = []; + gameToJoin = false; + if (FileTools.exists("logs/doneGames.json")) { + doneGames = JSON.parse(FileAction.read("logs/doneGames.json")); + } + + gameList + .sort(function (a, b) { + return b.players - a.players; + }); + + for (let { gameName, players } of gameList) { + if (players < Starter.Config.MinPlayers) continue; + if (doneGames.indexOf(gameName) === -1 + && includeCheck(gameName) + && excludeCheck(gameName)) { + console.log("ÿc7Game: " + gameName + ", Players: " + players); + gameToJoin = gameName; + + break; + } + } + + if (gameToJoin) { + doneGames.length >= 20 && doneGames.shift(); + doneGames.push(gameToJoin); + FileAction.write("logs/doneGames.json", JSON.stringify(doneGames)); + + me.blockMouse = true; + + try { + joinGame(gameToJoin, ""); + } catch (joinErr) { + console.log(joinErr); + } + + me.blockMouse = false; + + Starter.locationTimeout(5000, location); + } + } + } + ); + locations.set(sdk.game.locations.SelectDifficultySP, + function () { + hideConsole(); + sendKey(sdk.keys.Escape); + } + ); + addLocations([sdk.game.locations.GameNameExists, sdk.game.locations.GameIsFull], + function () { + Controls.CreateGameWindow.click(); + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + } + ); + locations.set(sdk.game.locations.TcpIp, + function () { + Controls.TcpIpCancel.click(); + } + ); + + return { + /** @param {number} loc */ + run: function (loc) { + try { + let func = locations.get(loc); + if (typeof func === "function") { + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }, + }; +})(); + +function main () { + debugLog(me.profile); + addEventListener("copydata", Starter.receiveCopyData); + addEventListener("scriptmsg", Starter.scriptMsgEvent); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + delay(500); + D2Bot.init(); + load("threads/heartbeat.js"); + + while (!Object.keys(Starter.gameInfo).length) { + D2Bot.requestGameInfo(); + delay(500); + } + + Starter.gameCount = (DataFile.getStats().runs + 1 || 1); + DataFile.updateStats("nextGame", ""); + + if (Starter.gameInfo.error) { + D2Bot.retrieve(); + delay(200); + + if (Starter.gameInfo.crashInfo) { + D2Bot.printToConsole( + "Crash Info: Script: " + Starter.gameInfo.crashInfo.currScript + + " Area: " + Starter.gameInfo.crashInfo.area, + sdk.colors.D2Bot.Gray + ); + } + + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } + + D2Bot.store(JSON.stringify({ currScript: "none", area: "out of game" })); + + while (!Object.keys(Starter.profileInfo).length) { + D2Bot.getProfile(); + console.log("Getting Profile"); + delay(500); + } + + while (true) { + // returns true before actually in game so we can't only use this check + while (me.ingame) { + // returns false when switching acts so we can't use while + if (me.gameReady) { + Starter.isUp = "yes"; + + if (!Starter.inGame) { + Starter.gameStart = getTickCount(); + + console.log("Updating Status"); + + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + retry = 0; + retryTick = 0; + + DataFile.updateStats("runs", Starter.gameCount); + DataFile.updateStats("ingameTick"); + Starter.setNextGame(me.gamename); + + /** @type {string[]} */ + let doneGames = []; + if (FileTools.exists("logs/doneGames.json")) { + doneGames = JSON.parse(FileAction.read("logs/doneGames.json")); + } + + doneGames.length >= 20 && doneGames.shift(); + if (!doneGames.includes(me.gamename)) { + doneGames.push(me.gamename); + } + FileAction.write("logs/doneGames.json", JSON.stringify(doneGames)); + } + + D2Bot.updateStatus( + me.charname + " (" + me.charlvl + ") | Game: " + (me.gamename || "singleplayer") + + Starter.timer(Starter.gameStart) + ); + } + + delay(1000); + } + + Starter.isUp = "no"; + + locationAction.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/default.dbj b/d2bs/kolbot/default.dbj index bcaad5ad6..ba977987a 100644 --- a/d2bs/kolbot/default.dbj +++ b/d2bs/kolbot/default.dbj @@ -1,270 +1,312 @@ /** * @filename default.dbj -* @author kolton +* @author kolton, theBGuy * @desc gets executed upon gamejoin, main thread for bot * +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/mulelogger/MuleLogger")} +* @typedef {import("./libs/systems/gameaction/GameAction")} */ js_strict(true); +include("critical.js"); // required -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("AutoMule.js"); -include("Gambling.js"); -include("CraftingSystem.js"); -include("TorchSystem.js"); -include("MuleLogger.js"); -include("GameAction.js"); -include("common/util.js"); +// globals needed for core gameplay +includeCoreLibs(); -includeCommonLibs(); +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +// main thread specific +const LocalChat = require("./libs/modules/LocalChat"); function main () { - D2Bot.init(); // Get D2Bot# handle - D2Bot.ingame(); - - (function (global, original) { - global.load = function (...args) { - original.apply(this, args); - delay(500); - }; - })([].filter.constructor("return this")(), load); - - // wait until game is ready - while (!me.gameReady) { - delay(50); - } - - clearAllEvents(); // remove any event listeners from game crash - - // load heartbeat if it isn't already running - !getScript("tools/heartbeat.js") && load("tools/heartbeat.js"); - - if (getScript("d2botmap.dbj")) { - include("manualplay/MapMode.js"); - MapMode.include(); - Config.init(true); - LocalChat.init(); - - // load threads - me.automap = true; - load("libs/manualplay/threads/mapthread.js"); - load("libs/manualplay/threads/maphelper.js"); - load("libs/manualplay/threads/maptoolsthread.js"); - Config.ManualPlayPick && load("libs/manualplay/threads/pickthread.js"); - Config.PublicMode && load("tools/party.js"); - - while (true) { - delay(1000); - } - } - - // MuleLogger handler - if (MuleLogger.inGameCheck()) return true; - - // don't load default for dropper/mules - if (getScript("D2BotDropper.dbj") || getScript("D2BotMule.dbj")) { - FileTools.exists("libs/ItemDB.js") && include("ItemDB.js"); - load("tools/AreaWatcher.js"); - - while (true) { - delay(1000); - } - } - - let sojPause; - let sojCounter = 0; - let startTime = getTickCount(); - - this.scriptEvent = function (msg) { - if (msg === "quit") return; - if (typeof msg === "string" && msg === "soj") { - sojPause = true; - sojCounter = 0; - } - }; - - this.copyDataEvent = function (mode, msg) { - // "Mule Profile" option from D2Bot# - if (mode === 0 && msg === "mule") { - if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo")) { - if (AutoMule.getMuleItems().length > 0) { - D2Bot.printToConsole("Mule triggered"); - scriptBroadcast("mule"); - scriptBroadcast("quit"); - } else { - D2Bot.printToConsole("No items to mule."); - } - } else { - D2Bot.printToConsole("Profile not enabled for muling."); - } - } - - // getProfile - if (mode === 1638) { - msg = JSON.parse(msg); - - if (msg.Tag) { - GameAction.init(msg.Tag); - } - } - }; - - // Initialize libs - load config variables, build pickit list, attacks, containers and cubing and runeword recipes - Config.init(true); - Pickit.init(true); - Attack.init(); - Storage.Init(); - CraftingSystem.buildLists(); - Runewords.init(); - Cubing.init(); - LocalChat.init(); - - // Load event listeners - addEventListener("scriptmsg", this.scriptEvent); - addEventListener("copydata", this.copyDataEvent); - - // GameAction/AutoMule/TorchSystem/Gambling/Crafting handler - if (GameAction.inGameCheck() || AutoMule.inGameCheck() || TorchSystem.inGameCheck() || Gambling.inGameCheck() || CraftingSystem.inGameCheck()) { - return true; - } - - me.maxgametime = Config.MaxGameTime * 1000; - let stats = DataFile.getStats(); - - // Check for experience decrease -> log death. Skip report if life chicken is disabled. - if (stats.name === me.name && me.getStat(sdk.stats.Experience) < stats.experience && Config.LifeChicken > 0) { - D2Bot.printToConsole("You died in last game. | Area :: " + stats.lastArea + " | Script :: " + stats.debugInfo.currScript, sdk.colors.D2Bot.Red); - D2Bot.printToConsole("Experience decreased by " + (stats.experience - me.getStat(sdk.stats.Experience)), sdk.colors.D2Bot.Red); - DataFile.updateStats("deaths"); - D2Bot.updateDeaths(); - } - - DataFile.updateStats(["experience", "name"]); - - // Load threads - load("tools/ToolsThread.js"); - (Config.TownCheck || Config.TownHP > 0 || Config.TownMP > 0) && load("tools/TownChicken.js"); - - if (Config.PublicMode) { - Config.PublicMode === true ? require("libs/modules/SimpleParty") : load("tools/Party.js"); - } - - Config.AntiHostile && load("tools/AntiHostile.js"); - - if (Config.FastPick) { - print("ÿc2Fast pickit active."); - addEventListener("itemaction", Pickit.itemEvent); - } - - // One time maintenance - check cursor, get corpse, clear leftover items, pick items in case anything important was dropped - if (!Scripts.UserAddon && !Scripts.Test) { - // main checks - Cubing.cursorCheck(); - Town.getCorpse(); - Town.clearBelt(); - Pather.init(); // initialize wp data - - let {x, y} = me; - Config.ClearInvOnStart && Town.clearInventory(); - [x, y].distance > 3 && Pather.moveTo(x, y); - Pickit.pickItems(); - me.hpPercent <= 10 && Town.heal() && me.cancelUIFlags(); - - if (Config.DebugMode) { - delay(2000); - let script = getScript(); - - if (script) { - do { - console.log(script); - } while (script.getNext()); - } - } - } - - me.automap = Config.AutoMap; - - // Next game = drop keys - TorchSystem.keyCheck() && scriptBroadcast("torch"); - - // Auto skill and stat - if (Config.AutoSkill.Enabled && include("common/AutoSkill.js")) { - AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save); - } - - if (Config.AutoStat.Enabled && include("common/AutoStat.js")) { - AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk); - } - - // offline - !me.realm && D2Bot.updateRuns(); - - // Go - Loader.init(); - - if (Config.MinGameTime && getTickCount() - startTime < Config.MinGameTime * 1000) { - try { - Town.goToTown(); - - while (getTickCount() - startTime < Config.MinGameTime * 1000) { - me.overhead("Stalling for " + Math.round(((startTime + (Config.MinGameTime * 1000)) - getTickCount()) / 1000) + " Seconds"); - delay(1000); - } - } catch (e1) { - print(e1); - } - } - - DataFile.updateStats("gold"); - - if (sojPause) { - try { - Town.doChores(); - me.maxgametime = 0; - - while (sojCounter < Config.SoJWaitTime) { - me.overhead("Waiting for SoJ sales... " + (Config.SoJWaitTime - sojCounter) + " min"); - delay(6e4); - - sojCounter += 1; - } - } catch (e2) { - print(e2); - } - } - - if (Config.LastMessage) { - switch (typeof Config.LastMessage) { - case "string": - say(Config.LastMessage.replace("$nextgame", DataFile.getStats().nextGame, "i")); - - break; - case "object": - for (let i = 0; i < Config.LastMessage.length; i += 1) { - say(Config.LastMessage[i].replace("$nextgame", DataFile.getStats().nextGame, "i")); - } - - break; - } - } - - removeEventListener("scriptmsg", this.scriptEvent); - - AutoMule.muleCheck() && scriptBroadcast("mule"); - CraftingSystem.checkFullSets() && scriptBroadcast("crafting"); - TorchSystem.keyCheck() && scriptBroadcast("torch"); - - // Anni handler. Mule Anni if it's in unlocked space and profile is set to mule torch/anni. - let anni = me.findItem(sdk.items.SmallCharm, sdk.items.mode.inStorage, -1, sdk.items.quality.Unique); - - if (anni && !Storage.Inventory.IsLocked(anni, Config.Inventory) && AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) { - scriptBroadcast("muleAnni"); - } - - removeEventListener("copydata", this.copyDataEvent); - - scriptBroadcast("quit"); - - return true; + D2Bot.init(); // Get D2Bot# handle + D2Bot.ingame(); + + (function (global, original) { + global.load = function (...args) { + original.apply(this, args); + delay(500); + }; + })([].filter.constructor("return this")(), load); + + // wait until game is ready + while (!me.gameReady) { + delay(50); + } + + clearAllEvents(); // remove any event listeners from game crash + + // load heartbeat if it isn't already running + let _heartbeat = getScript("threads/heartbeat.js"); + if (!_heartbeat || !_heartbeat.running) { + load("threads/heartbeat.js"); + } + + // SoloPlay runs in it's own thread - check to ensure it exists in the files + if (getScript("D2BotSoloPlay.dbj") && FileTools.exists("libs/SoloPlay/SoloPlay.js")) { + load("libs/SoloPlay/SoloPlay.js"); + getScript(true).stop(); // kill this thread + return true; + } + + // map mode runs in it's own thread + if (getScript("d2botmap.dbj")) { + load("libs/manualplay/main.js"); + getScript(true).stop(); // kill this thread + return true; + } + + // muling runs in it's own thread + if (getScript("d2botmule.dbj")) { + load("libs/systems/automule/main.js"); + getScript(true).stop(); // kill this thread + return true; + } + + // MuleLogger handler + if (MuleLogger.inGameCheck()) return true; + + // don't load default for dropper/mules + if (getScript("D2BotDropper.dbj")) { + load("threads/AreaWatcher.js"); + + while (me.ingame) { + delay(10000); + } + return true; + } + + let sojPause; + let sojCounter = 0; + let startTime = getTickCount(); + + this.scriptEvent = function (msg) { + if (msg === "quit") return; + if (typeof msg === "string" && msg === "soj") { + sojPause = true; + sojCounter = 0; + } + }; + + this.copyDataEvent = function (mode, msg) { + // "Mule Profile" option from D2Bot# + if (mode === 0 && msg === "mule") { + if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo")) { + if (AutoMule.getMuleItems().length > 0) { + D2Bot.printToConsole("Mule triggered"); + scriptBroadcast("mule"); + scriptBroadcast("quit"); + } else { + D2Bot.printToConsole("No items to mule."); + } + } else { + D2Bot.printToConsole("Profile not enabled for muling."); + } + } + + // getProfile + if (mode === 1638) { + msg = JSON.parse(msg); + + if (msg.Tag) { + GameAction.init(msg.Tag); + } + } + }; + + // Initialize libs - load config variables, build pickit list, attacks, containers and cubing and runeword recipes + Config.init(true); + Pickit.init(true); + Attack.init(); + Storage.Init(); + CraftingSystem.buildLists(); + Runewords.init(); + Cubing.init(); + LocalChat.init(); + + // Load event listeners + addEventListener("scriptmsg", this.scriptEvent); + addEventListener("copydata", this.copyDataEvent); + + // GameAction/AutoMule/TorchSystem/Gambling/Crafting handler + if (GameAction.inGameCheck() + || AutoMule.inGameCheck() + || TorchSystem.inGameCheck() + || Gambling.inGameCheck() + || CraftingSystem.inGameCheck()) { + return true; + } + + me.maxgametime = Time.minutes(Config.MaxGameTime); + let stats = DataFile.getStats(); + + // Check for experience decrease -> log death. Skip report if life chicken is disabled. + if (stats.name === me.name && me.getStat(sdk.stats.Experience) < stats.experience && Config.LifeChicken > 0) { + D2Bot.printToConsole( + "You died in last game. | Area :: " + stats.lastArea + "\n" + + "Experience decreased by " + (stats.experience - me.getStat(sdk.stats.Experience)), + sdk.colors.D2Bot.Red + ); + DataFile.updateStats("deaths"); + D2Bot.updateDeaths(); + } + + DataFile.updateStats(["experience", "name"]); + + // Load threads + load("threads/ToolsThread.js"); + if (Config.TownCheck || Config.TownHP > 0 || Config.TownMP > 0) { + require("libs/modules/workers/TownChicken"); + } + + if (Config.DebugMode.Stack) { + require("libs/modules/workers/Guard"); + } + + if (Config.PublicMode) { + Config.PublicMode === true + ? require("libs/modules/workers/SimpleParty") + : load("threads/Party.js"); + } + + Config.AntiHostile && load("threads/AntiHostile.js"); + + // Advertise + if (Config.Advertise.Enabled) { + require("libs/modules/workers/Advertise"); + } + + if (Config.FastPick) { + print("ÿc2Fast pickit active."); + addEventListener("itemaction", Pickit.itemEvent); + } + + // waypoint cacher + require("libs/modules/workers/WpWatcher"); + + // One time maintenance - check cursor, get corpse, clear leftover items, pick items in case anything important was dropped + if (!Scripts.UserAddon && !Scripts.Test) { + // main checks + Cubing.cursorCheck(); + Town.getCorpse(); + Town.clearBelt(); + Pather.init(); // initialize wp data + + let { x, y } = me; + Config.ClearInvOnStart && Town.clearInventory(); + [x, y].distance > 3 && Pather.moveTo(x, y); + Pickit.pickItems(); + me.hpPercent <= 10 && Town.heal() && me.cancelUIFlags(); + + if (Config.DebugMode.Memory) { + delay(2000); + getThreads() + .sort(function (a, b) { + return b.memory - a.memory; + }) + .forEach(function (thread) { + console.debug(thread); + }); + } + } + + me.automap = Config.AutoMap; + + // Next game = drop keys + TorchSystem.keyCheck() && scriptBroadcast("torch"); + + // Auto skill and stat + if (Config.AutoSkill.Enabled && include("core/Auto/AutoSkill.js")) { + AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save); + } + + if (Config.AutoStat.Enabled && include("core/Auto/AutoStat.js")) { + AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk); + } + + // offline + !me.realm && D2Bot.updateRuns(); + + // Go + Loader.init(); + + if (Config.MinGameTime && getTickCount() - startTime < Config.MinGameTime * 1000) { + try { + Town.goToTown(); + + if (Config.AnnounceGameTimeRemaing) { + say( + "Next game in " + + Math.round(((startTime + Config.MinGameTime) - getTickCount()) / 1000) + + " seconds." + ); + } + + while (getTickCount() - startTime < Config.MinGameTime * 1000) { + me.overhead( + "Stalling for " + + Math.round(((startTime + Time.seconds(Config.MinGameTime)) - getTickCount()) / 1000) + " Seconds" + ); + delay(1000); + } + } catch (e) { + console.error(e); + } + } + + DataFile.updateStats("gold"); + + if (sojPause) { + try { + Town.doChores(); + me.maxgametime = 0; + + while (sojCounter < Config.SoJWaitTime) { + me.overhead("Waiting for SoJ sales... " + (Config.SoJWaitTime - sojCounter) + " min"); + delay(6e4); + + sojCounter += 1; + } + } catch (e) { + console.error(e); + } + } + + if (Config.LastMessage) { + switch (typeof Config.LastMessage) { + case "string": + say(Config.LastMessage.replace("$nextgame", DataFile.getStats().nextGame, "i")); + + break; + case "object": + for (let msg of Config.LastMessage) { + say(msg.replace("$nextgame", DataFile.getStats().nextGame, "i")); + } + + break; + } + } + + removeEventListener("scriptmsg", this.scriptEvent); + + AutoMule.muleCheck() && scriptBroadcast("mule"); + CraftingSystem.checkFullSets() && scriptBroadcast("crafting"); + TorchSystem.keyCheck() && scriptBroadcast("torch"); + + // Anni handler. Mule Anni if it's in unlocked space and profile is set to mule torch/anni. + let anni = me.findItem(sdk.items.SmallCharm, sdk.items.mode.inStorage, -1, sdk.items.quality.Unique); + + if (anni && !Storage.Inventory.IsLocked(anni, Config.Inventory) + && AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) { + scriptBroadcast("muleAnni"); + } + + removeEventListener("copydata", this.copyDataEvent); + + scriptBroadcast("quit"); + + return true; } diff --git a/d2bs/kolbot/libs/AutoMule.js b/d2bs/kolbot/libs/AutoMule.js deleted file mode 100644 index 9c3cb258e..000000000 --- a/d2bs/kolbot/libs/AutoMule.js +++ /dev/null @@ -1,767 +0,0 @@ -/** -* @filename AutoMule.js -* @author kolton, theBGuy -* @desc Configuration file for Mule system -* -*/ - -const AutoMule = { - Mules: { - "Mule1": { - muleProfile: "", // The name of mule profile in d2bot#. It will be started and stopped when needed. - accountPrefix: "", // Account prefix. Numbers added automatically when making accounts. - accountPassword: "", // Account password. - charPrefix: "", // Character prefix. Suffix added automatically when making characters. - realm: "", // Available options: "useast", "uswest", "europe", "asia" - expansion: true, - ladder: true, - hardcore: false, - charsPerAcc: 18, // Maximum number of mules to create per account (between 1 to 18) - - // Game name and password of the mule game. Never use the same game name as for mule logger. - muleGameName: ["", ""], // ["gamename", "password"] - - // List of profiles that will mule items. Example: enabledProfiles: ["profile 1", "profile 2"], - enabledProfiles: [""], - - // Stop a profile prior to muling. Useful when running 8 bots without proxies. - stopProfile: "", - stopProfileKeyRelease: false, // true = stopProfile key will get released on stop. useful when using 100% of your keys for botting. - - // Trigger muling at the end of a game if used space in stash and inventory is equal to or more than given percent. - usedStashTrigger: 80, - usedInventoryTrigger: 80, - - // Mule items that have been stashed at some point but are no longer in pickit. - muleOrphans: true, - // Continuous Mule settings - continuousMule: false, // Mule stays in game for continuous muling. muleProfile must be dedicated and started manually. - skipMuleResponse: false, // Skip mule response check and attempt to join mule game. Useful if mule is shared and/or ran on different system. - onlyLogWhenFull: false // Only log character when full, solves an issue with droppers attempting to use characters who are already in game - } - }, - - /** Torch/Anni mules - - Torch is muled in OrgTorch script after finishing uber Tristram successfully or when starting OrgTorch script with a Torch already on the character. - - Anni is muled after successfully killing Diablo in Palace Cellar level 3 using Config.KillDclone option or KillDClone script. - If a profile is listed in Torch/Anni mule's enabledProfiles list, it will also do a check to mule Anni at the end of each game. - Anni that is in locked inventory slot will not be muled. - - * Each mule will hold either a Torch or an Anni, but not both. As soon as the current mule has either one, a new one will be created. - */ - TorchAnniMules: { - "Mule1": { - muleProfile: "", // The name of mule profile in d2bot#. It will be started and stopped when needed. - accountPrefix: "", // Account prefix. Numbers added automatically when making accounts. - accountPassword: "", // Account password. - charPrefix: "", // Character prefix. Suffix added automatically when making characters. - realm: "", // Available options: "useast", "uswest", "europe", "asia" - expansion: true, - ladder: true, - hardcore: false, - charsPerAcc: 8, // Maximum number of mules to create per account (between 1 to 18) - - // Game name and password of the mule game. Never use the same game name as for mule logger. - muleGameName: ["", ""], // ["gamename", "password"] - - // List of profiles that will mule items. Example: enabledProfiles: ["profile 1", "profile 2"], - enabledProfiles: [""], - - // Stop a profile prior to muling. Useful when running 8 bots without proxies. - stopProfile: "", - stopProfileKeyRelease: false, // true = stopProfile key will get released on stop. useful when using 100% of your keys for botting. - - // Continuous Mule settings - continuousMule: true, // Mule stays in game for continuous muling. muleProfile must be dedicated and started manually. - skipMuleResponse: true, // Skip mule response check and attempt to join mule game. Useful if mule is shared and/or ran on different system. - onlyLogWhenFull: true // Only log character when full, solves an issue with droppers attempting to use characters who are already in game - } - //########################################################################################## - }, - - inGame: false, - check: false, - torchAnniCheck: false, - gids: [], - - // *** Master functions *** - getInfo: function () { - let info; - - for (let i in this.Mules) { - if (this.Mules.hasOwnProperty(i)) { - for (let j = 0; j < this.Mules[i].enabledProfiles.length; j += 1) { - if (this.Mules[i].enabledProfiles[j].toLowerCase() === me.profile.toLowerCase()) { - !info && (info = {}); - info.muleInfo = this.Mules[i]; - - break; - } - } - } - } - - for (let i in this.TorchAnniMules) { - if (this.TorchAnniMules.hasOwnProperty(i)) { - for (let j = 0; j < this.TorchAnniMules[i].enabledProfiles.length; j += 1) { - if (this.TorchAnniMules[i].enabledProfiles[j].toLowerCase() === me.profile.toLowerCase()) { - !info && (info = {}); - info.torchMuleInfo = this.TorchAnniMules[i]; - - break; - } - } - } - } - - return info; - }, - - muleCheck: function () { - let info = this.getInfo(); - - if (info && info.hasOwnProperty("muleInfo")) { - let items = this.getMuleItems(); - - if (info.muleInfo.hasOwnProperty("usedStashTrigger") && info.muleInfo.hasOwnProperty("usedInventoryTrigger") - && Storage.Inventory.UsedSpacePercent() >= info.muleInfo.usedInventoryTrigger - && Storage.Stash.UsedSpacePercent() >= info.muleInfo.usedStashTrigger && items.length > 0) { - D2Bot.printToConsole("MuleCheck triggered!", sdk.colors.D2Bot.DarkGold); - - return true; - } - - for (let i = 0; i < items.length; i += 1) { - if (this.matchItem(items[i], Config.AutoMule.Trigger)) { - D2Bot.printToConsole("MuleCheck triggered!", sdk.colors.D2Bot.DarkGold); - return true; - } - } - } - - return false; - }, - - getMule: function () { - let info = this.getInfo(); - - if (info) { - if (this.check && info.hasOwnProperty("muleInfo")) return info.muleInfo; - if (this.torchAnniCheck && info.hasOwnProperty("torchMuleInfo")) return info.torchMuleInfo; - } - - return false; - }, - - outOfGameCheck: function () { - if (!this.check && !this.torchAnniCheck) return false; - - let muleObj = this.getMule(); - if (!muleObj) return false; - - function muleCheckEvent(mode, msg) { - mode === 10 && (muleInfo = JSON.parse(msg)); - } - - let stopCheck = false; - let once = false; - let muleInfo = {status: ""}; - let failCount = 0; - let Controls = require("./modules/Control"); - - if (!muleObj.continuousMule || !muleObj.skipMuleResponse) { - addEventListener("copydata", muleCheckEvent); - } - - if (muleObj.continuousMule) { - D2Bot.printToConsole("Starting mule.", sdk.colors.D2Bot.DarkGold); - D2Bot.start(muleObj.muleProfile); - } else { - D2Bot.printToConsole("Starting " + (this.torchAnniCheck === 2 ? "anni " : this.torchAnniCheck === 1 ? "torch " : "") + "mule profile: " + muleObj.muleProfile, sdk.colors.D2Bot.DarkGold); - } - - MainLoop: - while (true) { - // Set status to ready if using continuous mule with no response check - if (muleObj.continuousMule && muleObj.skipMuleResponse) { - muleInfo.status = "ready"; - - // If nothing received our copy data start the mule profile - } else if (!sendCopyData(null, muleObj.muleProfile, 10, JSON.stringify({profile: me.profile, mode: this.torchAnniCheck || 0})) && !muleObj.continuousMule) { - // if the mule profile isn't already running and there is a profile to be stopped, stop it before starting the mule profile - if (!stopCheck && muleObj.stopProfile && !String.isEqual(me.profile, muleObj.stopProfile)) { - D2Bot.stop(muleObj.stopProfile, muleObj.stopProfileKeyRelease); - stopCheck = true; - delay(2000); // prevents cd-key in use error if using -skiptobnet on mule profile - } - - D2Bot.start(muleObj.muleProfile); - } - - delay(1000); - - switch (muleInfo.status) { - case "loading": - if (!muleObj.continuousMule && !stopCheck && muleObj.stopProfile && !String.isEqual(me.profile, muleObj.stopProfile)) { - D2Bot.stop(muleObj.stopProfile, muleObj.stopProfileKeyRelease); - - stopCheck = true; - } - - failCount += 1; - - break; - case "busy": - case "begin": - D2Bot.printToConsole("Mule profile is busy.", sdk.colors.D2Bot.Red); - - break MainLoop; - case "ready": - Starter.LocationEvents.openJoinGameWindow(); - - delay(2000); - - this.inGame = true; - me.blockMouse = true; - - try { - joinGame(muleObj.muleGameName[0], muleObj.muleGameName[1]); - } catch (joinError) { - delay(100); - } - - me.blockMouse = false; - - // Untested change 11.Feb.14. - for (let i = 0; i < 8; i += 1) { - delay(1000); - - if (me.ingame && me.gameReady) { - break MainLoop; - } - } - - if (!once && getLocation() === sdk.game.locations.GameIsFull) { - Controls.CreateGameWindow.click(); - Starter.LocationEvents.openJoinGameWindow(); - // how long should we wait? - once = true; - let date = new Date(); - let dateString = "[" + new Date( - date.getTime() + Time.minutes(3) - (date.getTimezoneOffset() * 60000) - ).toISOString().slice(0, -5).replace(/-/g, "/").replace("T", " ") + "]"; - console.log("Game is full so lets hangout for a bit before we try again. Next attempt at " + dateString); - } - - if (muleObj.continuousMule && muleObj.skipMuleResponse && !me.ingame) { - D2Bot.printToConsole("Unable to join mule game", sdk.colors.D2Bot.Red); - - break MainLoop; - } - - break; - default: - failCount += 1; - - break; - } - - if (failCount >= 260) { - D2Bot.printToConsole("No response from mule profile.", sdk.colors.D2Bot.Red); - - break; - } - } - - removeEventListener("copydata", muleCheckEvent); - - while (me.ingame) { - delay(1000); - } - - this.inGame = false; - this.check = false; - this.torchAnniCheck = false; - - // No response - stop mule profile - if (!muleObj.continuousMule) { - if (failCount >= 60) { - D2Bot.stop(muleObj.muleProfile, true); - delay(1000); - } - - if (stopCheck && muleObj.stopProfile) { - D2Bot.start(muleObj.stopProfile); - } - } - - return true; - }, - - inGameCheck: function () { - let muleObj, tick; - let begin = false; - let timeout = Time.minutes(4); // Ingame mule timeout - let status = "muling"; - - // Single player - if (!me.gamename) return false; - - let info = this.getInfo(); - - // Profile is not a part of AutoMule - if (!info) return false; - - // Profile is not in mule or torch mule game - if (!((info.hasOwnProperty("muleInfo") && String.isEqual(me.gamename, info.muleInfo.muleGameName[0])) - || (info.hasOwnProperty("torchMuleInfo") && String.isEqual(me.gamename, info.torchMuleInfo.muleGameName[0])))) { - return false; - } - - function dropStatusEvent (mode, msg) { - if (mode === 10) { - switch (JSON.parse(msg).status) { - case "report": // reply to status request - sendCopyData(null, muleObj.muleProfile, 12, JSON.stringify({status: status})); - - break; - case "quit": // quit command - status = "quit"; - - break; - } - } - } - - function muleModeEvent (msg) { - switch (msg) { - case "2": - case "1": - AutoMule.torchAnniCheck = Number(msg); - - break; - case "0": - AutoMule.check = true; - } - } - - try { - addEventListener("scriptmsg", muleModeEvent); - scriptBroadcast("getMuleMode"); - delay(500); - - if (!this.check && !this.torchAnniCheck) { - throw new Error("Error - Unable to determine mule mode"); - } - - muleObj = this.getMule(); - me.maxgametime = 0; - - !muleObj.continuousMule && addEventListener("copydata", dropStatusEvent); - - if (!Town.goToTown(1)) { - throw new Error("Error - Failed to go to Act 1"); - } - - Town.move("stash"); - - if (muleObj.continuousMule) { - print("ÿc4AutoMuleÿc0: Looking for valid mule"); - tick = getTickCount(); - - while (getTickCount() - tick < timeout) { - if (this.verifyMulePrefix(muleObj.charPrefix)) { - print("ÿc4AutoMuleÿc0: Found valid mule"); - begin = true; - - break; - } - - delay(2000); - } - - if (!begin) { - throw new Error("Error - Unable to find mule character"); - } - } else { - sendCopyData(null, muleObj.muleProfile, 11, "begin"); - } - - let gameType = this.torchAnniCheck === 2 ? " anni" : this.torchAnniCheck === 1 ? " torch" : ""; - print("ÿc4AutoMuleÿc0: In" + gameType + " mule game."); - D2Bot.updateStatus("AutoMule: In" + gameType + " mule game."); - - if (this.torchAnniCheck === 2) { - this.dropCharm(true); - } else if (this.torchAnniCheck === 1) { - this.dropCharm(false); - } else { - this.dropStuff(); - } - - status = "done"; - tick = getTickCount(); - - while (true) { - if (muleObj.continuousMule) { - if (this.isFinished()) { - D2Bot.printToConsole("Done muling.", sdk.colors.D2Bot.DarkGold); - status = "quit"; - } else { - delay(5000); - } - } - - if (status === "quit") { - break; - } - - if (getTickCount() - tick > timeout) { - if (Misc.getPlayerCount() > 1) { - // we aren't alone currently so chill for a bit longer - tick = getTickCount(); - Packet.questRefresh(); // to prevent disconnect from idleing - } else { - D2Bot.printToConsole("Mule didn't rejoin. Picking up items.", sdk.colors.D2Bot.Red); - Misc.useItemLog = false; // Don't log items picked back up in town. - Pickit.pickItems(); - - break; - } - } - - delay(500); - } - - return true; - } catch (e) { - console.error(e); - - return false; - } finally { - removeEventListener("scriptmsg", muleModeEvent); - removeEventListener("copydata", dropStatusEvent); - - if (!muleObj.continuousMule) { - D2Bot.stop(muleObj.muleProfile, true); - delay(1000); - muleObj.stopProfile && D2Bot.start(muleObj.stopProfile); - } - - delay(2000); - quit(); - } - }, - - // finished if no items are on ground - isFinished: function () { - let item = Game.getItem(); - - if (item) { - do { - // check if the items we dropped are on the ground still - if (getDistance(me, item) < 20 && item.onGroundOrDropping && AutoMule.gids.includes(item.gid)) { - return false; - } - } while (item.getNext()); - } - - // we are finished so reset gid list - AutoMule.gids.length = 0; - - return true; - }, - - // make sure mule character is in game - verifyMulePrefix: function (mulePrefix) { - try { - let player = getParty(); - - if (player) { - let regex = new RegExp(mulePrefix, "i"); - - do { - // case insensitive matching - if (player.name.match(regex)) { - return true; - } - } while (player.getNext()); - } - } catch (e) { - delay(100); - } - - return false; - }, - - dropStuff: function () { - if (!Town.openStash()) return false; - - let items = (this.getMuleItems() || []); - if (items.length === 0) return false; - AutoMule.gids = items.map(i => i.gid); - - D2Bot.printToConsole("AutoMule: Transfering " + items.length + " items.", sdk.colors.D2Bot.DarkGold); - D2Bot.printToConsole("AutoMule: " + JSON.stringify(items.map(i => i.prettyPrint)), sdk.colors.D2Bot.DarkGold); - - items.forEach(item => item.drop()); - delay(1000); - me.cancel(); - - return true; - }, - - matchItem: function (item, list) { - let parsedPickit = [], classIDs = []; - - for (let i = 0; i < list.length; i += 1) { - let info = { - file: "Character Config", - line: list[i] - }; - - // classids - if (typeof list[i] === "number") { - classIDs.push(list[i]); - } else if (typeof list[i] === "string") { - // pickit entries - let parsedLine = NTIP.ParseLineInt(list[i], info); - parsedLine && parsedPickit.push(parsedLine); - } - } - - return (classIDs.includes(item.classid) || NTIP.CheckItem(item, parsedPickit)); - }, - - // get a list of items to mule - getMuleItems: function () { - let info = this.getInfo(); - - if (!info || !info.hasOwnProperty("muleInfo")) return false; - - let items = []; - let item = me.getItem(-1, sdk.items.mode.inStorage); - const muleOrphans = !!(info.muleInfo.hasOwnProperty("muleOrphans") && info.muleInfo.muleOrphans); - - /** - * @param {ItemUnit} item - */ - const isAKey = (item) => [sdk.items.quest.KeyofTerror, sdk.items.quest.KeyofHate, sdk.items.quest.KeyofDestruction].includes(item.classid); - - /** - * check if wanted by any of the systems - * @param {ItemUnit} item - * @returns {boolean} if item is wanted by various systems - */ - const isWanted = (item) => (AutoMule.cubingIngredient(item) || AutoMule.runewordIngredient(item) || AutoMule.utilityIngredient(item)); - - if (item) { - do { - if (Town.ignoredItemTypes.indexOf(item.itemType) === -1 - && (![Pickit.Result.UNID, Pickit.Result.UNWANTED, Pickit.Result.TRASH].includes(Pickit.checkItem(item).result) || (item.isInStash && muleOrphans)) - && item.classid !== sdk.quest.item.Cube // Don't drop Horadric Cube - && (!item.isAnni) // Don't drop Annihilus - && (!item.isTorch) // Don't drop Hellfire Torch - && (item.isInStash || (item.isInInventory && !Storage.Inventory.IsLocked(item, Config.Inventory))) // Don't drop items in locked slots - && ((!TorchSystem.getFarmers() && !TorchSystem.isFarmer()) || isAKey(item))) { // Don't drop Keys if part of TorchSystem - // Always drop items on Force or Trigger list - if (this.matchItem(item, Config.AutoMule.Force.concat(Config.AutoMule.Trigger)) - // Don't drop Excluded items or Runeword/Cubing/CraftingSystem ingredients - || (!this.matchItem(item, Config.AutoMule.Exclude) && !isWanted(item))) { - items.push(copyUnit(item)); - } - } - } while (item.getNext()); - } - - return items; - }, - - utilityIngredient: function (item) { - return (!!item && CraftingSystem.validGids.includes(item.gid)); - }, - - // check if an item is a cubing ingredient - cubingIngredient: function (item) { - if (!item) return false; - - for (let i = 0; i < Cubing.validIngredients.length; i += 1) { - if (item.gid === Cubing.validIngredients[i].gid) { - return true; - } - } - - return false; - }, - - // check if an item is a runeword ingrediend - rune, empty base or bad rolled base - runewordIngredient: function (item) { - if (!item) return false; - if (Runewords.validGids.includes(item.gid)) return true; - - if (!this.baseGids) { - this.baseGids = []; - - for (let i = 0; i < Config.Runewords.length; i += 1) { - let base = Runewords.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0)) || Runewords.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0), true); - base && this.baseGids.push(base.gid); - } - } - - return this.baseGids.includes(item.gid); - }, - - dropCharm: function (dropAnni) { - if (!Town.openStash()) return false; - - let item; - - if (dropAnni) { - item = me.findItem(sdk.items.SmallCharm, sdk.items.mode.inStorage, -1, sdk.items.quality.Unique); - - if (item && !Storage.Inventory.IsLocked(item, Config.Inventory)) { - D2Bot.printToConsole("AutoMule: Transfering Anni.", sdk.colors.D2Bot.DarkGold); - } else { - return false; - } - } else { - item = me.findItem(sdk.items.LargeCharm, sdk.items.mode.inStorage, -1, sdk.items.quality.Unique); - - if (item) { - D2Bot.printToConsole("AutoMule: Transfering Torch.", sdk.colors.D2Bot.DarkGold); - } else { - return false; - } - } - - item.drop(); - delay(1000); - me.cancel() && me.cancel(); - - return true; - }, - - // *** Mule functions *** - getMaster: function (info) { - let muleObj = info.mode === 1 ? this.TorchAnniMules : this.Mules; - - for (let i in muleObj) { - if (muleObj.hasOwnProperty(i)) { - for (let j in muleObj[i]) { - if (muleObj[i].hasOwnProperty(j) && j === "enabledProfiles") { - for (let k = 0; k < muleObj[i][j].length; k += 1) { - if (String.isEqual(muleObj[i][j][k], info.profile)) { - return { - profile: muleObj[i][j][k], - mode: info.mode - }; - } - } - } - } - } - } - - return false; - }, - - /** - * @param {number} mode - mule mode - * @param {string} master - profile that whats to mule - * @param {boolean} continuous - whether we are continuous or not - * @returns {muleObj | boolean} - */ - getMuleObject: function (mode, master, continuous = false) { - mode = mode || 0; - let mule = mode > 0 ? this.TorchAnniMules : this.Mules; - - for (let i in mule) { - if (mule.hasOwnProperty(i)) { - if (mule[i].muleProfile && mule[i].enabledProfiles && String.isEqual(mule[i].muleProfile, me.profile) - && (continuous || mule[i].enabledProfiles.includes(master))) { - return mule[i]; - } - } - } - - return false; - }, - - getMuleFilename: function (mode, master, continuous = false) { - mode = mode || 0; - let mule = mode > 0 ? this.TorchAnniMules : this.Mules; - let file; - - // Iterate through mule object - for (let i in mule) { - if (mule.hasOwnProperty(i)) { - // Mule profile matches config - if (mule[i].muleProfile && String.isEqual(mule[i].muleProfile, me.profile) && (continuous || mule[i].enabledProfiles.includes(master))) { - file = mode === 0 ? "logs/AutoMule." + i + ".json" : "logs/TorchMule." + i + ".json"; - - // If file exists check for valid info - if (FileTools.exists(file)) { - try { - let jsonStr = FileTools.readText(file); - let jsonObj = JSON.parse(jsonStr); - - // Return filename containing correct mule info - if (mule[i].accountPrefix && jsonObj.account && jsonObj.account.match(mule[i].accountPrefix)) { - return file; - } - } catch (e) { - print(e); - } - } else { - return file; - } - } - } - } - - // File exists but doesn't contain valid info - remake - FileTools.remove(file); - - return file; - }, - - getMuleMode: function() { - for (let i in this.Mules) { - if (this.Mules.hasOwnProperty(i)) { - if (this.Mules[i].muleProfile && String.isEqual(this.Mules[i].muleProfile, me.profile)) { - return 0; - } - } - } - - for (let i in this.TorchAnniMules) { - if (this.TorchAnniMules.hasOwnProperty(i)) { - if (this.TorchAnniMules[i].muleProfile && String.isEqual(this.TorchAnniMules[i].muleProfile, me.profile)) { - return 1; - } - } - } - - return 0; - }, - - isContinousMule: function () { - for (let i in this.Mules) { - if (this.Mules.hasOwnProperty(i)) { - if (this.Mules[i].muleProfile && String.isEqual(this.Mules[i].muleProfile, me.profile)) { - return this.Mules[i].continuousMule; - } - } - } - - for (let i in this.TorchAnniMules) { - if (this.TorchAnniMules.hasOwnProperty(i)) { - if (this.TorchAnniMules[i].muleProfile && String.isEqual(this.TorchAnniMules[i].muleProfile, me.profile)) { - return this.TorchAnniMules[i].continuousMule; - } - } - } - - return false; - } -}; diff --git a/d2bs/kolbot/libs/CraftingSystem.js b/d2bs/kolbot/libs/CraftingSystem.js deleted file mode 100644 index cbd9fbda1..000000000 --- a/d2bs/kolbot/libs/CraftingSystem.js +++ /dev/null @@ -1,486 +0,0 @@ -/** -* @filename CraftingSystem.js -* @author kolton -* @desc Multi-profile crafting system -* @notes This system is experimental, there will be no support offered for it. -* If you can't get it to work, leave it be. -* -*/ - -const CraftingSystem = {}; - -CraftingSystem.Teams = { - "Team 1": { - // List of profiles that will collect ingredients - Collectors: [], - - // List of profiles that will craft/reroll items - Workers: [], - - // List of Worker game names (without the numbers) - CraftingGames: [], - - /* BaseItems - list of base item class ids - * Ingredients - list of recipe ingredients - * SetAmount - number of full sets to gather before transfering - * Type - the type of recipe. Available options: "crafting", "runewords", "cubing" - */ - Sets: [ - // LLD Crafting - - // Caster Belt set, char lvl 29 - // Light Belt classid 345, shopped at nightmare Elzix - // Sharkskin Belt classid 391, shopped at nightmare Elzix - //{BaseItems: [345, 391], Ingredients: [615, 643, 561], SetAmount: 2, Type: "crafting"}, - - // Runeword Making - - // Spirit Runeset + Hel - //{BaseItems: [29, 30, 31], Ingredients: [616, 618, 619, 620, 624], SetAmount: 2, Type: "runewords"}, - - // Misc. Cubing - - // Reroll rare Diadem - //{BaseItems: [421], Ingredients: [601, 601, 601], SetAmount: 1, Type: "cubing"} - ] - } -}; - -// ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## - -// Get the Crafting System information for current profile -CraftingSystem.getInfo = function () { - for (let i in CraftingSystem.Teams) { - if (CraftingSystem.Teams.hasOwnProperty(i)) { - for (let j = 0; j < CraftingSystem.Teams[i].Collectors.length; j += 1) { - if (CraftingSystem.Teams[i].Collectors[j].toLowerCase() === me.profile.toLowerCase()) { - let info = CraftingSystem.Teams[i]; - info.collector = true; - info.worker = false; - - return info; - } - } - - for (let j = 0; j < CraftingSystem.Teams[i].Workers.length; j += 1) { - if (CraftingSystem.Teams[i].Workers[j].toLowerCase() === me.profile.toLowerCase()) { - let info = CraftingSystem.Teams[i]; - info.collector = false; - info.worker = true; - - return info; - } - } - } - } - - return false; -}; - -// ################################################# -// # Item collector out of game specific functions # -// ################################################# - -CraftingSystem.check = false; -CraftingSystem.inGame = false; - -CraftingSystem.outOfGameCheck = function () { - if (!CraftingSystem.check) return false; - - let info = CraftingSystem.getInfo(); - - function scriptMsg(msg) { - let obj; - - try { - obj = JSON.parse(msg); - } catch (e) { - return false; - } - - obj.name === "RequestWorker" && scriptBroadcast(JSON.stringify({name: "WorkerName", value: worker.name})); - - return true; - } - - if (info && info.collector) { - let worker = CraftingSystem.getWorker(); - - if (worker && worker.game) { - D2Bot.printToConsole("CraftingSystem: Transfering items.", sdk.colors.D2Bot.DarkGold); - D2Bot.updateStatus("CraftingSystem: In game."); - addEventListener("scriptmsg", scriptMsg); - - CraftingSystem.inGame = true; - me.blockMouse = true; - - delay(2000); - joinGame(worker.game[0], worker.game[1]); - - me.blockMouse = false; - - delay(5000); - - while (me.ingame) { - delay(1000); - } - - CraftingSystem.inGame = false; - CraftingSystem.check = false; - - removeEventListener("scriptmsg", scriptMsg); - - return true; - } - } - - return false; -}; - -CraftingSystem.getWorker = function () { - let rval = { - game: false, - name: false - }; - let info = CraftingSystem.getInfo(); - - function checkEvent(mode, msg) { - if (mode === 4) { - for (let i = 0; i < info.CraftingGames.length; i += 1) { - if (info.CraftingGames[i] && msg.match(info.CraftingGames[i], "i")) { - rval.game = msg.split("/"); - - break; - } - } - } - } - - if (info && info.collector) { - addEventListener("copydata", checkEvent); - - rval.game = false; - - for (let i = 0; i < info.Workers.length; i += 1) { - sendCopyData(null, info.Workers[i], 0, JSON.stringify({name: "GetGame", profile: me.profile})); - delay(100); - - if (rval.game) { - rval.name = info.Workers[i]; - - break; - } - } - - removeEventListener("copydata", checkEvent); - - return rval; - } - - return false; -}; - -// ############################################# -// # Item collector in-game specific functions # -// ############################################# - -CraftingSystem.inGameCheck = function () { - let info = CraftingSystem.getInfo(); - - if (info && info.collector) { - for (let i = 0; i < info.CraftingGames.length; i += 1) { - if (info.CraftingGames[i] && me.gamename.match(info.CraftingGames[i], "i")) { - CraftingSystem.dropItems(); - me.cancel(); - delay(5000); - quit(); - - return true; - } - } - } - - return false; -}; - -CraftingSystem.neededItems = []; -CraftingSystem.validGids = []; -CraftingSystem.itemList = []; -CraftingSystem.fullSets = []; - -// Check whether item can be used for crafting -CraftingSystem.validItem = function (item) { - switch (item.itemType) { - case sdk.items.type.Jewel: - // Use junk jewels only - return NTIP.CheckItem(item) === Pickit.Result.UNWANTED; - } - - return true; -}; - -// Check if the item should be picked for crafting -CraftingSystem.checkItem = function (item) { - let info = CraftingSystem.getInfo(); - - if (info) { - for (let i = 0; i < CraftingSystem.neededItems.length; i += 1) { - if (item.classid === CraftingSystem.neededItems[i] && CraftingSystem.validItem(item)) { - return true; - } - } - } - - return false; -}; - -// Check if the item should be kept or dropped -CraftingSystem.keepItem = function (item) { - let info = CraftingSystem.getInfo(); - - if (info) { - if (info.collector) return CraftingSystem.validGids.includes(item.gid); - - if (info.worker) { - // Let pickit decide whether to keep crafted - return item.crafted ? false : true; - } - } - - return false; -}; - -// Collect ingredients only if a worker needs them -CraftingSystem.getSetInfoFromWorker = function (workerName) { - let setInfo = false; - let info = CraftingSystem.getInfo(); - - function copyData(mode, msg) { - let obj; - - if (mode === 4) { - try { - obj = JSON.parse(msg); - } catch (e) { - return false; - } - - obj && obj.name === "SetInfo" && (setInfo = obj.value); - } - - return true; - } - - if (info && info.collector) { - addEventListener("copydata", copyData); - sendCopyData(null, workerName, 0, JSON.stringify({name: "GetSetInfo", profile: me.profile})); - delay(100); - - if (setInfo !== false) { - removeEventListener("copydata", copyData); - - return setInfo; - } - - removeEventListener("copydata", copyData); - } - - return false; -}; - -CraftingSystem.init = function (name) { - let info = CraftingSystem.getInfo(); - - if (info && info.collector) { - for (let i = 0; i < info.Sets.length; i += 1) { - info.Sets[i].Enabled = false; - } - - let setInfo = CraftingSystem.getSetInfoFromWorker(name); - - if (setInfo) { - for (let i = 0; i < setInfo.length; i += 1) { - if (setInfo[i] === 1 && info.Sets[i].Enabled === false) { - info.Sets[i].Enabled = true; - } - } - } - } -}; - -// Build global lists of needed items and valid ingredients -CraftingSystem.buildLists = function (onlyNeeded) { - let info = CraftingSystem.getInfo(); - - if (info && info.collector) { - CraftingSystem.neededItems = []; - CraftingSystem.validGids = []; - CraftingSystem.fullSets = []; - CraftingSystem.itemList = me.findItems(-1, sdk.items.mode.inStorage); - - for (let i = 0; i < info.Sets.length; i += 1) { - if (!onlyNeeded || info.Sets[i].Enabled) { - CraftingSystem.checkSet(info.Sets[i]); - } - } - - return true; - } - - return false; -}; - -// Check which ingredients a set needs and has -CraftingSystem.checkSet = function (set) { - let rval = {}; - let setNeeds = []; - let setHas = []; - - // Get what set needs - // Multiply by SetAmount - for (let amount = 0; amount < set.SetAmount; amount += 1) { - for (let i = 0; i < set.Ingredients.length; i += 1) { - setNeeds.push(set.Ingredients[i]); - } - } - - // Remove what set already has - for (let i = 0; i < setNeeds.length; i += 1) { - for (let j = 0; j < CraftingSystem.itemList.length; j += 1) { - if (CraftingSystem.itemList[j].classid === setNeeds[i]) { - setHas.push(CraftingSystem.itemList[j].gid); - setNeeds.splice(i, 1); - CraftingSystem.itemList.splice(j, 1); - - i -= 1; - j -= 1; - } - } - } - - // The set is complete - setNeeds.length === 0 && CraftingSystem.fullSets.push(setHas.slice()); - - CraftingSystem.neededItems = CraftingSystem.neededItems.concat(setNeeds); - CraftingSystem.validGids = CraftingSystem.validGids.concat(setHas); - - CraftingSystem.neededItems.sort(Sort.numbers); - CraftingSystem.validGids.sort(Sort.numbers); - - return rval; -}; - -// Update lists when a valid ingredient is picked -CraftingSystem.update = function (item) { - CraftingSystem.neededItems.splice(CraftingSystem.neededItems.indexOf(item.classid), 1); - CraftingSystem.validGids.push(item.gid); - - return true; -}; - -// Cube flawless gems if the ingredient is a perfect gem -CraftingSystem.checkSubrecipes = function () { - for (let i = 0; i < CraftingSystem.neededItems.length; i += 1) { - switch (CraftingSystem.neededItems[i]) { - case sdk.items.gems.Perfect.Amethyst: - case sdk.items.gems.Perfect.Topaz: - case sdk.items.gems.Perfect.Sapphire: - case sdk.items.gems.Perfect.Emerald: - case sdk.items.gems.Perfect.Ruby: - case sdk.items.gems.Perfect.Diamond: - case sdk.items.gems.Perfect.Skull: - if (Cubing.subRecipes.indexOf(CraftingSystem.neededItems[i]) === -1) { - Cubing.subRecipes.push(CraftingSystem.neededItems[i]); - Cubing.recipes.push({ - Ingredients: [CraftingSystem.neededItems[i] - 1, CraftingSystem.neededItems[i] - 1, CraftingSystem.neededItems[i] - 1], - Index: 0, - AlwaysEnabled: true, - MainRecipe: "Crafting" - }); - } - - break; - } - } - - return true; -}; - -// Check if there are any complete ingredient sets -CraftingSystem.checkFullSets = function () { - let info = CraftingSystem.getInfo(); - - if (info && info.collector) { - for (let i = 0; i < info.Workers.length; i += 1) { - CraftingSystem.init(info.Workers[i]); - CraftingSystem.buildLists(true); - - if (CraftingSystem.fullSets.length) { - return true; - } - } - } - - return false; -}; - -// Drop complete ingredient sets -CraftingSystem.dropItems = function () { - Town.goToTown(1); - Town.move("stash"); - Town.openStash(); - - let worker; - - function scriptMsg(msg) { - let obj; - - try { - obj = JSON.parse(msg); - } catch (e) { - return false; - } - - !!obj && obj.name === "WorkerName" && (worker = obj.value); - - return true; - } - - addEventListener("scriptmsg", scriptMsg); - scriptBroadcast(JSON.stringify({name: "RequestWorker"})); - delay(100); - - if (worker) { - CraftingSystem.init(worker); - CraftingSystem.buildLists(true); - removeEventListener("scriptmsg", scriptMsg); - - while (CraftingSystem.fullSets.length) { - let gidList = CraftingSystem.fullSets.shift(); - - while (gidList.length) { - let item = me.getItem(-1, -1, gidList.shift()); - !!item && item.drop(); - } - } - - CraftingSystem.dropGold(); - delay(1000); - me.cancel(); - } - - return true; -}; - -CraftingSystem.dropGold = function () { - Town.goToTown(1); - Town.move("stash"); - - if (me.getStat(sdk.stats.Gold) >= 10000) { - gold(10000); - } else if (me.getStat(sdk.stats.GoldBank) + me.getStat(sdk.stats.Gold) >= 10000) { - Town.openStash(); - gold(10000 - me.getStat(sdk.stats.Gold), 4); - gold(10000); - } -}; diff --git a/d2bs/kolbot/libs/Gambling.js b/d2bs/kolbot/libs/Gambling.js deleted file mode 100644 index 8c2f10ac4..000000000 --- a/d2bs/kolbot/libs/Gambling.js +++ /dev/null @@ -1,190 +0,0 @@ -/** -* @filename Gambling.js -* @author kolton -* @desc multi-profile gambling system -* -*/ - -const Gambling = { - Teams: { - //#################################################################################################### - /* Gambling system for kolbot - - Allows lower level characters to get a steady income of gold to gamble LLD/VLLD items - Not recommended for rings/amulets because of their high price (unless you want 3 gold finders to supply one gambler) - It's possible to have multiple teams of gamblers/gold finders. Individual entries are separated by commas. - - Setting up: - - "Gamble Team 1": { // Put a unique team name here. - - goldFinders: ["GF Profile 1", "GF Profile 2"], // List of gold finder PROFILE names. They will join gamble games to drop gold - - gamblers: ["Gambler 1", "Gambler 2"], // List of gambler PROFILE names. They will keep gambling and picking up gold from gold finders. - - gambleGames: ["Gambling-", "HeyIGamble-"], // Games that gold finders will join, don't use numbers. - - goldTrigger: 2500000, // Minimum amount of gold before giving it to gamblers. - - goldReserve: 200000 // Amount of gold to keep after dropping. - } - - Once set up properly, the gold finders will run their own games and join gamblers' games when they're out of gold. - */ - - "Gamble Team 1": { - goldFinders: [""], - gamblers: [""], - gambleGames: [""], - - goldTrigger: 2500000, - goldReserve: 200000 - } - - //#################################################################################################### - }, - - inGame: false, - - getInfo: function (profile) { - !profile && (profile = me.profile); - - for (let i in this.Teams) { - if (this.Teams.hasOwnProperty(i)) { - for (let j = 0; j < this.Teams[i].goldFinders.length; j += 1) { - if (this.Teams[i].goldFinders[j].toLowerCase() === profile.toLowerCase()) { - this.Teams[i].goldFinder = true; - this.Teams[i].gambler = false; - - return this.Teams[i]; - } - } - - for (let j = 0; j < this.Teams[i].gamblers.length; j += 1) { - if (this.Teams[i].gamblers[j].toLowerCase() === profile.toLowerCase()) { - this.Teams[i].goldFinder = false; - this.Teams[i].gambler = true; - - return this.Teams[i]; - } - } - } - } - - return false; - }, - - inGameCheck: function () { - let info = this.getInfo(); - - if (info && info.goldFinder) { - for (let i = 0; i < info.gambleGames.length; i += 1) { - if (info.gambleGames[i] && me.gamename.match(info.gambleGames[i], "i")) { - this.dropGold(); - DataFile.updateStats("gold"); - delay(5000); - quit(); - - return true; - } - } - } - - return false; - }, - - dropGold: function () { - let info = this.getInfo(); - - if (info && info.goldFinder) { - Town.goToTown(1); - Town.move("stash"); - - while (me.getStat(sdk.stats.Gold) + me.getStat(sdk.stats.GoldBank) > info.goldReserve) { - gold(me.getStat(sdk.stats.Gold)); // drop current gold - Town.openStash(); - - // check stashed gold vs max carrying capacity - if (me.getStat(sdk.stats.GoldBank) <= me.getStat(sdk.stats.Level) * 1e4) { - // leave minGold in stash, pick the rest - gold(me.getStat(sdk.stats.GoldBank) - info.goldReserve, 4); - } else { - // pick max carrying capacity - gold(me.getStat(sdk.stats.Level) * 1e4, 4); - } - - delay(1000); - } - } - }, - - outOfGameCheck: function () { - let info = this.getInfo(); - - if (info && info.goldFinder && DataFile.getStats().gold >= info.goldTrigger) { - let game = this.getGame(); - - if (game) { - D2Bot.printToConsole("Joining gold drop game.", sdk.colors.D2Bot.DarkGold); - - this.inGame = true; - me.blockMouse = true; - - delay(2000); - joinGame(game[0], game[1]); - - me.blockMouse = false; - - delay(5000); - - while (me.ingame) { - delay(1000); - } - - this.inGame = false; - - return true; - } - } - - return false; - }, - - getGame: function () { - let game; - let info = this.getInfo(); - - if (!info || !info.goldFinder) { - return false; - } - - function checkEvent(mode, msg) { - if (mode === 4) { - for (let i = 0; i < info.gambleGames.length; i += 1) { - if (info.gambleGames[i] && msg.match(info.gambleGames[i], "i")) { - game = msg.split("/"); - - break; - } - } - } - } - - addEventListener("copydata", checkEvent); - - game = null; - - for (let i = 0; i < info.gamblers.length; i += 1) { - sendCopyData(null, info.gamblers[i], 0, me.profile); - delay(100); - - if (game) { - break; - } - } - - removeEventListener("copydata", checkEvent); - - return game; - } -}; diff --git a/d2bs/kolbot/libs/GameAction.js b/d2bs/kolbot/libs/GameAction.js deleted file mode 100644 index d514e2a55..000000000 --- a/d2bs/kolbot/libs/GameAction.js +++ /dev/null @@ -1,196 +0,0 @@ -/* eslint-disable dot-notation */ -/** -* @filename GameAction.js -* @author noah-@github.com -* @desc Perform task based actions specified by Profile Tag -* -*/ -include("MuleLogger.js"); - -const GameAction = { - LogNames: true, // Put account/character name on the picture - LogItemLevel: true, // Add item level to the picture - LogEquipped: false, // include equipped items - LogMerc: false, // include items merc has equipped (if alive) - SaveScreenShot: false, // Save pictures in jpg format (saved in 'Images' folder) - IngameTime: 60, // Time to wait before leaving game - - // don't edit - init: function (task) { - this.task = JSON.parse(task); - - if (this.task["data"] && typeof this.task.data === "string") { - this.task.data = JSON.parse(this.task.data); - } - - MuleLogger.LogNames = this.LogNames; - MuleLogger.LogItemLevel = this.LogItemLevel; - MuleLogger.LogEquipped = this.LogEquipped; - MuleLogger.LogMerc = this.LogMerc; - MuleLogger.SaveScreenShot = this.SaveScreenShot; - - return true; - }, - - update: function (action, data) { - if (typeof action !== "string") throw new Error("Action must be a string!"); - - typeof data !== "string" && (data = JSON.stringify(data)); - - D2Bot.printToConsole(data); - - let tag = JSON.parse(JSON.stringify(this.task)); // deep copy - tag.action = action; - tag.data = data; - D2Bot.setTag(tag); - }, - - gameInfo: function () { - let gi = { gameName: null, gamePass: null }; - - switch (this.task.action) { - case "doMule": - gi = null; - - break; // create random game - case "doDrop": - gi.gameName = this.task.data.gameName; - gi.gamePass = this.task.data.gamePass; - - break; // join game - default: - gi = null; - - break; - } - - return gi; - }, - - getLogin: function () { - let li = { realm: null, account: null, password: null }; - - (this.task && this.task.data) && (li.password = this.load(this.task.hash)); - - // drop specific object - if (this.task.data["items"] && this.task.data.items.length > 0) { - li.realm = this.task.data.items[0].realm; - li.account = this.task.data.items[0].account; - } - - // mule log specific objects - this.task.data["realm"] && (li.realm = this.task.data.realm); - this.task.data["account"] && (li.account = this.task.data.account); - - if (!li.password || !li.account || !li.realm) { - this.update("done", "Realm, Account, or Password was invalid!"); - D2Bot.stop(); - delay(500); - } - - return li; - }, - - getCharacters: function () { - let chars = []; - - // drop specific object - if (this.task.data["items"]) { - for (let i = 0; i < this.task.data.items.length; i += 1) { - if (chars.indexOf(this.task.data.items[i].character) === -1) { - chars.push(this.task.data.items[i].character); - } - } - } - - // mule log specific object - this.task.data["chars"] && (chars = this.task.data.chars); - - return chars; - }, - - inGameCheck: function () { - if (getScript("D2BotGameAction.dbj")) { - while (!this["task"]) { - D2Bot.getProfile(); - delay(500); - } - - switch (this.task.action) { - case "doMule": - MuleLogger.logChar(); - - break; - case "doDrop": - this.dropItems(this.task.data.items); - MuleLogger.logChar(); - - break; - default: - break; - } - - while ((getTickCount() - me.gamestarttime) < this.IngameTime * 1000) { - delay(1000); - } - - quit(); - - return true; - } - - return false; - }, - - load: function (hash) { - let filename = "data/secure/" + hash + ".txt"; - - if (!FileTools.exists(filename)) { - this.update("done", "File " + filename + " does not exist!"); - D2Bot.stop(); - delay(5000); - quitGame(); - } - - return FileTools.readText(filename); - }, - - save: function (hash, data) { - let filename = "data/secure/" + hash + ".txt"; - FileTools.writeText(filename, data); - }, - - dropItems: function (droplist) { - if (!droplist) return; - - while (!me.gameReady) { - delay(100); - } - - let items = me.getItemsEx(); - - if (!items || !items.length) return; - - for (let i = 0; i < droplist.length; i += 1) { - if (droplist[i].character !== me.charname) { - continue; - } - - let info = droplist[i].itemid.split(":");//":" + unit.classid + ":" + unit.location + ":" + unit.x + ":" + unit.y; - - let classid = info[1]; - let loc = info[2]; - let unitX = info[3]; - let unitY = info[4]; - - // for debug purposes - print("classid: " + classid + " location: " + loc + " X: " + unitX + " Y: " + unitY); - - for (let j = 0; j < items.length; j += 1) { - if (items[j].classid.toString() === classid && items[j].location.toString() === loc && items[j].x.toString() === unitX && items[j].y.toString() === unitY) { - items[j].drop(); - } - } - } - }, -}; diff --git a/d2bs/kolbot/libs/GameData.js b/d2bs/kolbot/libs/GameData.js deleted file mode 100644 index a518f47a9..000000000 --- a/d2bs/kolbot/libs/GameData.js +++ /dev/null @@ -1,289 +0,0 @@ -/** -* @filename GameData.js -* @author Nishimura-Katsuo -* @desc game data library -* -*/ - -const MONSTER_INDEX_COUNT = 734; -const AREA_INDEX_COUNT = 137; -const SUPER = [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 1, 4, 0, 2, 3, 1, 0, 1, 1, 0, 0, 0, 1, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5, 1, 1, 1, 1, 3]; -const AREA_LOCALE_STRING = [5389, 5055, 5054, 5053, 5052, 5051, 5050, 5049, 5048, 5047, 5046, 5045, 5044, 5043, 5042, 5041, 5040, 5039, 5038, 5037, 5036, 5035, 5034, 5033, 5032, 5031, 5030, 5029, 5028, 5027, 5026, 5025, 5024, 5023, 5022, 5021, 5020, 5019, 5018, 788, 852, 851, 850, 849, 848, 847, 846, 845, 844, 843, 842, 841, 840, 839, 838, 837, 836, 835, 834, 833, 832, 831, 830, 829, 828, 827, 826, 826, 826, 826, 826, 826, 826, 825, 824, 820, 819, 818, 817, 816, 815, 814, 813, 812, 810, 811, 809, 808, 806, 805, 807, 804, 845, 844, 803, 802, 801, 800, 799, 798, 797, 796, 795, 790, 792, 793, 794, 791, 789, 22646, 22647, 22648, 22649, 22650, 22651, 22652, 22653, 22654, 22655, 22656, 22657, 22658, 22659, 22660, 22662, 21865, 21866, 21867, 22663, 22664, 22665, 22667, 22666, 5389, 5389, 5389, 5018]; -const MONSTER_KEYS = [ - ["mon1", "mon2", "mon3", "mon4", "mon5", "mon6", "mon7", "mon8", "mon9", "mon10"], - ["nmon1", "nmon2", "nmon3", "nmon4", "nmon5", "nmon6", "nmon7", "nmon8", "nmon9", "nmon10"], -][me.diff && 1]; // mon is for normal, nmon is for nm/hell, umon is specific to picking champion/uniques in normal - -/** - * MonsterData[classID] - * .Index = Index of this monster - * .Level = Level of this monster in normal (use GameData.monsterLevel to find monster levels) - * .Ranged = if monster is ranged - * .Rarity = weight of this monster in level generation - * .Threat = threat level used by mercs - * .Align = alignment of unit (determines what it will attack) - * .Melee = if monster is melee - * .NPC = if unit is NPC - * .Demon = if monster is demon - * .Flying = if monster is flying - * .Boss = if monster is a boss - * .ActBoss = if monster is act boss - * .Killable = if monster can be killed - * .Convertable = if monster is affected by convert or mind blast - * .NeverCount = if not counted as a minion - * .DeathDamage = explodes on death - * .Regeneration = hp regeneration - * .LocaleString = locale string index for getLocaleString - * .ExperienceModifier = percent of base monster exp this unit rewards when killed - * .Undead = 2 if greater undead, 1 if lesser undead, 0 if neither - * .Drain = drain effectiveness percent - * .Block = block percent - * .Physical = physical resist - * .Magic = magic resist - * .Fire = fire resist - * .Lightning = lightning resist - * .Poison = poison resist - * .Minions = array of minions that can spawn with this unit - */ - -const MonsterData = Array(MONSTER_INDEX_COUNT); - -for (let i = 0; i < MonsterData.length; i++) { - let index = i; - MonsterData[i] = Object.freeze(Object.defineProperties({}, { - Index: {get: () => index, enumerable: true}, - Level: {get: () => getBaseStat("monstats", index, "Level"), enumerable: true}, // normal only, nm/hell are determined by area's LevelEx - Ranged: {get: () => getBaseStat("monstats", index, "RangedType"), enumerable: true}, - Rarity: {get: () => getBaseStat("monstats", index, "Rarity"), enumerable: true}, - Threat: {get: () => getBaseStat("monstats", index, "threat"), enumerable: true}, - Align: {get: () => getBaseStat("monstats", index, "Align"), enumerable: true}, - Melee: {get: () => getBaseStat("monstats", index, "isMelee"), enumerable: true}, - NPC: {get: () => getBaseStat("monstats", index, "npc"), enumerable: true}, - Demon: {get: () => getBaseStat("monstats", index, "demon"), enumerable: true}, - Flying: {get: () => getBaseStat("monstats", index, "flying"), enumerable: true}, - Boss: {get: () => getBaseStat("monstats", index, "boss"), enumerable: true}, - ActBoss: {get: () => getBaseStat("monstats", index, "primeevil"), enumerable: true}, - Killable: {get: () => getBaseStat("monstats", index, "killable"), enumerable: true}, - Convertable: {get: () => getBaseStat("monstats", index, "switchai"), enumerable: true}, - NeverCount: {get: () => getBaseStat("monstats", index, "neverCount"), enumerable: true}, - DeathDamage: {get: () => getBaseStat("monstats", index, "deathDmg"), enumerable: true}, - Regeneration: {get: () => getBaseStat("monstats", index, "DamageRegen"), enumerable: true}, - LocaleString: {get: () => getBaseStat("monstats", index, "NameStr"), enumerable: true}, - ExperienceModifier: {get: () => getBaseStat("monstats", index, ["Exp", "Exp(N)", "Exp(H)"][me.diff]), enumerable: true}, - Undead: {get: () => (getBaseStat("monstats", index, "hUndead") && 2) | (getBaseStat("monstats", index, "lUndead") && 1), enumerable: true}, - Drain: {get: () => getBaseStat("monstats", index, ["Drain", "Drain(N)", "Drain(H)"][me.diff]), enumerable: true}, - Block: {get: () => getBaseStat("monstats", index, ["ToBlock", "ToBlock(N)", "ToBlock(H)"][me.diff]), enumerable: true}, - Physical: {get: () => getBaseStat("monstats", index, ["ResDm", "ResDm(N)", "ResDm(H)"][me.diff]), enumerable: true}, - Magic: {get: () => getBaseStat("monstats", index, ["ResMa", "ResMa(N)", "ResMa(H)"][me.diff]), enumerable: true}, - Fire: {get: () => getBaseStat("monstats", index, ["ResFi", "ResFi(N)", "ResFi(H)"][me.diff]), enumerable: true}, - Lightning: {get: () => getBaseStat("monstats", index, ["ResLi", "ResLi(N)", "ResLi(H)"][me.diff]), enumerable: true}, - Cold: {get: () => getBaseStat("monstats", index, ["ResCo", "ResCo(N)", "ResCo(H)"][me.diff]), enumerable: true}, - Poison: {get: () => getBaseStat("monstats", index, ["ResPo", "ResPo(N)", "ResPo(H)"][me.diff]), enumerable: true}, - Minions: {get: () => [getBaseStat("monstats", index, "minion1"), getBaseStat("monstats", index, "minion2")].filter(mon => mon !== 65535), enumerable: true}, - })); -} - -Object.freeze(MonsterData); - -/** - * AreaData[areaID] - * .Super = number of super uniques present in this area - * .Index = areaID - * .Act = act this area is in [0-4] - * .MonsterDensity = value used to determine monster population density - * .ChampionPacks.Min = minimum number of champion or unique packs that spawn here - * .ChampionPacks.Max = maximum number of champion or unique packs that spawn here - * .Waypoint = number in waypoint menu that leads to this area - * .Level = level of area (use GameData.areaLevel) - * .Size.x = width of area - * .Size.y = depth of area - * .Monsters = array of monsters that can spawn in this area - * .LocaleString = locale string index for getLocaleString - */ - -const AreaData = new Array(AREA_INDEX_COUNT); - -for (let i = 0; i < AreaData.length; i++) { - let index = i; - AreaData[i] = Object.freeze(Object.defineProperties({}, { - Super: {get: () => SUPER[index], enumerable: true}, - Index: {get: () => index, enumerable: true}, - Act: {get: () => getBaseStat("levels", index, "Act"), enumerable: true}, - MonsterDensity: {get: () => getBaseStat("levels", index, ["MonDen", "MonDen(N)", "MonDen(H)"][me.diff]), enumerable: true}, - ChampionPacks: {get: () => ({Min: getBaseStat("levels", index, ["MonUMin", "MonUMin(N)", "MonUMin(H)"][me.diff]), Max: getBaseStat("levels", index, ["MonUMax", "MonUMax(N)", "MonUMax(H)"][me.diff])}), enumerable: true}, - Waypoint: {get: () => getBaseStat("levels", index, "Waypoint"), enumerable: true}, - Level: {get: () => getBaseStat("levels", index, ["MonLvl1Ex", "MonLvl2Ex", "MonLvl3Ex"][me.diff]), enumerable: true}, - Size: {get: () => { - if (index === 111) { // frigid highlands doesn't specify size, manual measurement - return {x: 210, y: 710}; - } - - if (index === 112) { // arreat plateau doesn't specify size, manual measurement - return {x: 690, y: 230}; - } - - return { - x: getBaseStat("leveldefs", index, ["SizeX", "SizeX(N)", "SizeX(H)"][me.diff]), - y: getBaseStat("leveldefs", index, ["SizeY", "SizeY(N)", "SizeY(H)"][me.diff]) - }; - }, enumerable: true}, - Monsters: {get: () => MONSTER_KEYS.map(key => getBaseStat("levels", index, key)).filter(key => key !== 65535), enumerable: true}, - LocaleString: {get: () => AREA_LOCALE_STRING[index], enumerable: true}, - })); -} - -Object.freeze(AreaData); - -const GameData = { - townAreas: [0, 1, 40, 75, 103, 109], - monsterLevel: function (monsterID, areaID) { - if (me.diff) { // levels on nm/hell are determined by area, not by monster data - return AreaData[areaID].Level; - } - - return MonsterData[monsterID].Level; - }, - monsterExp: function (monsterID, areaID) { - return Experience.monsterExp[this.monsterLevel(monsterID, areaID)][me.diff] * MonsterData[monsterID].ExperienceModifier / 100; - }, - areaLevel: function (areaID) { - let levels = 0, total = 0; - - if (me.diff) { // levels on nm/hell are determined by area, not by monster data - return AreaData[areaID].Level; - } - - AreaData[areaID].Monsters.forEach(mon => { - levels += MonsterData[mon].Level * MonsterData[mon].Rarity; - total += MonsterData[mon].Rarity; - }); - - return Math.round(levels / total); - }, - areaImmunities: function (areaID) { - let resists = {Physical: 0, Magic: 0, Fire: 0, Lightning: 0, Cold: 0, Poison: 0}; - - function checkmon (monID) { - for (let k in resists) { - resists[k] = Math.max(resists[k], MonsterData[monID][k]); - } - } - - AreaData[areaID].Monsters.forEach(mon => { - checkmon(mon); - MonsterData[mon].Minions.forEach(checkmon); - }); - - return Object.keys(resists).filter(key => resists[key] >= 100); - }, - levelModifier: function (clvl, mlvl) { - let bonus; - - if (clvl < 25 || mlvl < clvl) { - bonus = Experience.expCurve[Math.min(20, Math.max(0, Math.floor(mlvl - clvl + 10)))] / 255; - } else { - bonus = clvl / mlvl; - } - - return bonus * Experience.expPenalty[Math.min(30, Math.max(0, Math.round(clvl - 69)))] / 1024; - }, - multiplayerModifier: function (count) { - if (!count) { - let party = getParty(me); - - if (!party) { - return 1; - } - - count = 1; - - while (party.getNext()) { - count++; - } - } - - return (count + 1) / 2; - }, - partyModifier: function (playerID) { - let party = getParty(me), partyid = -1, level = 0, total = 0; - - if (!party) { - return 1; - } - - partyid = party.partyid; - - do { - if (party.partyid === partyid) { - total += party.level; - - if (playerID === party.name || playerID === party.gid) { - level = party.level; - } - } - } while (party.getNext()); - - return level / total; - }, - killExp: function (playerID, monsterID, areaID) { - let exp = this.monsterExp(monsterID, areaID), party = getParty(me), partyid = -1, level = 0, total = 0, gamesize = 0; - - if (!party) { - return 0; - } - - partyid = party.partyid; - - do { - gamesize++; - - if (party.partyid === partyid) { - total += party.level; - - if (playerID === party.name || playerID === party.gid) { - level = party.level; - } - } - } while (party.getNext()); - - return Math.floor(exp * this.levelModifier(level, this.monsterLevel(monsterID, areaID)) * this.multiplayerModifier(gamesize) * level / total); - }, - areaPartyExp: function (areaID, exclude = null, onlytown = true, ignore = null) { // amount of total party exp gained per kill on average - let party = getParty(me), partyid = -1, partylevels = 0, gamesize = 0, exp = 0, playerexp = 0, poolsize = 0; - - if (!party) { - return 0; - } - - // very rough approximation of unique population ratio, could be approved but this works well enough - let uniqueratio = parseFloat(Config.ChampionBias) * (AreaData[areaID].ChampionPacks.Min + AreaData[areaID].ChampionPacks.Max + AreaData[areaID].Super * 2) / (AreaData[areaID].Size.x * AreaData[areaID].Size.y); - - partyid = party.partyid; - - do { - gamesize++; - - if (party.partyid === partyid && party.name !== exclude && party.gid !== exclude && (!onlytown || this.townAreas.indexOf(party.area) > -1) && (areaID < 128 || party.level >= (1 + me.diff) * 20)) { - partylevels += party.level; - - if (party.name !== ignore && party.gid !== ignore) { - poolsize = 0; - playerexp = 0; - - AreaData[areaID].Monsters.forEach(mon => { - if (MonsterData[mon].Rarity > 0) { - playerexp += ((1 - uniqueratio) + (3 * uniqueratio)) * this.monsterExp(mon, areaID) * this.levelModifier(party.level, this.monsterLevel(mon, areaID)) * MonsterData[mon].Rarity; - poolsize += MonsterData[mon].Rarity; - } - }); - - if (poolsize) { - exp += party.level * playerexp / poolsize; - } - } - } - } while (party.getNext()); - - return (partylevels ? exp * this.multiplayerModifier(gamesize) / partylevels : 0); - } -}; diff --git a/d2bs/kolbot/libs/MuleLogger.js b/d2bs/kolbot/libs/MuleLogger.js deleted file mode 100644 index e7b1c4989..000000000 --- a/d2bs/kolbot/libs/MuleLogger.js +++ /dev/null @@ -1,212 +0,0 @@ -/** -* @filename MuleLogger.js -* @author kolton, theBGuy -* @desc Log items and perm configurable accounts/characters -* -*/ -!isIncluded("common/prototypes.js") && include("common/prototypes.js"); - -const MuleLogger = { - LogAccounts: { - /* Format: - "account1/password1/realm": ["charname1", "charname2 etc"], - "account2/password2/realm": ["charnameX", "charnameY etc"], - "account3/password3/realm": ["all"] - - To log a full account, put "account/password/realm": ["all"] - - realm = useast, uswest, europe or asia - - Individual entries are separated with a comma. - */ - }, - - LogGame: ["", ""], // ["gamename", "password"] - LogNames: true, // Put account/character name on the picture - LogItemLevel: true, // Add item level to the picture - LogEquipped: true, // include equipped items - LogMerc: true, // include items merc has equipped (if alive) - SaveScreenShot: false, // Save pictures in jpg format (saved in 'Images' folder) - AutoPerm: true, // override InGameTime to perm character - IngameTime: rand(60, 120), // (180, 210) to avoid RD, increase it to (7230, 7290) for mule perming - - inGameCheck: function () { - if (getScript("D2BotMuleLog.dbj") && this.LogGame[0] && me.gamename.match(this.LogGame[0], "i")) { - print("ÿc4MuleLoggerÿc0: Logging items on " + me.account + " - " + me.name + "."); - D2Bot.printToConsole("MuleLogger: Logging items on " + me.account + " - " + me.name + ".", sdk.colors.D2Bot.DarkGold); - this.logChar(); - let stayInGame = this.IngameTime; - let tick = getTickCount() + rand(1500, 1750) * 1000; // trigger anti-idle every ~30 minutes - - if (this.AutoPerm) { - let permInfo = this.loadPermedStatus(); - - if (!!permInfo.charname) { - if (permInfo.charname === me.charname && !permInfo.perm) { - stayInGame = rand(7230, 7290); - } - } - } - - while ((getTickCount() - me.gamestarttime) < Time.seconds(stayInGame)) { - me.overhead("ÿc2Log items done. ÿc4Stay in " + "ÿc4game more:ÿc0 " + Math.floor(stayInGame - (getTickCount() - me.gamestarttime) / 1000) + " sec"); - - delay(1000); - - if ((getTickCount() - tick) > 0) { - Packet.questRefresh(); // quest status refresh, working as anti-idle - tick += rand(1500, 1750) * 1000; - } - } - - quit(); - - return true; - } - - return false; - }, - - savePermedStatus: function (charPermInfo = {}) { - FileTools.writeText("logs/MuleLogPermInfo.json", JSON.stringify(charPermInfo)); - }, - - loadPermedStatus: function () { - if (!FileTools.exists("logs/MuleLogPermInfo.json")) throw new Error("File logs/MuleLogPermInfo.json does not exist!"); - let info = (FileTools.readText("logs/MuleLogPermInfo.json")); - return info ? JSON.parse(info) : {}; - }, - - load: function (hash) { - let filename = "data/secure/" + hash + ".txt"; - if (!FileTools.exists(filename)) throw new Error("File " + filename + " does not exist!"); - return FileTools.readText(filename); - }, - - save: function (hash, data) { - let filename = "data/secure/" + hash + ".txt"; - FileTools.writeText(filename, data); - }, - - remove: function () { - FileTools.remove("logs/MuleLog.json"); - FileTools.remove("logs/MuleLogPermInfo.json"); - }, - - // Log kept item stats in the manager. - logItem: function (unit, logIlvl = this.LogItemLevel) { - if (!isIncluded("common/misc.js")) { - include("common/misc.js"); - include("common/util.js"); - } - - let header = ""; - let name = unit.itemType + "_" + unit.fname.split("\n").reverse().join(" ").replace(/(y|ÿ)c[0-9!"+<:;.*]|\/|\\/g, "").trim(); - let desc = Misc.getItemDesc(unit, logIlvl) + "$" + unit.gid + ":" + unit.classid + ":" + unit.location + ":" + unit.x + ":" + unit.y + (unit.getFlag(sdk.items.flags.Ethereal) ? ":eth" : ""); - let color = unit.getColor(); - let code = Misc.getItemCode(unit); - let sock = unit.getItemsEx(); - - if (sock.length) { - for (let i = 0; i < sock.length; i += 1) { - if (sock[i].itemType === sdk.items.type.Jewel) { - desc += "\n\n"; - desc += Misc.getItemDesc(sock[i], logIlvl); - } - } - } - - return { - itemColor: color, - image: code, - title: name, - description: desc, - header: header, - sockets: Misc.getItemSockets(unit) - }; - }, - - logChar: function (logIlvl = this.LogItemLevel, logName = this.LogNames, saveImg = this.SaveScreenShot) { - while (!me.gameReady) { - delay(100); - } - - // try again if db is locked!! - if (isIncluded("ItemDB.js") || include("ItemDB.js")) { - while (!ItemDB.init(false)) { - delay(1000); - } - } - - let items = me.getItemsEx(); - if (!items.length) return; - - let folder, realm = me.realm || "Single Player"; - let finalString = ""; - - if (!FileTools.exists("mules/" + realm)) { - folder = dopen("mules"); - - folder.create(realm); - } - - if (!FileTools.exists("mules/" + realm + "/" + me.account)) { - folder = dopen("mules/" + realm); - - folder.create(me.account); - } - - // from bottom up: merc, equipped, inventory, stash, cube - items.sort(function (a, b) { - if (a.mode < b.mode) return -1; - if (a.mode > b.mode) return 1; - if (a.location === sdk.storage.Cube) return -1; - if (b.location === sdk.storage.Cube) return 1; - return b.location - a.location; - }); - - for (let i = 0; i < items.length; i += 1) { - if ((this.LogEquipped || items[i].isInStorage) && (items[i].quality > sdk.items.quality.Normal || !Misc.skipItem(items[i].classid))) { - let parsedItem = this.logItem(items[i], logIlvl); - - // Log names to saved image - logName && (parsedItem.header = (me.account || "Single Player") + " / " + me.name); - // Save image to kolbot/images/ - saveImg && D2Bot.saveItem(parsedItem); - // Always put name on Char Viewer items - !parsedItem.header && (parsedItem.header = (me.account || "Single Player") + " / " + me.name); - // Remove itemtype_ prefix from the name - parsedItem.title = parsedItem.title.substr(parsedItem.title.indexOf("_") + 1); - - items[i].isEquipped && (parsedItem.title += (items[i].isOnSwap ? " (secondary equipped)" : " (equipped)")); - items[i].isInInventory && (parsedItem.title += " (inventory)"); - items[i].isInStash && (parsedItem.title += " (stash)"); - items[i].isInCube && (parsedItem.title += " (cube)"); - - let string = JSON.stringify(parsedItem); - finalString += (string + "\n"); - } - } - - if (this.LogMerc) { - let merc = Misc.poll(() => me.getMerc(), 1000, 100); - - if (merc) { - let mercItems = merc.getItemsEx(); - - for (let i = 0; i < mercItems.length; i += 1) { - let parsedItem = this.logItem(mercItems[i]); - parsedItem.title += " (merc)"; - let string = JSON.stringify(parsedItem); - finalString += (string + "\n"); - saveImg && D2Bot.saveItem(parsedItem); - } - } - } - - // hcl = hardcore class ladder - // sen = softcore expan nonladder - FileTools.writeText("mules/" + realm + "/" + me.account + "/" + me.name + "." + ( me.playertype ? "h" : "s" ) + (me.gametype ? "e" : "c" ) + ( me.ladder > 0 ? "l" : "n" ) + ".txt", finalString); - print("Item logging done."); - } -}; diff --git a/d2bs/kolbot/libs/NTItemAlias.dbl b/d2bs/kolbot/libs/NTItemAlias.dbl deleted file mode 100644 index 4a3157f7f..000000000 --- a/d2bs/kolbot/libs/NTItemAlias.dbl +++ /dev/null @@ -1,1477 +0,0 @@ -/** -* @filename NTItemAlias.dbl -* @author kolton -* @credit d2nt -* @desc Item alias's to work with NTItemParser for kolbots pickit system -* -*/ - -var NTIPAliasType = {}; -NTIPAliasType["shield"] = 2; -NTIPAliasType["armor"] = 3; -NTIPAliasType["gold"] = 4; -NTIPAliasType["bowquiver"] = 5; -NTIPAliasType["crossbowquiver"] = 6; -NTIPAliasType["playerbodypart"] = 7; -NTIPAliasType["herb"] = 8; -NTIPAliasType["potion"] = 9; -NTIPAliasType["ring"] = 10; -NTIPAliasType["elixir"] = 11; -NTIPAliasType["amulet"] = 12; -NTIPAliasType["charm"] = 13; -NTIPAliasType["notused"] = 14; -NTIPAliasType["boots"] = 15; -NTIPAliasType["gloves"] = 16; -NTIPAliasType["notused"] = 17; -NTIPAliasType["book"] = 18; -NTIPAliasType["belt"] = 19; -NTIPAliasType["gem"] = 20; -NTIPAliasType["torch"] = 21; -NTIPAliasType["scroll"] = 22; -NTIPAliasType["notused"] = 23; -NTIPAliasType["scepter"] = 24; -NTIPAliasType["wand"] = 25; -NTIPAliasType["staff"] = 26; -NTIPAliasType["bow"] = 27; -NTIPAliasType["axe"] = 28; -NTIPAliasType["club"] = 29; -NTIPAliasType["sword"] = 30; -NTIPAliasType["hammer"] = 31; -NTIPAliasType["knife"] = 32; -NTIPAliasType["spear"] = 33; -NTIPAliasType["polearm"] = 34; -NTIPAliasType["crossbow"] = 35; -NTIPAliasType["mace"] = 36; -NTIPAliasType["helm"] = 37; -NTIPAliasType["missilepotion"] = 38; -NTIPAliasType["quest"] = 39; -NTIPAliasType["bodypart"] = 40; -NTIPAliasType["key"] = 41; -NTIPAliasType["throwingknife"] = 42; -NTIPAliasType["throwingaxe"] = 43; -NTIPAliasType["javelin"] = 44; -NTIPAliasType["weapon"] = 45; -NTIPAliasType["meleeweapon"] = 46; -NTIPAliasType["missileweapon"] = 47; -NTIPAliasType["thrownweapon"] = 48; -NTIPAliasType["comboweapon"] = 49; -NTIPAliasType["anyarmor"] = 50; -NTIPAliasType["anyshield"] = 51; -NTIPAliasType["miscellaneous"] = 52; -NTIPAliasType["socketfiller"] = 53; -NTIPAliasType["secondhand"] = 54; -NTIPAliasType["stavesandrods"] = 55; -NTIPAliasType["missile"] = 56; -NTIPAliasType["blunt"] = 57; -NTIPAliasType["jewel"] = 58; -NTIPAliasType["classspecific"] = 59; -NTIPAliasType["amazonitem"] = 60; -NTIPAliasType["barbarianitem"] = 61; -NTIPAliasType["necromanceritem"] = 62; -NTIPAliasType["paladinitem"] = 63; -NTIPAliasType["sorceressitem"] = 64; -NTIPAliasType["assassinitem"] = 65; -NTIPAliasType["druiditem"] = 66; -NTIPAliasType["handtohand"] = 67; -NTIPAliasType["orb"] = 68; -NTIPAliasType["voodooheads"] = 69; -NTIPAliasType["auricshields"] = 70; -NTIPAliasType["primalhelm"] = 71; -NTIPAliasType["pelt"] = 72; -NTIPAliasType["cloak"] = 73; -NTIPAliasType["rune"] = 74; -NTIPAliasType["circlet"] = 75; -NTIPAliasType["healingpotion"] = 76; -NTIPAliasType["manapotion"] = 77; -NTIPAliasType["rejuvpotion"] = 78; -NTIPAliasType["staminapotion"] = 79; -NTIPAliasType["antidotepotion"] = 80; -NTIPAliasType["thawingpotion"] = 81; -NTIPAliasType["smallcharm"] = 82; -NTIPAliasType["mediumcharm"] = 83; -NTIPAliasType["largecharm"] = 84; -NTIPAliasType["amazonbow"] = 85; -NTIPAliasType["amazonspear"] = 86; -NTIPAliasType["amazonjavelin"] = 87; -NTIPAliasType["assassinclaw"] = 88; -NTIPAliasType["magicbowquiv"] = 89; -NTIPAliasType["magicxbowquiv"] = 90; -NTIPAliasType["chippedgem"] = 91; -NTIPAliasType["flawedgem"] = 92; -NTIPAliasType["standardgem"] = 93; -NTIPAliasType["flawlessgem"] = 94; -NTIPAliasType["perfectgem"] = 95; -NTIPAliasType["amethyst"] = 96; -NTIPAliasType["diamond"] = 97; -NTIPAliasType["emerald"] = 98; -NTIPAliasType["ruby"] = 99; -NTIPAliasType["sapphire"] = 100; -NTIPAliasType["topaz"] = 101; -NTIPAliasType["skull"] = 102; - -var NTIPAliasClassID = {}; -NTIPAliasClassID["hax"] = 0; NTIPAliasClassID["handaxe"] = 0; -NTIPAliasClassID["axe"] = 1; -NTIPAliasClassID["2ax"] = 2; NTIPAliasClassID["doubleaxe"] = 2; -NTIPAliasClassID["mpi"] = 3; NTIPAliasClassID["militarypick"] = 3; -NTIPAliasClassID["wax"] = 4; NTIPAliasClassID["waraxe"] = 4; -NTIPAliasClassID["lax"] = 5; NTIPAliasClassID["largeaxe"] = 5; -NTIPAliasClassID["bax"] = 6; NTIPAliasClassID["broadaxe"] = 6; -NTIPAliasClassID["btx"] = 7; NTIPAliasClassID["battleaxe"] = 7; -NTIPAliasClassID["gax"] = 8; NTIPAliasClassID["greataxe"] = 8; -NTIPAliasClassID["gix"] = 9; NTIPAliasClassID["giantaxe"] = 9; -NTIPAliasClassID["wnd"] = 10; NTIPAliasClassID["wand"] = 10; -NTIPAliasClassID["ywn"] = 11; NTIPAliasClassID["yewwand"] = 11; -NTIPAliasClassID["bwn"] = 12; NTIPAliasClassID["bonewand"] = 12; -NTIPAliasClassID["gwn"] = 13; NTIPAliasClassID["grimwand"] = 13; -NTIPAliasClassID["clb"] = 14; NTIPAliasClassID["club"] = 14; -NTIPAliasClassID["scp"] = 15; NTIPAliasClassID["scepter"] = 15; -NTIPAliasClassID["gsc"] = 16; NTIPAliasClassID["grandscepter"] = 16; -NTIPAliasClassID["wsp"] = 17; NTIPAliasClassID["warscepter"] = 17; -NTIPAliasClassID["spc"] = 18; NTIPAliasClassID["spikedclub"] = 18; -NTIPAliasClassID["mac"] = 19; NTIPAliasClassID["mace"] = 19; -NTIPAliasClassID["mst"] = 20; NTIPAliasClassID["morningstar"] = 20; -NTIPAliasClassID["fla"] = 21; NTIPAliasClassID["flail"] = 21; -NTIPAliasClassID["whm"] = 22; NTIPAliasClassID["warhammer"] = 22; -NTIPAliasClassID["mau"] = 23; NTIPAliasClassID["maul"] = 23; -NTIPAliasClassID["gma"] = 24; NTIPAliasClassID["greatmaul"] = 24; -NTIPAliasClassID["ssd"] = 25; NTIPAliasClassID["shortsword"] = 25; -NTIPAliasClassID["scm"] = 26; NTIPAliasClassID["scimitar"] = 26; -NTIPAliasClassID["sbr"] = 27; NTIPAliasClassID["sabre"] = 27; -NTIPAliasClassID["flc"] = 28; NTIPAliasClassID["falchion"] = 28; -NTIPAliasClassID["crs"] = 29; NTIPAliasClassID["crystalsword"] = 29; -NTIPAliasClassID["bsd"] = 30; NTIPAliasClassID["broadsword"] = 30; -NTIPAliasClassID["lsd"] = 31; NTIPAliasClassID["longsword"] = 31; -NTIPAliasClassID["wsd"] = 32; NTIPAliasClassID["warsword"] = 32; -NTIPAliasClassID["2hs"] = 33; NTIPAliasClassID["twohandedsword"] = 33; -NTIPAliasClassID["clm"] = 34; NTIPAliasClassID["claymore"] = 34; -NTIPAliasClassID["gis"] = 35; NTIPAliasClassID["giantsword"] = 35; -NTIPAliasClassID["bsw"] = 36; NTIPAliasClassID["bastardsword"] = 36; -NTIPAliasClassID["flb"] = 37; NTIPAliasClassID["flamberge"] = 37; -NTIPAliasClassID["gsd"] = 38; NTIPAliasClassID["greatsword"] = 38; -NTIPAliasClassID["dgr"] = 39; NTIPAliasClassID["dagger"] = 39; -NTIPAliasClassID["dir"] = 40; NTIPAliasClassID["dirk"] = 40; -NTIPAliasClassID["kri"] = 41; NTIPAliasClassID["kris"] = 41; -NTIPAliasClassID["bld"] = 42; NTIPAliasClassID["blade"] = 42; -NTIPAliasClassID["tkf"] = 43; NTIPAliasClassID["throwingknife"] = 43; -NTIPAliasClassID["tax"] = 44; NTIPAliasClassID["throwingaxe"] = 44; -NTIPAliasClassID["bkf"] = 45; NTIPAliasClassID["balancedknife"] = 45; -NTIPAliasClassID["bal"] = 46; NTIPAliasClassID["balancedaxe"] = 46; -NTIPAliasClassID["jav"] = 47; NTIPAliasClassID["javelin"] = 47; -NTIPAliasClassID["pil"] = 48; NTIPAliasClassID["pilum"] = 48; -NTIPAliasClassID["ssp"] = 49; NTIPAliasClassID["shortspear"] = 49; -NTIPAliasClassID["glv"] = 50; NTIPAliasClassID["glaive"] = 50; -NTIPAliasClassID["tsp"] = 51; NTIPAliasClassID["throwingspear"] = 51; -NTIPAliasClassID["spr"] = 52; NTIPAliasClassID["spear"] = 52; -NTIPAliasClassID["tri"] = 53; NTIPAliasClassID["trident"] = 53; -NTIPAliasClassID["brn"] = 54; NTIPAliasClassID["brandistock"] = 54; -NTIPAliasClassID["spt"] = 55; NTIPAliasClassID["spetum"] = 55; -NTIPAliasClassID["pik"] = 56; NTIPAliasClassID["pike"] = 56; -NTIPAliasClassID["bar"] = 57; NTIPAliasClassID["bardiche"] = 57; -NTIPAliasClassID["vou"] = 58; NTIPAliasClassID["voulge"] = 58; -NTIPAliasClassID["scy"] = 59; NTIPAliasClassID["scythe"] = 59; -NTIPAliasClassID["pax"] = 60; NTIPAliasClassID["poleaxe"] = 60; -NTIPAliasClassID["hal"] = 61; NTIPAliasClassID["halberd"] = 61; -NTIPAliasClassID["wsc"] = 62; NTIPAliasClassID["warscythe"] = 62; -NTIPAliasClassID["sst"] = 63; NTIPAliasClassID["shortstaff"] = 63; -NTIPAliasClassID["lst"] = 64; NTIPAliasClassID["longstaff"] = 64; -NTIPAliasClassID["cst"] = 65; NTIPAliasClassID["gnarledstaff"] = 65; -NTIPAliasClassID["bst"] = 66; NTIPAliasClassID["battlestaff"] = 66; -NTIPAliasClassID["wst"] = 67; NTIPAliasClassID["warstaff"] = 67; -NTIPAliasClassID["sbw"] = 68; NTIPAliasClassID["shortbow"] = 68; -NTIPAliasClassID["hbw"] = 69; NTIPAliasClassID["hunter'sbow"] = 69; -NTIPAliasClassID["lbw"] = 70; NTIPAliasClassID["longbow"] = 70; -NTIPAliasClassID["cbw"] = 71; NTIPAliasClassID["compositebow"] = 71; -NTIPAliasClassID["sbb"] = 72; NTIPAliasClassID["shortbattlebow"] = 72; -NTIPAliasClassID["lbb"] = 73; NTIPAliasClassID["longbattlebow"] = 73; -NTIPAliasClassID["swb"] = 74; NTIPAliasClassID["shortwarbow"] = 74; -NTIPAliasClassID["lwb"] = 75; NTIPAliasClassID["longwarbow"] = 75; -NTIPAliasClassID["lxb"] = 76; NTIPAliasClassID["lightcrossbow"] = 76; -NTIPAliasClassID["mxb"] = 77; NTIPAliasClassID["crossbow"] = 77; -NTIPAliasClassID["hxb"] = 78; NTIPAliasClassID["heavycrossbow"] = 78; -NTIPAliasClassID["rxb"] = 79; NTIPAliasClassID["repeatingcrossbow"] = 79; -NTIPAliasClassID["gps"] = 80; NTIPAliasClassID["rancidgaspotion"] = 80; -NTIPAliasClassID["ops"] = 81; NTIPAliasClassID["oilpotion"] = 81; -NTIPAliasClassID["gpm"] = 82; NTIPAliasClassID["chokinggaspotion"] = 82; -NTIPAliasClassID["opm"] = 83; NTIPAliasClassID["explodingpotion"] = 83; -NTIPAliasClassID["gpl"] = 84; NTIPAliasClassID["stranglinggaspotion"] = 84; -NTIPAliasClassID["opl"] = 85; NTIPAliasClassID["fulminatingpotion"] = 85; -NTIPAliasClassID["d33"] = 86; NTIPAliasClassID["decoygidbinn"] = 86; -NTIPAliasClassID["g33"] = 87; NTIPAliasClassID["thegidbinn"] = 87; -NTIPAliasClassID["leg"] = 88; NTIPAliasClassID["wirt'sleg"] = 88; -NTIPAliasClassID["hdm"] = 89; NTIPAliasClassID["horadricmalus"] = 89; -NTIPAliasClassID["hfh"] = 90; NTIPAliasClassID["hellforgehammer"] = 90; -NTIPAliasClassID["hst"] = 91; NTIPAliasClassID["horadricstaff"] = 91; -NTIPAliasClassID["msf"] = 92; NTIPAliasClassID["shaftofthehoradricstaff"] = 92; -NTIPAliasClassID["9ha"] = 93; NTIPAliasClassID["hatchet"] = 93; -NTIPAliasClassID["9ax"] = 94; NTIPAliasClassID["cleaver"] = 94; -NTIPAliasClassID["92a"] = 95; NTIPAliasClassID["twinaxe"] = 95; -NTIPAliasClassID["9mp"] = 96; NTIPAliasClassID["crowbill"] = 96; -NTIPAliasClassID["9wa"] = 97; NTIPAliasClassID["naga"] = 97; -NTIPAliasClassID["9la"] = 98; NTIPAliasClassID["militaryaxe"] = 98; -NTIPAliasClassID["9ba"] = 99; NTIPAliasClassID["beardedaxe"] = 99; -NTIPAliasClassID["9bt"] = 100; NTIPAliasClassID["tabar"] = 100; -NTIPAliasClassID["9ga"] = 101; NTIPAliasClassID["gothicaxe"] = 101; -NTIPAliasClassID["9gi"] = 102; NTIPAliasClassID["ancientaxe"] = 102; -NTIPAliasClassID["9wn"] = 103; NTIPAliasClassID["burntwand"] = 103; -NTIPAliasClassID["9yw"] = 104; NTIPAliasClassID["petrifiedwand"] = 104; -NTIPAliasClassID["9bw"] = 105; NTIPAliasClassID["tombwand"] = 105; -NTIPAliasClassID["9gw"] = 106; NTIPAliasClassID["gravewand"] = 106; -NTIPAliasClassID["9cl"] = 107; NTIPAliasClassID["cudgel"] = 107; -NTIPAliasClassID["9sc"] = 108; NTIPAliasClassID["runescepter"] = 108; -NTIPAliasClassID["9qs"] = 109; NTIPAliasClassID["holywatersprinkler"] = 109; -NTIPAliasClassID["9ws"] = 110; NTIPAliasClassID["divinescepter"] = 110; -NTIPAliasClassID["9sp"] = 111; NTIPAliasClassID["barbedclub"] = 111; -NTIPAliasClassID["9ma"] = 112; NTIPAliasClassID["flangedmace"] = 112; -NTIPAliasClassID["9mt"] = 113; NTIPAliasClassID["jaggedstar"] = 113; -NTIPAliasClassID["9fl"] = 114; NTIPAliasClassID["knout"] = 114; -NTIPAliasClassID["9wh"] = 115; NTIPAliasClassID["battlehammer"] = 115; -NTIPAliasClassID["9m9"] = 116; NTIPAliasClassID["warclub"] = 116; -NTIPAliasClassID["9gm"] = 117; NTIPAliasClassID["marteldefer"] = 117; -NTIPAliasClassID["9ss"] = 118; NTIPAliasClassID["gladius"] = 118; -NTIPAliasClassID["9sm"] = 119; NTIPAliasClassID["cutlass"] = 119; -NTIPAliasClassID["9sb"] = 120; NTIPAliasClassID["shamshir"] = 120; -NTIPAliasClassID["9fc"] = 121; NTIPAliasClassID["tulwar"] = 121; -NTIPAliasClassID["9cr"] = 122; NTIPAliasClassID["dimensionalblade"] = 122; -NTIPAliasClassID["9bs"] = 123; NTIPAliasClassID["battlesword"] = 123; -NTIPAliasClassID["9ls"] = 124; NTIPAliasClassID["runesword"] = 124; -NTIPAliasClassID["9wd"] = 125; NTIPAliasClassID["ancientsword"] = 125; -NTIPAliasClassID["92h"] = 126; NTIPAliasClassID["espandon"] = 126; -NTIPAliasClassID["9cm"] = 127; NTIPAliasClassID["dacianfalx"] = 127; -NTIPAliasClassID["9gs"] = 128; NTIPAliasClassID["tusksword"] = 128; -NTIPAliasClassID["9b9"] = 129; NTIPAliasClassID["gothicsword"] = 129; -NTIPAliasClassID["9fb"] = 130; NTIPAliasClassID["zweihander"] = 130; -NTIPAliasClassID["9gd"] = 131; NTIPAliasClassID["executionersword"] = 131; -NTIPAliasClassID["9dg"] = 132; NTIPAliasClassID["poignard"] = 132; -NTIPAliasClassID["9di"] = 133; NTIPAliasClassID["rondel"] = 133; -NTIPAliasClassID["9kr"] = 134; NTIPAliasClassID["cinquedeas"] = 134; -NTIPAliasClassID["9bl"] = 135; NTIPAliasClassID["stiletto"] = 135; -NTIPAliasClassID["9tk"] = 136; NTIPAliasClassID["battledart"] = 136; -NTIPAliasClassID["9ta"] = 137; NTIPAliasClassID["francisca"] = 137; -NTIPAliasClassID["9bk"] = 138; NTIPAliasClassID["wardart"] = 138; -NTIPAliasClassID["9b8"] = 139; NTIPAliasClassID["hurlbat"] = 139; -NTIPAliasClassID["9ja"] = 140; NTIPAliasClassID["warjavelin"] = 140; -NTIPAliasClassID["9pi"] = 141; NTIPAliasClassID["greatpilum"] = 141; -NTIPAliasClassID["9s9"] = 142; NTIPAliasClassID["simbilan"] = 142; -NTIPAliasClassID["9gl"] = 143; NTIPAliasClassID["spiculum"] = 143; -NTIPAliasClassID["9ts"] = 144; NTIPAliasClassID["harpoon"] = 144; -NTIPAliasClassID["9sr"] = 145; NTIPAliasClassID["warspear"] = 145; -NTIPAliasClassID["9tr"] = 146; NTIPAliasClassID["fuscina"] = 146; -NTIPAliasClassID["9br"] = 147; NTIPAliasClassID["warfork"] = 147; -NTIPAliasClassID["9st"] = 148; NTIPAliasClassID["yari"] = 148; -NTIPAliasClassID["9p9"] = 149; NTIPAliasClassID["lance"] = 149; -NTIPAliasClassID["9b7"] = 150; NTIPAliasClassID["lochaberaxe"] = 150; -NTIPAliasClassID["9vo"] = 151; NTIPAliasClassID["bill"] = 151; -NTIPAliasClassID["9s8"] = 152; NTIPAliasClassID["battlescythe"] = 152; -NTIPAliasClassID["9pa"] = 153; NTIPAliasClassID["partizan"] = 153; -NTIPAliasClassID["9h9"] = 154; NTIPAliasClassID["becdecorbin"] = 154; -NTIPAliasClassID["9wc"] = 155; NTIPAliasClassID["grimscythe"] = 155; -NTIPAliasClassID["8ss"] = 156; NTIPAliasClassID["jostaff"] = 156; -NTIPAliasClassID["8ls"] = 157; NTIPAliasClassID["quarterstaff"] = 157; -NTIPAliasClassID["8cs"] = 158; NTIPAliasClassID["cedarstaff"] = 158; -NTIPAliasClassID["8bs"] = 159; NTIPAliasClassID["gothicstaff"] = 159; -NTIPAliasClassID["8ws"] = 160; NTIPAliasClassID["runestaff"] = 160; -NTIPAliasClassID["8sb"] = 161; NTIPAliasClassID["edgebow"] = 161; -NTIPAliasClassID["8hb"] = 162; NTIPAliasClassID["razorbow"] = 162; -NTIPAliasClassID["8lb"] = 163; NTIPAliasClassID["cedarbow"] = 163; -NTIPAliasClassID["8cb"] = 164; NTIPAliasClassID["doublebow"] = 164; -NTIPAliasClassID["8s8"] = 165; NTIPAliasClassID["shortsiegebow"] = 165; -NTIPAliasClassID["8l8"] = 166; NTIPAliasClassID["largesiegebow"] = 166; -NTIPAliasClassID["8sw"] = 167; NTIPAliasClassID["runebow"] = 167; -NTIPAliasClassID["8lw"] = 168; NTIPAliasClassID["gothicbow"] = 168; -NTIPAliasClassID["8lx"] = 169; NTIPAliasClassID["arbalest"] = 169; -NTIPAliasClassID["8mx"] = 170; NTIPAliasClassID["siegecrossbow"] = 170; -NTIPAliasClassID["8hx"] = 171; NTIPAliasClassID["ballista"] = 171; -NTIPAliasClassID["8rx"] = 172; NTIPAliasClassID["chukonu"] = 172; -NTIPAliasClassID["qf1"] = 173; NTIPAliasClassID["khalim'sflail"] = 173; -NTIPAliasClassID["qf2"] = 174; NTIPAliasClassID["khalim'swill"] = 174; -NTIPAliasClassID["ktr"] = 175; NTIPAliasClassID["katar"] = 175; -NTIPAliasClassID["wrb"] = 176; NTIPAliasClassID["wristblade"] = 176; -NTIPAliasClassID["axf"] = 177; NTIPAliasClassID["hatchethands"] = 177; -NTIPAliasClassID["ces"] = 178; NTIPAliasClassID["cestus"] = 178; -NTIPAliasClassID["clw"] = 179; NTIPAliasClassID["claws"] = 179; -NTIPAliasClassID["btl"] = 180; NTIPAliasClassID["bladetalons"] = 180; -NTIPAliasClassID["skr"] = 181; NTIPAliasClassID["scissorskatar"] = 181; -NTIPAliasClassID["9ar"] = 182; NTIPAliasClassID["quhab"] = 182; -NTIPAliasClassID["9wb"] = 183; NTIPAliasClassID["wristspike"] = 183; -NTIPAliasClassID["9xf"] = 184; NTIPAliasClassID["fascia"] = 184; -NTIPAliasClassID["9cs"] = 185; NTIPAliasClassID["handscythe"] = 185; -NTIPAliasClassID["9lw"] = 186; NTIPAliasClassID["greaterclaws"] = 186; -NTIPAliasClassID["9tw"] = 187; NTIPAliasClassID["greatertalons"] = 187; -NTIPAliasClassID["9qr"] = 188; NTIPAliasClassID["scissorsquhab"] = 188; -NTIPAliasClassID["7ar"] = 189; NTIPAliasClassID["suwayyah"] = 189; -NTIPAliasClassID["7wb"] = 190; NTIPAliasClassID["wristsword"] = 190; -NTIPAliasClassID["7xf"] = 191; NTIPAliasClassID["warfist"] = 191; -NTIPAliasClassID["7cs"] = 192; NTIPAliasClassID["battlecestus"] = 192; -NTIPAliasClassID["7lw"] = 193; NTIPAliasClassID["feralclaws"] = 193; -NTIPAliasClassID["7tw"] = 194; NTIPAliasClassID["runictalons"] = 194; -NTIPAliasClassID["7qr"] = 195; NTIPAliasClassID["scissorssuwayyah"] = 195; -NTIPAliasClassID["7ha"] = 196; NTIPAliasClassID["tomahawk"] = 196; -NTIPAliasClassID["7ax"] = 197; NTIPAliasClassID["smallcrescent"] = 197; -NTIPAliasClassID["72a"] = 198; NTIPAliasClassID["ettinaxe"] = 198; -NTIPAliasClassID["7mp"] = 199; NTIPAliasClassID["warspike"] = 199; -NTIPAliasClassID["7wa"] = 200; NTIPAliasClassID["berserkeraxe"] = 200; -NTIPAliasClassID["7la"] = 201; NTIPAliasClassID["feralaxe"] = 201; -NTIPAliasClassID["7ba"] = 202; NTIPAliasClassID["silveredgedaxe"] = 202; -NTIPAliasClassID["7bt"] = 203; NTIPAliasClassID["decapitator"] = 203; -NTIPAliasClassID["7ga"] = 204; NTIPAliasClassID["championaxe"] = 204; -NTIPAliasClassID["7gi"] = 205; NTIPAliasClassID["gloriousaxe"] = 205; -NTIPAliasClassID["7wn"] = 206; NTIPAliasClassID["polishedwand"] = 206; -NTIPAliasClassID["7yw"] = 207; NTIPAliasClassID["ghostwand"] = 207; -NTIPAliasClassID["7bw"] = 208; NTIPAliasClassID["lichwand"] = 208; -NTIPAliasClassID["7gw"] = 209; NTIPAliasClassID["unearthedwand"] = 209; -NTIPAliasClassID["7cl"] = 210; NTIPAliasClassID["truncheon"] = 210; -NTIPAliasClassID["7sc"] = 211; NTIPAliasClassID["mightyscepter"] = 211; -NTIPAliasClassID["7qs"] = 212; NTIPAliasClassID["seraphrod"] = 212; -NTIPAliasClassID["7ws"] = 213; NTIPAliasClassID["caduceus"] = 213; -NTIPAliasClassID["7sp"] = 214; NTIPAliasClassID["tyrantclub"] = 214; -NTIPAliasClassID["7ma"] = 215; NTIPAliasClassID["reinforcedmace"] = 215; -NTIPAliasClassID["7mt"] = 216; NTIPAliasClassID["devilstar"] = 216; -NTIPAliasClassID["7fl"] = 217; NTIPAliasClassID["scourge"] = 217; -NTIPAliasClassID["7wh"] = 218; NTIPAliasClassID["legendarymallet"] = 218; -NTIPAliasClassID["7m7"] = 219; NTIPAliasClassID["ogremaul"] = 219; -NTIPAliasClassID["7gm"] = 220; NTIPAliasClassID["thundermaul"] = 220; -NTIPAliasClassID["7ss"] = 221; NTIPAliasClassID["falcata"] = 221; -NTIPAliasClassID["7sm"] = 222; NTIPAliasClassID["ataghan"] = 222; -NTIPAliasClassID["7sb"] = 223; NTIPAliasClassID["elegantblade"] = 223; -NTIPAliasClassID["7fc"] = 224; NTIPAliasClassID["hydraedge"] = 224; -NTIPAliasClassID["7cr"] = 225; NTIPAliasClassID["phaseblade"] = 225; -NTIPAliasClassID["7bs"] = 226; NTIPAliasClassID["conquestsword"] = 226; -NTIPAliasClassID["7ls"] = 227; NTIPAliasClassID["crypticsword"] = 227; -NTIPAliasClassID["7wd"] = 228; NTIPAliasClassID["mythicalsword"] = 228; -NTIPAliasClassID["72h"] = 229; NTIPAliasClassID["legendsword"] = 229; -NTIPAliasClassID["7cm"] = 230; NTIPAliasClassID["highlandblade"] = 230; -NTIPAliasClassID["7gs"] = 231; NTIPAliasClassID["balrogblade"] = 231; -NTIPAliasClassID["7b7"] = 232; NTIPAliasClassID["championsword"] = 232; -NTIPAliasClassID["7fb"] = 233; NTIPAliasClassID["colossussword"] = 233; -NTIPAliasClassID["7gd"] = 234; NTIPAliasClassID["colossusblade"] = 234; -NTIPAliasClassID["7dg"] = 235; NTIPAliasClassID["boneknife"] = 235; -NTIPAliasClassID["7di"] = 236; NTIPAliasClassID["mithrilpoint"] = 236; -NTIPAliasClassID["7kr"] = 237; NTIPAliasClassID["fangedknife"] = 237; -NTIPAliasClassID["7bl"] = 238; NTIPAliasClassID["legendspike"] = 238; -NTIPAliasClassID["7tk"] = 239; NTIPAliasClassID["flyingknife"] = 239; -NTIPAliasClassID["7ta"] = 240; NTIPAliasClassID["flyingaxe"] = 240; -NTIPAliasClassID["7bk"] = 241; NTIPAliasClassID["wingedknife"] = 241; -NTIPAliasClassID["7b8"] = 242; NTIPAliasClassID["wingedaxe"] = 242; -NTIPAliasClassID["7ja"] = 243; NTIPAliasClassID["hyperionjavelin"] = 243; -NTIPAliasClassID["7pi"] = 244; NTIPAliasClassID["stygianpilum"] = 244; -NTIPAliasClassID["7s7"] = 245; NTIPAliasClassID["balrogspear"] = 245; -NTIPAliasClassID["7gl"] = 246; NTIPAliasClassID["ghostglaive"] = 246; -NTIPAliasClassID["7ts"] = 247; NTIPAliasClassID["wingedharpoon"] = 247; -NTIPAliasClassID["7sr"] = 248; NTIPAliasClassID["hyperionspear"] = 248; -NTIPAliasClassID["7tr"] = 249; NTIPAliasClassID["stygianpike"] = 249; -NTIPAliasClassID["7br"] = 250; NTIPAliasClassID["mancatcher"] = 250; -NTIPAliasClassID["7st"] = 251; NTIPAliasClassID["ghostspear"] = 251; -NTIPAliasClassID["7p7"] = 252; NTIPAliasClassID["warpike"] = 252; -NTIPAliasClassID["7o7"] = 253; NTIPAliasClassID["ogreaxe"] = 253; -NTIPAliasClassID["7vo"] = 254; NTIPAliasClassID["colossusvoulge"] = 254; -NTIPAliasClassID["7s8"] = 255; NTIPAliasClassID["thresher"] = 255; -NTIPAliasClassID["7pa"] = 256; NTIPAliasClassID["crypticaxe"] = 256; -NTIPAliasClassID["7h7"] = 257; NTIPAliasClassID["greatpoleaxe"] = 257; -NTIPAliasClassID["7wc"] = 258; NTIPAliasClassID["giantthresher"] = 258; -NTIPAliasClassID["6ss"] = 259; NTIPAliasClassID["walkingstick"] = 259; -NTIPAliasClassID["6ls"] = 260; NTIPAliasClassID["stalagmite"] = 260; -NTIPAliasClassID["6cs"] = 261; NTIPAliasClassID["elderstaff"] = 261; -NTIPAliasClassID["6bs"] = 262; NTIPAliasClassID["shillelagh"] = 262; -NTIPAliasClassID["6ws"] = 263; NTIPAliasClassID["archonstaff"] = 263; -NTIPAliasClassID["6sb"] = 264; NTIPAliasClassID["spiderbow"] = 264; -NTIPAliasClassID["6hb"] = 265; NTIPAliasClassID["bladebow"] = 265; -NTIPAliasClassID["6lb"] = 266; NTIPAliasClassID["shadowbow"] = 266; -NTIPAliasClassID["6cb"] = 267; NTIPAliasClassID["greatbow"] = 267; -NTIPAliasClassID["6s7"] = 268; NTIPAliasClassID["diamondbow"] = 268; -NTIPAliasClassID["6l7"] = 269; NTIPAliasClassID["crusaderbow"] = 269; -NTIPAliasClassID["6sw"] = 270; NTIPAliasClassID["wardbow"] = 270; -NTIPAliasClassID["6lw"] = 271; NTIPAliasClassID["hydrabow"] = 271; -NTIPAliasClassID["6lx"] = 272; NTIPAliasClassID["pelletbow"] = 272; -NTIPAliasClassID["6mx"] = 273; NTIPAliasClassID["gorgoncrossbow"] = 273; -NTIPAliasClassID["6hx"] = 274; NTIPAliasClassID["colossuscrossbow"] = 274; -NTIPAliasClassID["6rx"] = 275; NTIPAliasClassID["demoncrossbow"] = 275; -NTIPAliasClassID["ob1"] = 276; NTIPAliasClassID["eagleorb"] = 276; -NTIPAliasClassID["ob2"] = 277; NTIPAliasClassID["sacredglobe"] = 277; -NTIPAliasClassID["ob3"] = 278; NTIPAliasClassID["smokedsphere"] = 278; -NTIPAliasClassID["ob4"] = 279; NTIPAliasClassID["claspedorb"] = 279; -NTIPAliasClassID["ob5"] = 280; NTIPAliasClassID["jared'sstone"] = 280; -NTIPAliasClassID["am1"] = 281; NTIPAliasClassID["stagbow"] = 281; -NTIPAliasClassID["am2"] = 282; NTIPAliasClassID["reflexbow"] = 282; -NTIPAliasClassID["am3"] = 283; NTIPAliasClassID["maidenspear"] = 283; -NTIPAliasClassID["am4"] = 284; NTIPAliasClassID["maidenpike"] = 284; -NTIPAliasClassID["am5"] = 285; NTIPAliasClassID["maidenjavelin"] = 285; -NTIPAliasClassID["ob6"] = 286; NTIPAliasClassID["glowingorb"] = 286; -NTIPAliasClassID["ob7"] = 287; NTIPAliasClassID["crystallineglobe"] = 287; -NTIPAliasClassID["ob8"] = 288; NTIPAliasClassID["cloudysphere"] = 288; -NTIPAliasClassID["ob9"] = 289; NTIPAliasClassID["sparklingball"] = 289; -NTIPAliasClassID["oba"] = 290; NTIPAliasClassID["swirlingcrystal"] = 290; -NTIPAliasClassID["am6"] = 291; NTIPAliasClassID["ashwoodbow"] = 291; -NTIPAliasClassID["am7"] = 292; NTIPAliasClassID["ceremonialbow"] = 292; -NTIPAliasClassID["am8"] = 293; NTIPAliasClassID["ceremonialspear"] = 293; -NTIPAliasClassID["am9"] = 294; NTIPAliasClassID["ceremonialpike"] = 294; -NTIPAliasClassID["ama"] = 295; NTIPAliasClassID["ceremonialjavelin"] = 295; -NTIPAliasClassID["obb"] = 296; NTIPAliasClassID["heavenlystone"] = 296; -NTIPAliasClassID["obc"] = 297; NTIPAliasClassID["eldritchorb"] = 297; -NTIPAliasClassID["obd"] = 298; NTIPAliasClassID["demonheart"] = 298; -NTIPAliasClassID["obe"] = 299; NTIPAliasClassID["vortexorb"] = 299; -NTIPAliasClassID["obf"] = 300; NTIPAliasClassID["dimensionalshard"] = 300; -NTIPAliasClassID["amb"] = 301; NTIPAliasClassID["matriarchalbow"] = 301; -NTIPAliasClassID["amc"] = 302; NTIPAliasClassID["grandmatronbow"] = 302; -NTIPAliasClassID["amd"] = 303; NTIPAliasClassID["matriarchalspear"] = 303; -NTIPAliasClassID["ame"] = 304; NTIPAliasClassID["matriarchalpike"] = 304; -NTIPAliasClassID["amf"] = 305; NTIPAliasClassID["matriarchaljavelin"] = 305; -NTIPAliasClassID["cap"] = 306; -NTIPAliasClassID["skp"] = 307; NTIPAliasClassID["skullcap"] = 307; -NTIPAliasClassID["hlm"] = 308; NTIPAliasClassID["helm"] = 308; -NTIPAliasClassID["fhl"] = 309; NTIPAliasClassID["fullhelm"] = 309; -NTIPAliasClassID["ghm"] = 310; NTIPAliasClassID["greathelm"] = 310; -NTIPAliasClassID["crn"] = 311; NTIPAliasClassID["crown"] = 311; -NTIPAliasClassID["msk"] = 312; NTIPAliasClassID["mask"] = 312; -NTIPAliasClassID["qui"] = 313; NTIPAliasClassID["quiltedarmor"] = 313; -NTIPAliasClassID["lea"] = 314; NTIPAliasClassID["leatherarmor"] = 314; -NTIPAliasClassID["hla"] = 315; NTIPAliasClassID["hardleatherarmor"] = 315; -NTIPAliasClassID["stu"] = 316; NTIPAliasClassID["studdedleather"] = 316; -NTIPAliasClassID["rng"] = 317; NTIPAliasClassID["ringmail"] = 317; -NTIPAliasClassID["scl"] = 318; NTIPAliasClassID["scalemail"] = 318; -NTIPAliasClassID["chn"] = 319; NTIPAliasClassID["chainmail"] = 319; -NTIPAliasClassID["brs"] = 320; NTIPAliasClassID["breastplate"] = 320; -NTIPAliasClassID["spl"] = 321; NTIPAliasClassID["splintmail"] = 321; -NTIPAliasClassID["plt"] = 322; NTIPAliasClassID["platemail"] = 322; -NTIPAliasClassID["fld"] = 323; NTIPAliasClassID["fieldplate"] = 323; -NTIPAliasClassID["gth"] = 324; NTIPAliasClassID["gothicplate"] = 324; -NTIPAliasClassID["ful"] = 325; NTIPAliasClassID["fullplatemail"] = 325; -NTIPAliasClassID["aar"] = 326; NTIPAliasClassID["ancientarmor"] = 326; -NTIPAliasClassID["ltp"] = 327; NTIPAliasClassID["lightplate"] = 327; -NTIPAliasClassID["buc"] = 328; NTIPAliasClassID["buckler"] = 328; -NTIPAliasClassID["sml"] = 329; NTIPAliasClassID["smallshield"] = 329; -NTIPAliasClassID["lrg"] = 330; NTIPAliasClassID["largeshield"] = 330; -NTIPAliasClassID["kit"] = 331; NTIPAliasClassID["kiteshield"] = 331; -NTIPAliasClassID["tow"] = 332; NTIPAliasClassID["towershield"] = 332; -NTIPAliasClassID["gts"] = 333; NTIPAliasClassID["gothicshield"] = 333; -NTIPAliasClassID["lgl"] = 334; NTIPAliasClassID["leathergloves"] = 334; -NTIPAliasClassID["vgl"] = 335; NTIPAliasClassID["heavygloves"] = 335; -NTIPAliasClassID["mgl"] = 336; NTIPAliasClassID["chaingloves"] = 336; -NTIPAliasClassID["tgl"] = 337; NTIPAliasClassID["lightgauntlets"] = 337; -NTIPAliasClassID["hgl"] = 338; NTIPAliasClassID["gauntlets"] = 338; -NTIPAliasClassID["lbt"] = 339; NTIPAliasClassID["boots"] = 339; -NTIPAliasClassID["vbt"] = 340; NTIPAliasClassID["heavyboots"] = 340; -NTIPAliasClassID["mbt"] = 341; NTIPAliasClassID["chainboots"] = 341; -NTIPAliasClassID["tbt"] = 342; NTIPAliasClassID["lightplatedboots"] = 342; -NTIPAliasClassID["hbt"] = 343; NTIPAliasClassID["greaves"] = 343; -NTIPAliasClassID["lbl"] = 344; NTIPAliasClassID["sash"] = 344; -NTIPAliasClassID["vbl"] = 345; NTIPAliasClassID["lightbelt"] = 345; -NTIPAliasClassID["mbl"] = 346; NTIPAliasClassID["belt"] = 346; -NTIPAliasClassID["tbl"] = 347; NTIPAliasClassID["heavybelt"] = 347; -NTIPAliasClassID["hbl"] = 348; NTIPAliasClassID["platedbelt"] = 348; -NTIPAliasClassID["bhm"] = 349; NTIPAliasClassID["bonehelm"] = 349; -NTIPAliasClassID["bsh"] = 350; NTIPAliasClassID["boneshield"] = 350; -NTIPAliasClassID["spk"] = 351; NTIPAliasClassID["spikedshield"] = 351; -NTIPAliasClassID["xap"] = 352; NTIPAliasClassID["warhat"] = 352; -NTIPAliasClassID["xkp"] = 353; NTIPAliasClassID["sallet"] = 353; -NTIPAliasClassID["xlm"] = 354; NTIPAliasClassID["casque"] = 354; -NTIPAliasClassID["xhl"] = 355; NTIPAliasClassID["basinet"] = 355; -NTIPAliasClassID["xhm"] = 356; NTIPAliasClassID["wingedhelm"] = 356; -NTIPAliasClassID["xrn"] = 357; NTIPAliasClassID["grandcrown"] = 357; -NTIPAliasClassID["xsk"] = 358; NTIPAliasClassID["deathmask"] = 358; -NTIPAliasClassID["xui"] = 359; NTIPAliasClassID["ghostarmor"] = 359; -NTIPAliasClassID["xea"] = 360; NTIPAliasClassID["serpentskinarmor"] = 360; -NTIPAliasClassID["xla"] = 361; NTIPAliasClassID["demonhidearmor"] = 361; -NTIPAliasClassID["xtu"] = 362; NTIPAliasClassID["trellisedarmor"] = 362; -NTIPAliasClassID["xng"] = 363; NTIPAliasClassID["linkedmail"] = 363; -NTIPAliasClassID["xcl"] = 364; NTIPAliasClassID["tigulatedmail"] = 364; -NTIPAliasClassID["xhn"] = 365; NTIPAliasClassID["mesharmor"] = 365; -NTIPAliasClassID["xrs"] = 366; NTIPAliasClassID["cuirass"] = 366; -NTIPAliasClassID["xpl"] = 367; NTIPAliasClassID["russetarmor"] = 367; -NTIPAliasClassID["xlt"] = 368; NTIPAliasClassID["templarcoat"] = 368; -NTIPAliasClassID["xld"] = 369; NTIPAliasClassID["sharktootharmor"] = 369; -NTIPAliasClassID["xth"] = 370; NTIPAliasClassID["embossedplate"] = 370; -NTIPAliasClassID["xul"] = 371; NTIPAliasClassID["chaosarmor"] = 371; -NTIPAliasClassID["xar"] = 372; NTIPAliasClassID["ornateplate"] = 372; -NTIPAliasClassID["xtp"] = 373; NTIPAliasClassID["mageplate"] = 373; -NTIPAliasClassID["xuc"] = 374; NTIPAliasClassID["defender"] = 374; -NTIPAliasClassID["xml"] = 375; NTIPAliasClassID["roundshield"] = 375; -NTIPAliasClassID["xrg"] = 376; NTIPAliasClassID["scutum"] = 376; -NTIPAliasClassID["xit"] = 377; NTIPAliasClassID["dragonshield"] = 377; -NTIPAliasClassID["xow"] = 378; NTIPAliasClassID["pavise"] = 378; -NTIPAliasClassID["xts"] = 379; NTIPAliasClassID["ancientshield"] = 379; -NTIPAliasClassID["xlg"] = 380; NTIPAliasClassID["demonhidegloves"] = 380; -NTIPAliasClassID["xvg"] = 381; NTIPAliasClassID["sharkskingloves"] = 381; -NTIPAliasClassID["xmg"] = 382; NTIPAliasClassID["heavybracers"] = 382; -NTIPAliasClassID["xtg"] = 383; NTIPAliasClassID["battlegauntlets"] = 383; -NTIPAliasClassID["xhg"] = 384; NTIPAliasClassID["wargauntlets"] = 384; -NTIPAliasClassID["xlb"] = 385; NTIPAliasClassID["demonhideboots"] = 385; -NTIPAliasClassID["xvb"] = 386; NTIPAliasClassID["sharkskinboots"] = 386; -NTIPAliasClassID["xmb"] = 387; NTIPAliasClassID["meshboots"] = 387; -NTIPAliasClassID["xtb"] = 388; NTIPAliasClassID["battleboots"] = 388; -NTIPAliasClassID["xhb"] = 389; NTIPAliasClassID["warboots"] = 389; -NTIPAliasClassID["zlb"] = 390; NTIPAliasClassID["demonhidesash"] = 390; -NTIPAliasClassID["zvb"] = 391; NTIPAliasClassID["sharkskinbelt"] = 391; -NTIPAliasClassID["zmb"] = 392; NTIPAliasClassID["meshbelt"] = 392; -NTIPAliasClassID["ztb"] = 393; NTIPAliasClassID["battlebelt"] = 393; -NTIPAliasClassID["zhb"] = 394; NTIPAliasClassID["warbelt"] = 394; -NTIPAliasClassID["xh9"] = 395; NTIPAliasClassID["grimhelm"] = 395; -NTIPAliasClassID["xsh"] = 396; NTIPAliasClassID["grimshield"] = 396; -NTIPAliasClassID["xpk"] = 397; NTIPAliasClassID["barbedshield"] = 397; -NTIPAliasClassID["dr1"] = 398; NTIPAliasClassID["wolfhead"] = 398; -NTIPAliasClassID["dr2"] = 399; NTIPAliasClassID["hawkhelm"] = 399; -NTIPAliasClassID["dr3"] = 400; NTIPAliasClassID["antlers"] = 400; -NTIPAliasClassID["dr4"] = 401; NTIPAliasClassID["falconmask"] = 401; -NTIPAliasClassID["dr5"] = 402; NTIPAliasClassID["spiritmask"] = 402; -NTIPAliasClassID["ba1"] = 403; NTIPAliasClassID["jawbonecap"] = 403; -NTIPAliasClassID["ba2"] = 404; NTIPAliasClassID["fangedhelm"] = 404; -NTIPAliasClassID["ba3"] = 405; NTIPAliasClassID["hornedhelm"] = 405; -NTIPAliasClassID["ba4"] = 406; NTIPAliasClassID["assaulthelmet"] = 406; -NTIPAliasClassID["ba5"] = 407; NTIPAliasClassID["avengerguard"] = 407; -NTIPAliasClassID["pa1"] = 408; NTIPAliasClassID["targe"] = 408; -NTIPAliasClassID["pa2"] = 409; NTIPAliasClassID["rondache"] = 409; -NTIPAliasClassID["pa3"] = 410; NTIPAliasClassID["heraldicshield"] = 410; -NTIPAliasClassID["pa4"] = 411; NTIPAliasClassID["aerinshield"] = 411; -NTIPAliasClassID["pa5"] = 412; NTIPAliasClassID["crownshield"] = 412; -NTIPAliasClassID["ne1"] = 413; NTIPAliasClassID["preservedhead"] = 413; -NTIPAliasClassID["ne2"] = 414; NTIPAliasClassID["zombiehead"] = 414; -NTIPAliasClassID["ne3"] = 415; NTIPAliasClassID["unravellerhead"] = 415; -NTIPAliasClassID["ne4"] = 416; NTIPAliasClassID["gargoylehead"] = 416; -NTIPAliasClassID["ne5"] = 417; NTIPAliasClassID["demonhead"] = 417; -NTIPAliasClassID["ci0"] = 418; NTIPAliasClassID["circlet"] = 418; -NTIPAliasClassID["ci1"] = 419; NTIPAliasClassID["coronet"] = 419; -NTIPAliasClassID["ci2"] = 420; NTIPAliasClassID["tiara"] = 420; -NTIPAliasClassID["ci3"] = 421; NTIPAliasClassID["diadem"] = 421; -NTIPAliasClassID["uap"] = 422; NTIPAliasClassID["shako"] = 422; -NTIPAliasClassID["ukp"] = 423; NTIPAliasClassID["hydraskull"] = 423; -NTIPAliasClassID["ulm"] = 424; NTIPAliasClassID["armet"] = 424; -NTIPAliasClassID["uhl"] = 425; NTIPAliasClassID["giantconch"] = 425; -NTIPAliasClassID["uhm"] = 426; NTIPAliasClassID["spiredhelm"] = 426; -NTIPAliasClassID["urn"] = 427; NTIPAliasClassID["corona"] = 427; -NTIPAliasClassID["usk"] = 428; NTIPAliasClassID["demonhead"] = 428; -NTIPAliasClassID["uui"] = 429; NTIPAliasClassID["duskshroud"] = 429; -NTIPAliasClassID["uea"] = 430; NTIPAliasClassID["wyrmhide"] = 430; -NTIPAliasClassID["ula"] = 431; NTIPAliasClassID["scarabhusk"] = 431; -NTIPAliasClassID["utu"] = 432; NTIPAliasClassID["wirefleece"] = 432; -NTIPAliasClassID["ung"] = 433; NTIPAliasClassID["diamondmail"] = 433; -NTIPAliasClassID["ucl"] = 434; NTIPAliasClassID["loricatedmail"] = 434; -NTIPAliasClassID["uhn"] = 435; NTIPAliasClassID["boneweave"] = 435; -NTIPAliasClassID["urs"] = 436; NTIPAliasClassID["greathauberk"] = 436; -NTIPAliasClassID["upl"] = 437; NTIPAliasClassID["balrogskin"] = 437; -NTIPAliasClassID["ult"] = 438; NTIPAliasClassID["hellforgeplate"] = 438; -NTIPAliasClassID["uld"] = 439; NTIPAliasClassID["krakenshell"] = 439; -NTIPAliasClassID["uth"] = 440; NTIPAliasClassID["lacqueredplate"] = 440; -NTIPAliasClassID["uul"] = 441; NTIPAliasClassID["shadowplate"] = 441; -NTIPAliasClassID["uar"] = 442; NTIPAliasClassID["sacredarmor"] = 442; -NTIPAliasClassID["utp"] = 443; NTIPAliasClassID["archonplate"] = 443; -NTIPAliasClassID["uuc"] = 444; NTIPAliasClassID["heater"] = 444; -NTIPAliasClassID["uml"] = 445; NTIPAliasClassID["luna"] = 445; -NTIPAliasClassID["urg"] = 446; NTIPAliasClassID["hyperion"] = 446; -NTIPAliasClassID["uit"] = 447; NTIPAliasClassID["monarch"] = 447; -NTIPAliasClassID["uow"] = 448; NTIPAliasClassID["aegis"] = 448; -NTIPAliasClassID["uts"] = 449; NTIPAliasClassID["ward"] = 449; -NTIPAliasClassID["ulg"] = 450; NTIPAliasClassID["bramblemitts"] = 450; -NTIPAliasClassID["uvg"] = 451; NTIPAliasClassID["vampirebonegloves"] = 451; -NTIPAliasClassID["umg"] = 452; NTIPAliasClassID["vambraces"] = 452; -NTIPAliasClassID["utg"] = 453; NTIPAliasClassID["crusadergauntlets"] = 453; -NTIPAliasClassID["uhg"] = 454; NTIPAliasClassID["ogregauntlets"] = 454; -NTIPAliasClassID["ulb"] = 455; NTIPAliasClassID["wyrmhideboots"] = 455; -NTIPAliasClassID["uvb"] = 456; NTIPAliasClassID["scarabshellboots"] = 456; -NTIPAliasClassID["umb"] = 457; NTIPAliasClassID["boneweaveboots"] = 457; -NTIPAliasClassID["utb"] = 458; NTIPAliasClassID["mirroredboots"] = 458; -NTIPAliasClassID["uhb"] = 459; NTIPAliasClassID["myrmidongreaves"] = 459; -NTIPAliasClassID["ulc"] = 460; NTIPAliasClassID["spiderwebsash"] = 460; -NTIPAliasClassID["uvc"] = 461; NTIPAliasClassID["vampirefangbelt"] = 461; -NTIPAliasClassID["umc"] = 462; NTIPAliasClassID["mithrilcoil"] = 462; -NTIPAliasClassID["utc"] = 463; NTIPAliasClassID["trollbelt"] = 463; -NTIPAliasClassID["uhc"] = 464; NTIPAliasClassID["colossusgirdle"] = 464; -NTIPAliasClassID["uh9"] = 465; NTIPAliasClassID["bonevisage"] = 465; -NTIPAliasClassID["ush"] = 466; NTIPAliasClassID["trollnest"] = 466; -NTIPAliasClassID["upk"] = 467; NTIPAliasClassID["bladebarrier"] = 467; -NTIPAliasClassID["dr6"] = 468; NTIPAliasClassID["alphahelm"] = 468; -NTIPAliasClassID["dr7"] = 469; NTIPAliasClassID["griffonheaddress"] = 469; -NTIPAliasClassID["dr8"] = 470; NTIPAliasClassID["hunter'sguise"] = 470; -NTIPAliasClassID["dr9"] = 471; NTIPAliasClassID["sacredfeathers"] = 471; -NTIPAliasClassID["dra"] = 472; NTIPAliasClassID["totemicmask"] = 472; -NTIPAliasClassID["ba6"] = 473; NTIPAliasClassID["jawbonevisor"] = 473; -NTIPAliasClassID["ba7"] = 474; NTIPAliasClassID["lionhelm"] = 474; -NTIPAliasClassID["ba8"] = 475; NTIPAliasClassID["ragemask"] = 475; -NTIPAliasClassID["ba9"] = 476; NTIPAliasClassID["savagehelmet"] = 476; -NTIPAliasClassID["baa"] = 477; NTIPAliasClassID["slayerguard"] = 477; -NTIPAliasClassID["pa6"] = 478; NTIPAliasClassID["akarantarge"] = 478; -NTIPAliasClassID["pa7"] = 479; NTIPAliasClassID["akaranrondache"] = 479; -NTIPAliasClassID["pa8"] = 480; NTIPAliasClassID["protectorshield"] = 480; -NTIPAliasClassID["pa9"] = 481; NTIPAliasClassID["gildedshield"] = 481; -NTIPAliasClassID["paa"] = 482; NTIPAliasClassID["royalshield"] = 482; -NTIPAliasClassID["ne6"] = 483; NTIPAliasClassID["mummifiedtrophy"] = 483; -NTIPAliasClassID["ne7"] = 484; NTIPAliasClassID["fetishtrophy"] = 484; -NTIPAliasClassID["ne8"] = 485; NTIPAliasClassID["sextontrophy"] = 485; -NTIPAliasClassID["ne9"] = 486; NTIPAliasClassID["cantortrophy"] = 486; -NTIPAliasClassID["nea"] = 487; NTIPAliasClassID["hierophanttrophy"] = 487; -NTIPAliasClassID["drb"] = 488; NTIPAliasClassID["bloodspirit"] = 488; -NTIPAliasClassID["drc"] = 489; NTIPAliasClassID["sunspirit"] = 489; -NTIPAliasClassID["drd"] = 490; NTIPAliasClassID["earthspirit"] = 490; -NTIPAliasClassID["dre"] = 491; NTIPAliasClassID["skyspirit"] = 491; -NTIPAliasClassID["drf"] = 492; NTIPAliasClassID["dreamspirit"] = 492; -NTIPAliasClassID["bab"] = 493; NTIPAliasClassID["carnagehelm"] = 493; -NTIPAliasClassID["bac"] = 494; NTIPAliasClassID["furyvisor"] = 494; -NTIPAliasClassID["bad"] = 495; NTIPAliasClassID["destroyerhelm"] = 495; -NTIPAliasClassID["bae"] = 496; NTIPAliasClassID["conquerorcrown"] = 496; -NTIPAliasClassID["baf"] = 497; NTIPAliasClassID["guardiancrown"] = 497; -NTIPAliasClassID["pab"] = 498; NTIPAliasClassID["sacredtarge"] = 498; -NTIPAliasClassID["pac"] = 499; NTIPAliasClassID["sacredrondache"] = 499; -NTIPAliasClassID["pad"] = 500; NTIPAliasClassID["kurastshield"] = 500; -NTIPAliasClassID["pae"] = 501; NTIPAliasClassID["zakarumshield"] = 501; -NTIPAliasClassID["paf"] = 502; NTIPAliasClassID["vortexshield"] = 502; -NTIPAliasClassID["neb"] = 503; NTIPAliasClassID["minionskull"] = 503; -NTIPAliasClassID["neg"] = 504; NTIPAliasClassID["hellspawnskull"] = 504; -NTIPAliasClassID["ned"] = 505; NTIPAliasClassID["overseerskull"] = 505; -NTIPAliasClassID["nee"] = 506; NTIPAliasClassID["succubusskull"] = 506; -NTIPAliasClassID["nef"] = 507; NTIPAliasClassID["bloodlordskull"] = 507; -NTIPAliasClassID["elx"] = 508; NTIPAliasClassID["elixir"] = 508; -NTIPAliasClassID["hpo"] = 509; -NTIPAliasClassID["mpo"] = 510; -NTIPAliasClassID["hpf"] = 511; -NTIPAliasClassID["mpf"] = 512; -NTIPAliasClassID["vps"] = 513; NTIPAliasClassID["staminapotion"] = 513; -NTIPAliasClassID["yps"] = 514; NTIPAliasClassID["antidotepotion"] = 514; -NTIPAliasClassID["rvs"] = 515; NTIPAliasClassID["rejuvenationpotion"] = 515; -NTIPAliasClassID["rvl"] = 516; NTIPAliasClassID["fullrejuvenationpotion"] = 516; -NTIPAliasClassID["wms"] = 517; NTIPAliasClassID["thawingpotion"] = 517; -NTIPAliasClassID["tbk"] = 518; NTIPAliasClassID["tomeoftownportal"] = 518; -NTIPAliasClassID["ibk"] = 519; NTIPAliasClassID["tomeofidentify"] = 519; -NTIPAliasClassID["amu"] = 520; NTIPAliasClassID["amulet"] = 520; -NTIPAliasClassID["vip"] = 521; NTIPAliasClassID["topofthehoradricstaff"] = 521; -NTIPAliasClassID["rin"] = 522; NTIPAliasClassID["ring"] = 522; -NTIPAliasClassID["gld"] = 523; NTIPAliasClassID["gold"] = 523; -NTIPAliasClassID["bks"] = 524; NTIPAliasClassID["scrollofinifuss"] = 524; -NTIPAliasClassID["bkd"] = 525; NTIPAliasClassID["keytothecairnstones"] = 525; -NTIPAliasClassID["aqv"] = 526; NTIPAliasClassID["arrows"] = 526; -NTIPAliasClassID["tch"] = 527; NTIPAliasClassID["torch"] = 527; -NTIPAliasClassID["cqv"] = 528; NTIPAliasClassID["bolts"] = 528; -NTIPAliasClassID["tsc"] = 529; NTIPAliasClassID["scrolloftownportal"] = 529; -NTIPAliasClassID["isc"] = 530; NTIPAliasClassID["scrollofidentify"] = 530; -NTIPAliasClassID["hrt"] = 531; NTIPAliasClassID["heart"] = 531; -NTIPAliasClassID["brz"] = 532; NTIPAliasClassID["brain"] = 532; -NTIPAliasClassID["jaw"] = 533; NTIPAliasClassID["jawbone"] = 533; -NTIPAliasClassID["eyz"] = 534; NTIPAliasClassID["eye"] = 534; -NTIPAliasClassID["hrn"] = 535; NTIPAliasClassID["horn"] = 535; -NTIPAliasClassID["tal"] = 536; NTIPAliasClassID["tail"] = 536; -NTIPAliasClassID["flg"] = 537; NTIPAliasClassID["flag"] = 537; -NTIPAliasClassID["fng"] = 538; NTIPAliasClassID["fang"] = 538; -NTIPAliasClassID["qll"] = 539; NTIPAliasClassID["quill"] = 539; -NTIPAliasClassID["sol"] = 540; NTIPAliasClassID["soul"] = 540; -NTIPAliasClassID["scz"] = 541; NTIPAliasClassID["scalp"] = 541; -NTIPAliasClassID["spe"] = 542; NTIPAliasClassID["spleen"] = 542; -NTIPAliasClassID["key"] = 543; -NTIPAliasClassID["luv"] = 544; NTIPAliasClassID["theblacktowerkey"] = 544; -NTIPAliasClassID["xyz"] = 545; NTIPAliasClassID["potionoflife"] = 545; -NTIPAliasClassID["j34"] = 546; NTIPAliasClassID["ajadefigurine"] = 546; -NTIPAliasClassID["g34"] = 547; NTIPAliasClassID["thegoldenbird"] = 547; -NTIPAliasClassID["bbb"] = 548; NTIPAliasClassID["lamesen'stome"] = 548; -NTIPAliasClassID["box"] = 549; NTIPAliasClassID["horadriccube"] = 549; -NTIPAliasClassID["tr1"] = 550; NTIPAliasClassID["horadricscroll"] = 550; -NTIPAliasClassID["mss"] = 551; NTIPAliasClassID["mephisto'ssoulstone"] = 551; -NTIPAliasClassID["ass"] = 552; NTIPAliasClassID["bookofskill"] = 552; -NTIPAliasClassID["qey"] = 553; NTIPAliasClassID["khalim'seye"] = 553; -NTIPAliasClassID["qhr"] = 554; NTIPAliasClassID["khalim'sheart"] = 554; -NTIPAliasClassID["qbr"] = 555; NTIPAliasClassID["khalim'sbrain"] = 555; -NTIPAliasClassID["ear"] = 556; -NTIPAliasClassID["gcv"] = 557; NTIPAliasClassID["chippedamethyst"] = 557; -NTIPAliasClassID["gfv"] = 558; NTIPAliasClassID["flawedamethyst"] = 558; -NTIPAliasClassID["gsv"] = 559; NTIPAliasClassID["amethyst"] = 559; -NTIPAliasClassID["gzv"] = 560; NTIPAliasClassID["flawlessamethyst"] = 560; -NTIPAliasClassID["gpv"] = 561; NTIPAliasClassID["perfectamethyst"] = 561; -NTIPAliasClassID["gcy"] = 562; NTIPAliasClassID["chippedtopaz"] = 562; -NTIPAliasClassID["gfy"] = 563; NTIPAliasClassID["flawedtopaz"] = 563; -NTIPAliasClassID["gsy"] = 564; NTIPAliasClassID["topaz"] = 564; -NTIPAliasClassID["gly"] = 565; NTIPAliasClassID["flawlesstopaz"] = 565; -NTIPAliasClassID["gpy"] = 566; NTIPAliasClassID["perfecttopaz"] = 566; -NTIPAliasClassID["gcb"] = 567; NTIPAliasClassID["chippedsapphire"] = 567; -NTIPAliasClassID["gfb"] = 568; NTIPAliasClassID["flawedsapphire"] = 568; -NTIPAliasClassID["gsb"] = 569; NTIPAliasClassID["sapphire"] = 569; -NTIPAliasClassID["glb"] = 570; NTIPAliasClassID["flawlesssapphire"] = 570; -NTIPAliasClassID["gpb"] = 571; NTIPAliasClassID["perfectsapphire"] = 571; -NTIPAliasClassID["gcg"] = 572; NTIPAliasClassID["chippedemerald"] = 572; -NTIPAliasClassID["gfg"] = 573; NTIPAliasClassID["flawedemerald"] = 573; -NTIPAliasClassID["gsg"] = 574; NTIPAliasClassID["emerald"] = 574; -NTIPAliasClassID["glg"] = 575; NTIPAliasClassID["flawlessemerald"] = 575; -NTIPAliasClassID["gpg"] = 576; NTIPAliasClassID["perfectemerald"] = 576; -NTIPAliasClassID["gcr"] = 577; NTIPAliasClassID["chippedruby"] = 577; -NTIPAliasClassID["gfr"] = 578; NTIPAliasClassID["flawedruby"] = 578; -NTIPAliasClassID["gsr"] = 579; NTIPAliasClassID["ruby"] = 579; -NTIPAliasClassID["glr"] = 580; NTIPAliasClassID["flawlessruby"] = 580; -NTIPAliasClassID["gpr"] = 581; NTIPAliasClassID["perfectruby"] = 581; -NTIPAliasClassID["gcw"] = 582; NTIPAliasClassID["chippeddiamond"] = 582; -NTIPAliasClassID["gfw"] = 583; NTIPAliasClassID["flaweddiamond"] = 583; -NTIPAliasClassID["gsw"] = 584; NTIPAliasClassID["diamond"] = 584; -NTIPAliasClassID["glw"] = 585; NTIPAliasClassID["flawlessdiamond"] = 585; -NTIPAliasClassID["gpw"] = 586; NTIPAliasClassID["perfectdiamond"] = 586; -NTIPAliasClassID["hp1"] = 587; NTIPAliasClassID["minorhealingpotion"] = 587; -NTIPAliasClassID["hp2"] = 588; NTIPAliasClassID["lighthealingpotion"] = 588; -NTIPAliasClassID["hp3"] = 589; NTIPAliasClassID["healingpotion"] = 589; -NTIPAliasClassID["hp4"] = 590; NTIPAliasClassID["greaterhealingpotion"] = 590; -NTIPAliasClassID["hp5"] = 591; NTIPAliasClassID["superhealingpotion"] = 591; -NTIPAliasClassID["mp1"] = 592; NTIPAliasClassID["minormanapotion"] = 592; -NTIPAliasClassID["mp2"] = 593; NTIPAliasClassID["lightmanapotion"] = 593; -NTIPAliasClassID["mp3"] = 594; NTIPAliasClassID["manapotion"] = 594; -NTIPAliasClassID["mp4"] = 595; NTIPAliasClassID["greatermanapotion"] = 595; -NTIPAliasClassID["mp5"] = 596; NTIPAliasClassID["supermanapotion"] = 596; -NTIPAliasClassID["skc"] = 597; NTIPAliasClassID["chippedskull"] = 597; -NTIPAliasClassID["skf"] = 598; NTIPAliasClassID["flawedskull"] = 598; -NTIPAliasClassID["sku"] = 599; NTIPAliasClassID["skull"] = 599; -NTIPAliasClassID["skl"] = 600; NTIPAliasClassID["flawlessskull"] = 600; -NTIPAliasClassID["skz"] = 601; NTIPAliasClassID["perfectskull"] = 601; -NTIPAliasClassID["hrb"] = 602; NTIPAliasClassID["herb"] = 602; -NTIPAliasClassID["cm1"] = 603; NTIPAliasClassID["smallcharm"] = 603; -NTIPAliasClassID["cm2"] = 604; NTIPAliasClassID["largecharm"] = 604; -NTIPAliasClassID["cm3"] = 605; NTIPAliasClassID["grandcharm"] = 605; -NTIPAliasClassID["rps"] = 606; -NTIPAliasClassID["rpl"] = 607; -NTIPAliasClassID["bps"] = 608; -NTIPAliasClassID["bpl"] = 609; -NTIPAliasClassID["r01"] = 610; NTIPAliasClassID["elrune"] = 610; -NTIPAliasClassID["r02"] = 611; NTIPAliasClassID["eldrune"] = 611; -NTIPAliasClassID["r03"] = 612; NTIPAliasClassID["tirrune"] = 612; -NTIPAliasClassID["r04"] = 613; NTIPAliasClassID["nefrune"] = 613; -NTIPAliasClassID["r05"] = 614; NTIPAliasClassID["ethrune"] = 614; -NTIPAliasClassID["r06"] = 615; NTIPAliasClassID["ithrune"] = 615; -NTIPAliasClassID["r07"] = 616; NTIPAliasClassID["talrune"] = 616; -NTIPAliasClassID["r08"] = 617; NTIPAliasClassID["ralrune"] = 617; -NTIPAliasClassID["r09"] = 618; NTIPAliasClassID["ortrune"] = 618; -NTIPAliasClassID["r10"] = 619; NTIPAliasClassID["thulrune"] = 619; -NTIPAliasClassID["r11"] = 620; NTIPAliasClassID["amnrune"] = 620; -NTIPAliasClassID["r12"] = 621; NTIPAliasClassID["solrune"] = 621; -NTIPAliasClassID["r13"] = 622; NTIPAliasClassID["shaelrune"] = 622; -NTIPAliasClassID["r14"] = 623; NTIPAliasClassID["dolrune"] = 623; -NTIPAliasClassID["r15"] = 624; NTIPAliasClassID["helrune"] = 624; -NTIPAliasClassID["r16"] = 625; NTIPAliasClassID["iorune"] = 625; -NTIPAliasClassID["r17"] = 626; NTIPAliasClassID["lumrune"] = 626; -NTIPAliasClassID["r18"] = 627; NTIPAliasClassID["korune"] = 627; -NTIPAliasClassID["r19"] = 628; NTIPAliasClassID["falrune"] = 628; -NTIPAliasClassID["r20"] = 629; NTIPAliasClassID["lemrune"] = 629; -NTIPAliasClassID["r21"] = 630; NTIPAliasClassID["pulrune"] = 630; -NTIPAliasClassID["r22"] = 631; NTIPAliasClassID["umrune"] = 631; -NTIPAliasClassID["r23"] = 632; NTIPAliasClassID["malrune"] = 632; -NTIPAliasClassID["r24"] = 633; NTIPAliasClassID["istrune"] = 633; -NTIPAliasClassID["r25"] = 634; NTIPAliasClassID["gulrune"] = 634; -NTIPAliasClassID["r26"] = 635; NTIPAliasClassID["vexrune"] = 635; -NTIPAliasClassID["r27"] = 636; NTIPAliasClassID["ohmrune"] = 636; -NTIPAliasClassID["r28"] = 637; NTIPAliasClassID["lorune"] = 637; -NTIPAliasClassID["r29"] = 638; NTIPAliasClassID["surrune"] = 638; -NTIPAliasClassID["r30"] = 639; NTIPAliasClassID["berrune"] = 639; -NTIPAliasClassID["r31"] = 640; NTIPAliasClassID["jahrune"] = 640; -NTIPAliasClassID["r32"] = 641; NTIPAliasClassID["chamrune"] = 641; -NTIPAliasClassID["r33"] = 642; NTIPAliasClassID["zodrune"] = 642; -NTIPAliasClassID["jew"] = 643; NTIPAliasClassID["jewel"] = 643; -NTIPAliasClassID["ice"] = 644; NTIPAliasClassID["malah'spotion"] = 644; -NTIPAliasClassID["0sc"] = 645; NTIPAliasClassID["scrollofknowledge"] = 645; -NTIPAliasClassID["tr2"] = 646; NTIPAliasClassID["scrollofresistance"] = 646; -NTIPAliasClassID["pk1"] = 647; NTIPAliasClassID["keyofterror"] = 647; -NTIPAliasClassID["pk2"] = 648; NTIPAliasClassID["keyofhate"] = 648; -NTIPAliasClassID["pk3"] = 649; NTIPAliasClassID["keyofdestruction"] = 649; -NTIPAliasClassID["dhn"] = 650; NTIPAliasClassID["diablo'shorn"] = 650; -NTIPAliasClassID["bey"] = 651; NTIPAliasClassID["baal'seye"] = 651; -NTIPAliasClassID["mbr"] = 652; NTIPAliasClassID["mephisto'sbrain"] = 652; -NTIPAliasClassID["toa"] = 653; NTIPAliasClassID["tokenofabsolution"] = 653; -NTIPAliasClassID["tes"] = 654; NTIPAliasClassID["twistedessenceofsuffering"] = 654; -NTIPAliasClassID["ceh"] = 655; NTIPAliasClassID["chargedessenceofhatred"] = 655; -NTIPAliasClassID["bet"] = 656; NTIPAliasClassID["burningessenceofterror"] = 656; -NTIPAliasClassID["fed"] = 657; NTIPAliasClassID["festeringessenceofdestruction"] = 657; -NTIPAliasClassID["std"] = 658; NTIPAliasClassID["standardofheroes"] = 658; - -var NTIPAliasClass = {}; -NTIPAliasClass["normal"] = 0; -NTIPAliasClass["exceptional"] = 1; -NTIPAliasClass["elite"] = 2; - -var NTIPAliasQuality = {}; -NTIPAliasQuality["lowquality"] = 1; -NTIPAliasQuality["normal"] = 2; -NTIPAliasQuality["superior"] = 3; -NTIPAliasQuality["magic"] = 4; -NTIPAliasQuality["set"] = 5; -NTIPAliasQuality["rare"] = 6; -NTIPAliasQuality["unique"] = 7; -NTIPAliasQuality["crafted"] = 8; - -var NTIPAliasFlag = {}; -NTIPAliasFlag["identified"] = 0x10; -NTIPAliasFlag["eth"] = 0x400000; NTIPAliasFlag["ethereal"] = 0x400000; -NTIPAliasFlag["runeword"] = 0x4000000; - -// rare item colors -var NTIPAliasColor = {}; -NTIPAliasColor["black"] = 3; -NTIPAliasColor["white"] = 20; -NTIPAliasColor["orange"] = 19; -NTIPAliasColor["lightyellow"] = 13; -NTIPAliasColor["lightred"] = 7; -NTIPAliasColor["lightgold"] = 15; -NTIPAliasColor["lightblue"] = 4; -NTIPAliasColor["lightpurple"] = 17; -NTIPAliasColor["crystalblue"] = 6; -NTIPAliasColor["crystalred"] = 9; -NTIPAliasColor["crystalgreen"] = 12; -NTIPAliasColor["darkyellow"] = 14; -NTIPAliasColor["darkred"] = 8; -NTIPAliasColor["darkgold"] = 16; -NTIPAliasColor["darkgreen"] = 11; -NTIPAliasColor["darkblue"] = 5; - -var NTIPAliasStat = {}; -NTIPAliasStat["strength"] = 0; -NTIPAliasStat["energy"] = 1; -NTIPAliasStat["dexterity"] = 2; -NTIPAliasStat["vitality"] = 3; -NTIPAliasStat["statpts"] = 4; -NTIPAliasStat["newskills"] = 5; -NTIPAliasStat["hitpoints"] = 6; -NTIPAliasStat["maxhp"] = 7; -NTIPAliasStat["mana"] = 8; -NTIPAliasStat["maxmana"] = 9; -NTIPAliasStat["stamina"] = 10; -NTIPAliasStat["maxstamina"] = 11; -NTIPAliasStat["level"] = 12; -NTIPAliasStat["experience"] = 13; -NTIPAliasStat["gold"] = 14; -NTIPAliasStat["goldbank"] = 15; -NTIPAliasStat["itemarmorpercent"] = [16,0]; NTIPAliasStat["enhanceddefense"] = [16,0]; -NTIPAliasStat["itemmaxdamagepercent"] = [17,0]; -NTIPAliasStat["itemmindamagepercent"] = [18,0]; NTIPAliasStat["enhanceddamage"] = [18,0]; -NTIPAliasStat["tohit"] = 19; -NTIPAliasStat["toblock"] = 20; -NTIPAliasStat["plusmindamage"] = [21, 1]; -NTIPAliasStat["mindamage"] = 21; -NTIPAliasStat["plusmaxdamage"] = [22, 1]; -NTIPAliasStat["maxdamage"] = 22; -NTIPAliasStat["secondarymindamage"] = 23; -NTIPAliasStat["secondarymaxdamage"] = 24; -NTIPAliasStat["damagepercent"] = 25; -NTIPAliasStat["manarecovery"] = 26; -NTIPAliasStat["manarecoverybonus"] = 27; -NTIPAliasStat["staminarecoverybonus"] = 28; -NTIPAliasStat["lastexp"] = 29; -NTIPAliasStat["nextexp"] = 30; - -NTIPAliasStat["armorclass"] = 31; NTIPAliasStat["defense"] = 31; -NTIPAliasStat["plusdefense"] = [31,0]; - -NTIPAliasStat["armorclassvsmissile"] = 32; -NTIPAliasStat["armorclassvshth"] = 33; -NTIPAliasStat["normaldamagereduction"] = 34; -NTIPAliasStat["magicdamagereduction"] = 35; -NTIPAliasStat["damageresist"] = 36; -NTIPAliasStat["magicresist"] = 37; -NTIPAliasStat["maxmagicresist"] = 38; -NTIPAliasStat["fireresist"] = 39; -NTIPAliasStat["maxfireresist"] = 40; -NTIPAliasStat["lightresist"] = 41; -NTIPAliasStat["maxlightresist"] = 42; -NTIPAliasStat["coldresist"] = 43; -NTIPAliasStat["maxcoldresist"] = 44; -NTIPAliasStat["poisonresist"] = 45; -NTIPAliasStat["maxpoisonresist"] = 46; -NTIPAliasStat["damageaura"] = 47; -NTIPAliasStat["firemindam"] = 48; -NTIPAliasStat["firemaxdam"] = 49; -NTIPAliasStat["lightmindam"] = 50; -NTIPAliasStat["lightmaxdam"] = 51; -NTIPAliasStat["magicmindam"] = 52; -NTIPAliasStat["magicmaxdam"] = 53; -NTIPAliasStat["coldmindam"] = 54; -NTIPAliasStat["coldmaxdam"] = 55; -NTIPAliasStat["coldlength"] = 56; -NTIPAliasStat["poisondamage"] = [57, 1]; -NTIPAliasStat["poisonmindam"] = 57; -NTIPAliasStat["poisonmaxdam"] = 58; -NTIPAliasStat["poisonlength"] = 59; -NTIPAliasStat["lifedrainmindam"] = 60; NTIPAliasStat["lifeleech"] = 60; -NTIPAliasStat["lifedrainmaxdam"] = 61; -NTIPAliasStat["manadrainmindam"] = 62; NTIPAliasStat["manaleech"] = 62; -NTIPAliasStat["manadrainmaxdam"] = 63; -NTIPAliasStat["stamdrainmindam"] = 64; -NTIPAliasStat["stamdrainmaxdam"] = 65; -NTIPAliasStat["stunlength"] = 66; -NTIPAliasStat["velocitypercent"] = 67; -NTIPAliasStat["attackrate"] = 68; -NTIPAliasStat["otheranimrate"] = 69; -NTIPAliasStat["quantity"] = 70; -NTIPAliasStat["value"] = 71; -NTIPAliasStat["durability"] = 72; -NTIPAliasStat["maxdurability"] = 73; -NTIPAliasStat["hpregen"] = 74; -NTIPAliasStat["itemmaxdurabilitypercent"] = 75; -NTIPAliasStat["itemmaxhppercent"] = 76; -NTIPAliasStat["itemmaxmanapercent"] = 77; -NTIPAliasStat["itemattackertakesdamage"] = 78; -NTIPAliasStat["itemgoldbonus"] = 79; -NTIPAliasStat["itemmagicbonus"] = 80; -NTIPAliasStat["itemknockback"] = 81; -NTIPAliasStat["itemtimeduration"] = 82; - -NTIPAliasStat["itemaddclassskills"] = 83; -NTIPAliasStat["itemaddamazonskills"] = [83,0]; NTIPAliasStat["amazonskills"] = [83,0]; -NTIPAliasStat["itemaddsorceressskills"] = [83,1]; NTIPAliasStat["sorceressskills"] = [83,1]; -NTIPAliasStat["itemaddnecromancerskills"] = [83,2]; NTIPAliasStat["necromancerskills"] = [83,2]; -NTIPAliasStat["itemaddpaladinskills"] = [83,3]; NTIPAliasStat["paladinskills"] = [83,3]; -NTIPAliasStat["itemaddbarbarianskills"] = [83,4]; NTIPAliasStat["barbarianskills"] = [83,4]; -NTIPAliasStat["itemadddruidskills"] = [83,5]; NTIPAliasStat["druidskills"] = [83,5]; -NTIPAliasStat["itemaddassassinskills"] = [83,6]; NTIPAliasStat["assassinskills"] = [83,6]; - -NTIPAliasStat["unsentparam1"] = 84; -NTIPAliasStat["itemaddexperience"] = 85; -NTIPAliasStat["itemhealafterkill"] = 86; -NTIPAliasStat["itemreducedprices"] = 87; -NTIPAliasStat["itemdoubleherbduration"] = 88; -NTIPAliasStat["itemlightradius"] = 89; -NTIPAliasStat["itemlightcolor"] = 90; -NTIPAliasStat["itemreqpercent"] = 91; -NTIPAliasStat["itemlevelreq"] = 92; -NTIPAliasStat["itemfasterattackrate"] = 93; NTIPAliasStat["ias"] = 93; -NTIPAliasStat["itemlevelreqpct"] = 94; -NTIPAliasStat["lastblockframe"] = 95; -NTIPAliasStat["itemfastermovevelocity"] = 96; NTIPAliasStat["frw"] = 96; - -// oskill -NTIPAliasStat["itemnonclassskill"] = 97; -// Amazon -NTIPAliasStat["plusskillcriticalstrike"] = [97,9]; -NTIPAliasStat["plusskillguidedarrow"] = [97,22]; -// Sorceress -NTIPAliasStat["plusskillteleport"] = [97,54]; -// Barbarian -NTIPAliasStat["plusskillbattleorders"] = [97,149]; -NTIPAliasStat["plusskillbattlecommand"] = [97,155]; -NTIPAliasStat["plusskillbattlecry"] = [97,146]; -// Druid -NTIPAliasStat["plusskillwerewolf"] = [97,223]; -NTIPAliasStat["plusskillshapeshifting"] = [97,224]; NTIPAliasStat["plusskilllycanthropy"] = [97,224]; -NTIPAliasStat["plusskillsummonspiritwolf"] = [97,227]; -NTIPAliasStat["plusskillferalrage"] = [97,232]; - -NTIPAliasStat["state"] = 98; -NTIPAliasStat["itemfastergethitrate"] = 99; NTIPAliasStat["fhr"] = 99; -NTIPAliasStat["monsterplayercount"] = 100; -NTIPAliasStat["skillpoisonoverridelength"] = 101; -NTIPAliasStat["itemfasterblockrate"] = 102; NTIPAliasStat["fbr"] = 102; -NTIPAliasStat["skillbypassundead"] = 103; -NTIPAliasStat["skillbypassdemons"] = 104; -NTIPAliasStat["itemfastercastrate"] = 105; NTIPAliasStat["fcr"] = 105; -NTIPAliasStat["skillbypassbeasts"] = 106; - -NTIPAliasStat["itemsingleskill"] = 107; -// Amazon skills -NTIPAliasStat["skillmagicarrow"] = [107,6]; -NTIPAliasStat["skillfirearrow"] = [107,7]; -NTIPAliasStat["skillinnersight"] = [107,8]; -NTIPAliasStat["skillcriticalstrike"] = [107,9]; -NTIPAliasStat["skilljab"] = [107,10]; -NTIPAliasStat["skillcoldarrow"] = [107,11]; -NTIPAliasStat["skillmultipleshot"] = [107,12]; -NTIPAliasStat["skilldodge"] = [107,13]; -NTIPAliasStat["skillpowerstrike"] = [107,14]; -NTIPAliasStat["skillpoisonjavelin"] = [107,15]; -NTIPAliasStat["skillexplodingarrow"] = [107,16]; -NTIPAliasStat["skillslowmissiles"] = [107,17]; -NTIPAliasStat["skillavoid"] = [107,18]; -NTIPAliasStat["skillimpale"] = [107,19]; -NTIPAliasStat["skilllightningbolt"] = [107,20]; -NTIPAliasStat["skillicearrow"] = [107,21]; -NTIPAliasStat["skillguidedarrow"] = [107,22]; -NTIPAliasStat["skillpenetrate"] = [107,23]; -NTIPAliasStat["skillchargedstrike"] = [107,24]; -NTIPAliasStat["skillplaguejavelin"] = [107,25]; -NTIPAliasStat["skillstrafe"] = [107,26]; -NTIPAliasStat["skillimmolationarrow"] = [107,27]; -NTIPAliasStat["skilldecoy"] = [107,28]; -NTIPAliasStat["skillevade"] = [107,29]; -NTIPAliasStat["skillfend"] = [107,30]; -NTIPAliasStat["skillfreezingarrow"] = [107,31]; -NTIPAliasStat["skillvalkyrie"] = [107,32]; -NTIPAliasStat["skillpierce"] = [107,33]; -NTIPAliasStat["skilllightningstrike"] = [107,34]; -NTIPAliasStat["skilllightningfury"] = [107,35]; -// Sorceress skills -NTIPAliasStat["skillfirebolt"] = [107,36]; -NTIPAliasStat["skillwarmth"] = [107,37]; -NTIPAliasStat["skillchargedbolt"] = [107,38]; -NTIPAliasStat["skillicebolt"] = [107,39]; -NTIPAliasStat["skillfrozenarmor"] = [107,40]; -NTIPAliasStat["skillinferno"] = [107,41]; -NTIPAliasStat["skillstaticfield"] = [107,42]; -NTIPAliasStat["skilltelekinesis"] = [107,43]; -NTIPAliasStat["skillfrostnova"] = [107,44]; -NTIPAliasStat["skilliceblast"] = [107,45]; -NTIPAliasStat["skillblaze"] = [107,46]; -NTIPAliasStat["skillfireball"] = [107,47]; -NTIPAliasStat["skillnova"] = [107,48]; -NTIPAliasStat["skilllightning"] = [107,49]; -NTIPAliasStat["skillshiverarmor"] = [107,50]; -NTIPAliasStat["skillfirewall"] = [107,51]; -NTIPAliasStat["skillenchant"] = [107,52]; -NTIPAliasStat["skillchainlightning"] = [107,53]; -NTIPAliasStat["skillteleport"] = [107,54]; -NTIPAliasStat["skillglacialspike"] = [107,55]; -NTIPAliasStat["skillmeteor"] = [107,56]; -NTIPAliasStat["skillthunderstorm"] = [107,57]; -NTIPAliasStat["skillenergyshield"] = [107,58]; -NTIPAliasStat["skillblizzard"] = [107,59]; -NTIPAliasStat["skillchillingarmor"] = [107,60]; -NTIPAliasStat["skillfiremastery"] = [107,61]; -NTIPAliasStat["skillhydra"] = [107,62]; -NTIPAliasStat["skilllightningmastery"] = [107,63]; -NTIPAliasStat["skillfrozenorb"] = [107,64]; -NTIPAliasStat["skillcoldmastery"] = [107,65]; -// Necromancer skills -NTIPAliasStat["skillamplifydamage"] = [107,66]; -NTIPAliasStat["skillteeth"] = [107,67]; -NTIPAliasStat["skillbonearmor"] = [107,68]; -NTIPAliasStat["skillskeletonmastery"] = [107,69]; -NTIPAliasStat["skillraiseskeleton"] = [107,70]; -NTIPAliasStat["skilldimvision"] = [107,71]; -NTIPAliasStat["skillweaken"] = [107,72]; -NTIPAliasStat["skillpoisondagger"] = [107,73]; -NTIPAliasStat["skillcorpseexplosion"] = [107,74]; -NTIPAliasStat["skillclaygolem"] = [107,75]; -NTIPAliasStat["skillironmaiden"] = [107,76]; -NTIPAliasStat["skillterror"] = [107,77]; -NTIPAliasStat["skillbonewall"] = [107,78]; -NTIPAliasStat["skillgolemmastery"] = [107,79]; -NTIPAliasStat["skillskeletalmage"] = [107,80]; -NTIPAliasStat["skillconfuse"] = [107,81]; -NTIPAliasStat["skilllifetap"] = [107,82]; -NTIPAliasStat["skillpoisonexplosion"] = [107,83]; -NTIPAliasStat["skillbonespear"] = [107,84]; -NTIPAliasStat["skillbloodgolem"] = [107,85]; -NTIPAliasStat["skillattract"] = [107,86]; -NTIPAliasStat["skilldecrepify"] = [107,87]; -NTIPAliasStat["skillboneprison"] = [107,88]; -NTIPAliasStat["skillsummonresist"] = [107,89]; -NTIPAliasStat["skillirongolem"] = [107,90]; -NTIPAliasStat["skilllowerresist"] = [107,91]; -NTIPAliasStat["skillpoisonnova"] = [107,92]; -NTIPAliasStat["skillbonespirit"] = [107,93]; -NTIPAliasStat["skillfiregolem"] = [107,94]; -NTIPAliasStat["skillrevive"] = [107,95]; -// Paladin skills -NTIPAliasStat["skillsacrifice"] = [107,96]; -NTIPAliasStat["skillsmite"] = [107,97]; -NTIPAliasStat["skillmight"] = [107,98]; -NTIPAliasStat["skillprayer"] = [107,99]; -NTIPAliasStat["skillresistfire"] = [107,100]; -NTIPAliasStat["skillholybolt"] = [107,101]; -NTIPAliasStat["skillholyfire"] = [107,102]; -NTIPAliasStat["skillthorns"] = [107,103]; -NTIPAliasStat["skilldefiance"] = [107,104]; -NTIPAliasStat["skillresistcold"] = [107,105]; -NTIPAliasStat["skillzeal"] = [107,106]; -NTIPAliasStat["skillcharge"] = [107,107]; -NTIPAliasStat["skillblessedaim"] = [107,108]; -NTIPAliasStat["skillcleansing"] = [107,109]; -NTIPAliasStat["skillresistlightning"] = [107,110]; -NTIPAliasStat["skillvengeance"] = [107,111]; -NTIPAliasStat["skillblessedhammer"] = [107,112]; -NTIPAliasStat["skillconcentration"] = [107,113]; -NTIPAliasStat["skillholyfreeze"] = [107,114]; -NTIPAliasStat["skillvigor"] = [107,115]; -NTIPAliasStat["skillconversion"] = [107,116]; -NTIPAliasStat["skillholyshield"] = [107,117]; -NTIPAliasStat["skillholyshock"] = [107,118]; -NTIPAliasStat["skillsanctuary"] = [107,119]; -NTIPAliasStat["skillmeditation"] = [107,120]; -NTIPAliasStat["skillfistoftheheavens"] = [107,121]; -NTIPAliasStat["skillfanaticism"] = [107,122]; -NTIPAliasStat["skillconviction"] = [107,123]; -NTIPAliasStat["skillredemption"] = [107,124]; -NTIPAliasStat["skillsalvation"] = [107,125]; -// Barbarian skills -NTIPAliasStat["skillbash"] = [107,126]; -NTIPAliasStat["skillswordmastery"] = [107,127]; -NTIPAliasStat["skillaxemastery"] = [107,128]; -NTIPAliasStat["skillmacemastery"] = [107,129]; -NTIPAliasStat["skillhowl"] = [107,130]; -NTIPAliasStat["skillfindpotion"] = [107,131]; -NTIPAliasStat["skillleap"] = [107,132]; -NTIPAliasStat["skilldoubleswing"] = [107,133]; -NTIPAliasStat["skillpolearmmastery"] = [107,134]; -NTIPAliasStat["skillthrowingmastery"] = [107,135]; -NTIPAliasStat["skillspearmastery"] = [107,136]; -NTIPAliasStat["skilltaunt"] = [107,137]; -NTIPAliasStat["skillshout"] = [107,138]; -NTIPAliasStat["skillstun"] = [107,139]; -NTIPAliasStat["skilldoublethrow"] = [107,140]; -NTIPAliasStat["skillincreasedstamina"] = [107,141]; -NTIPAliasStat["skillfinditem"] = [107,142]; -NTIPAliasStat["skillleapattack"] = [107,143]; -NTIPAliasStat["skillconcentrate"] = [107,144]; -NTIPAliasStat["skillironskin"] = [107,145]; -NTIPAliasStat["skillbattlecry"] = [107,146]; -NTIPAliasStat["skillfrenzy"] = [107,147]; -NTIPAliasStat["skillincreasedspeed"] = [107,148]; -NTIPAliasStat["skillbattleorders"] = [107,149]; -NTIPAliasStat["skillgrimward"] = [107,150]; -NTIPAliasStat["skillwhirlwind"] = [107,151]; -NTIPAliasStat["skillberserk"] = [107,152]; -NTIPAliasStat["skillnaturalresistance"] = [107,153]; -NTIPAliasStat["skillwarcry"] = [107,154]; -NTIPAliasStat["skillbattlecommand"] = [107,155]; -// Druid skills -NTIPAliasStat["skillraven"] = [107,221]; -NTIPAliasStat["skillpoisoncreeper"] = [107,222]; -NTIPAliasStat["skillwerewolf"] = [107,223]; -NTIPAliasStat["skilllycanthropy"] = [107,224]; -NTIPAliasStat["skillfirestorm"] = [107,225]; -NTIPAliasStat["skilloaksage"] = [107,226]; -NTIPAliasStat["skillsummonspiritwolf"] = [107,227]; -NTIPAliasStat["skillwerebear"] = [107,228]; -NTIPAliasStat["skillmoltenboulder"] = [107,229]; -NTIPAliasStat["skillarcticblast"] = [107,230]; -NTIPAliasStat["skillcarrionvine"] = [107,231]; -NTIPAliasStat["skillferalrage"] = [107,232]; -NTIPAliasStat["skillmaul"] = [107,233]; -NTIPAliasStat["skillfissure"] = [107,234]; -NTIPAliasStat["skillcyclonearmor"] = [107,235]; -NTIPAliasStat["skillheartofwolverine"] = [107,236]; -NTIPAliasStat["skillsummondirewolf"] = [107,237]; -NTIPAliasStat["skillrabies"] = [107,238]; -NTIPAliasStat["skillfireclaws"] = [107,239]; -NTIPAliasStat["skilltwister"] = [107,240]; -NTIPAliasStat["skillsolarcreeper"] = [107,241]; -NTIPAliasStat["skillhunger"] = [107,242]; -NTIPAliasStat["skillshockwave"] = [107,243]; -NTIPAliasStat["skillvolcano"] = [107,244]; -NTIPAliasStat["skilltornado"] = [107,245]; -NTIPAliasStat["skillspiritofbarbs"] = [107,246]; -NTIPAliasStat["skillsummongrizzly"] = [107,247]; -NTIPAliasStat["skillfury"] = [107,248]; -NTIPAliasStat["skillarmageddon"] = [107,249]; -NTIPAliasStat["skillhurricane"] = [107,250]; -// Assassin skills -NTIPAliasStat["skillfireblast"] = [107,251]; -NTIPAliasStat["skillclawmastery"] = [107,252]; -NTIPAliasStat["skillpsychichammer"] = [107,253]; -NTIPAliasStat["skilltigerstrike"] = [107,254]; -NTIPAliasStat["skilldragontalon"] = [107,255]; -NTIPAliasStat["skillshockweb"] = [107,256]; -NTIPAliasStat["skillbladesentinel"] = [107,257]; -NTIPAliasStat["skillburstofspeed"] = [107,258]; -NTIPAliasStat["skillfistsoffire"] = [107,259]; -NTIPAliasStat["skilldragonclaw"] = [107,260]; -NTIPAliasStat["skillchargedboltsentry"] = [107,261]; -NTIPAliasStat["skillwakeoffire"] = [107,262]; -NTIPAliasStat["skillweaponblock"] = [107,263]; -NTIPAliasStat["skillcloakofshadows"] = [107,264]; -NTIPAliasStat["skillcobrastrike"] = [107,265]; -NTIPAliasStat["skillbladefury"] = [107,266]; -NTIPAliasStat["skillfade"] = [107,267]; -NTIPAliasStat["skillshadowwarrior"] = [107,268]; -NTIPAliasStat["skillclawsofthunder"] = [107,269]; -NTIPAliasStat["skilldragontail"] = [107,270]; -NTIPAliasStat["skilllightningsentry"] = [107,271]; -NTIPAliasStat["skillwakeofinferno"] = [107,272]; -NTIPAliasStat["skillmindblast"] = [107,273]; -NTIPAliasStat["skillbladesofice"] = [107,274]; -NTIPAliasStat["skilldragonflight"] = [107,275]; -NTIPAliasStat["skilldeathsentry"] = [107,276]; -NTIPAliasStat["skillbladeshield"] = [107,277]; -NTIPAliasStat["skillvenom"] = [107,278]; -NTIPAliasStat["skillshadowmaster"] = [107,279]; -NTIPAliasStat["skillphoenixstrike"] = [107,280]; - -NTIPAliasStat["itemrestinpeace"] = 108; -NTIPAliasStat["curseresistance"] = 109; -NTIPAliasStat["itempoisonlengthresist"] = 110; -NTIPAliasStat["itemnormaldamage"] = 111; -NTIPAliasStat["itemhowl"] = 112; -NTIPAliasStat["itemstupidity"] = 113; -NTIPAliasStat["itemdamagetomana"] = 114; -NTIPAliasStat["itemignoretargetac"] = 115; -NTIPAliasStat["itemfractionaltargetac"] = 116; -NTIPAliasStat["itempreventheal"] = 117; -NTIPAliasStat["itemhalffreezeduration"] = 118; -NTIPAliasStat["itemtohitpercent"] = 119; -NTIPAliasStat["itemdamagetargetac"] = 120; -NTIPAliasStat["itemdemondamagepercent"] = 121; -NTIPAliasStat["itemundeaddamagepercent"] = 122; -NTIPAliasStat["itemdemontohit"] = 123; -NTIPAliasStat["itemundeadtohit"] = 124; -NTIPAliasStat["itemthrowable"] = 125; -NTIPAliasStat["itemelemskill"] = 126; -NTIPAliasStat["itemallskills"] = 127; -NTIPAliasStat["itemattackertakeslightdamage"] = 128; -NTIPAliasStat["ironmaidenlevel"] = 129; -NTIPAliasStat["lifetaplevel"] = 130; -NTIPAliasStat["thornspercent"] = 131; -NTIPAliasStat["bonearmor"] = 132; -NTIPAliasStat["bonearmormax"] = 133; -NTIPAliasStat["itemfreeze"] = 134; -NTIPAliasStat["itemopenwounds"] = 135; -NTIPAliasStat["itemcrushingblow"] = 136; -NTIPAliasStat["itemkickdamage"] = 137; -NTIPAliasStat["itemmanaafterkill"] = 138; -NTIPAliasStat["itemhealafterdemonkill"] = 139; -NTIPAliasStat["itemextrablood"] = 140; -NTIPAliasStat["itemdeadlystrike"] = 141; -NTIPAliasStat["itemabsorbfirepercent"] = 142; -NTIPAliasStat["itemabsorbfire"] = 143; -NTIPAliasStat["itemabsorblightpercent"] = 144; -NTIPAliasStat["itemabsorblight"] = 145; -NTIPAliasStat["itemabsorbmagicpercent"] = 146; -NTIPAliasStat["itemabsorbmagic"] = 147; -NTIPAliasStat["itemabsorbcoldpercent"] = 148; -NTIPAliasStat["itemabsorbcold"] = 149; -NTIPAliasStat["itemslow"] = 150; - -NTIPAliasStat["itemaura"] = 151; -NTIPAliasStat["mightaura"] = [151,98]; -NTIPAliasStat["holyfireaura"] = [151,102]; -NTIPAliasStat["thornsaura"] = [151,103]; -NTIPAliasStat["defianceaura"] = [151,104]; -NTIPAliasStat["concentrationaura"] = [151,113]; -NTIPAliasStat["holyfreezeaura"] = [151,114]; -NTIPAliasStat["vigoraura"] = [151,115]; -NTIPAliasStat["holyshockaura"] = [151,118]; -NTIPAliasStat["sanctuaryaura"] = [151,119]; -NTIPAliasStat["meditationaura"] = [151,120]; -NTIPAliasStat["fanaticismaura"] = [151,122]; -NTIPAliasStat["convictionaura"] = [151,123]; -NTIPAliasStat["redemptionaura"] = [151,124]; - -NTIPAliasStat["itemindestructible"] = 152; -NTIPAliasStat["itemcannotbefrozen"] = 153; -NTIPAliasStat["itemstaminadrainpct"] = 154; -NTIPAliasStat["itemreanimate"] = 155; -NTIPAliasStat["itempierce"] = 156; -NTIPAliasStat["itemmagicarrow"] = 157; -NTIPAliasStat["itemexplosivearrow"] = 158; -NTIPAliasStat["itemthrowmindamage"] = 159; -NTIPAliasStat["itemthrowmaxdamage"] = 160; -NTIPAliasStat["itemskillhandofathena"] = 161; -NTIPAliasStat["itemskillstaminapercent"] = 162; -NTIPAliasStat["itemskillpassivestaminapercent"] = 163; -NTIPAliasStat["itemskillconcentration"] = 164; -NTIPAliasStat["itemskillenchant"] = 165; -NTIPAliasStat["itemskillpierce"] = 166; -NTIPAliasStat["itemskillconviction"] = 167; -NTIPAliasStat["itemskillchillingarmor"] = 168; -NTIPAliasStat["itemskillfrenzy"] = 169; -NTIPAliasStat["itemskilldecrepify"] = 170; -NTIPAliasStat["itemskillarmorpercent"] = 171; -NTIPAliasStat["alignment"] = 172; -NTIPAliasStat["target0"] = 173; -NTIPAliasStat["target1"] = 174; -NTIPAliasStat["goldlost"] = 175; -NTIPAliasStat["conversionlevel"] = 176; -NTIPAliasStat["conversionmaxhp"] = 177; -NTIPAliasStat["unitdooverlay"] = 178; -NTIPAliasStat["attackvsmontype"] = 179; -NTIPAliasStat["damagevsmontype"] = 180; -NTIPAliasStat["fade"] = 181; -NTIPAliasStat["armoroverridepercent"] = 182; -NTIPAliasStat["unused183"] = 183; -NTIPAliasStat["unused184"] = 184; -NTIPAliasStat["unused185"] = 185; -NTIPAliasStat["unused186"] = 186; -NTIPAliasStat["unused187"] = 187; - -NTIPAliasStat["itemaddskilltab"] = 188; -NTIPAliasStat["itemaddbowandcrossbowskilltab"] = [188,0]; NTIPAliasStat["bowandcrossbowskilltab"] = [188,0]; -NTIPAliasStat["itemaddpassiveandmagicskilltab"] = [188,1]; NTIPAliasStat["passiveandmagicskilltab"] = [188,1]; -NTIPAliasStat["itemaddjavelinandspearskilltab"] = [188,2]; NTIPAliasStat["javelinandspearskilltab"] = [188,2]; -NTIPAliasStat["itemaddfireskilltab"] = [188,8]; NTIPAliasStat["fireskilltab"] = [188,8]; -NTIPAliasStat["itemaddlightningskilltab"] = [188,9]; NTIPAliasStat["lightningskilltab"] = [188,9]; -NTIPAliasStat["itemaddcoldskilltab"] = [188,10]; NTIPAliasStat["coldskilltab"] = [188,10]; -NTIPAliasStat["itemaddcursesskilltab"] = [188,16]; NTIPAliasStat["cursesskilltab"] = [188,16]; -NTIPAliasStat["itemaddpoisonandboneskilltab"] = [188,17]; NTIPAliasStat["poisonandboneskilltab"] = [188,17]; -NTIPAliasStat["itemaddnecromancersummoningskilltab"] = [188,18]; NTIPAliasStat["necromancersummoningskilltab"] = [188,18]; -NTIPAliasStat["itemaddpalicombatskilltab"] = [188,24]; NTIPAliasStat["palicombatskilltab"] = [188,24]; -NTIPAliasStat["itemaddoffensiveaurasskilltab"] = [188,25]; NTIPAliasStat["offensiveaurasskilltab"] = [188,25]; -NTIPAliasStat["itemadddefensiveaurasskilltab"] = [188,26]; NTIPAliasStat["defensiveaurasskilltab"] = [188,26]; -NTIPAliasStat["itemaddbarbcombatskilltab"] = [188,32]; NTIPAliasStat["barbcombatskilltab"] = [188,32]; -NTIPAliasStat["itemaddmasteriesskilltab"] = [188,33]; NTIPAliasStat["masteriesskilltab"] = [188,33]; -NTIPAliasStat["itemaddwarcriesskilltab"] = [188,34]; NTIPAliasStat["warcriesskilltab"] = [188,34]; -NTIPAliasStat["itemadddruidsummoningskilltab"] = [188,40]; NTIPAliasStat["druidsummoningskilltab"] = [188,40]; -NTIPAliasStat["itemaddshapeshiftingskilltab"] = [188,41]; NTIPAliasStat["shapeshiftingskilltab"] = [188,41]; -NTIPAliasStat["itemaddelementalskilltab"] = [188,42]; NTIPAliasStat["elementalskilltab"] = [188,42]; -NTIPAliasStat["itemaddtrapsskilltab"] = [188,48]; NTIPAliasStat["trapsskilltab"] = [188,48]; -NTIPAliasStat["itemaddshadowdisciplinesskilltab"] = [188,49]; NTIPAliasStat["shadowdisciplinesskilltab"] = [188,49]; -NTIPAliasStat["itemaddmartialartsskilltab"] = [188,50]; NTIPAliasStat["martialartsskilltab"] = [188,50]; - -NTIPAliasStat["unused189"] = 189; -NTIPAliasStat["unused190"] = 190; -NTIPAliasStat["unused191"] = 191; -NTIPAliasStat["unused192"] = 192; -NTIPAliasStat["unused193"] = 193; -NTIPAliasStat["itemnumsockets"] = 194; NTIPAliasStat["sockets"] = 194; -NTIPAliasStat["itemskillonattack"] = [195, 1]; -NTIPAliasStat["itemskillonattacklevel"] = [195, 2]; -NTIPAliasStat["itemskillonkill"] = [196, 1]; -NTIPAliasStat["itemskillonkilllevel"] = [196, 2]; -NTIPAliasStat["itemskillondeath"] = [197, 1]; -NTIPAliasStat["itemskillondeathlevel"] = [197, 2]; - -NTIPAliasStat["itemskillonhit"] = [198, 1]; -NTIPAliasStat["itemskillonhitlevel"] = [198, 2]; -NTIPAliasStat["amplifydamageonhit"] = [198,4225]; - -NTIPAliasStat["itemskillonlevelup"] = [199, 1]; -NTIPAliasStat["itemskillonleveluplevel"] = [199, 2]; -NTIPAliasStat["unused200"] = 200; -NTIPAliasStat["itemskillongethit"] = [201, 1]; -NTIPAliasStat["itemskillongethitlevel"] = [201, 2]; -NTIPAliasStat["unused202"] = 202; -NTIPAliasStat["unused203"] = 203; - -NTIPAliasStat["itemchargedskill"] = [204, 1]; -NTIPAliasStat["itemchargedskilllevel"] = [204, 2]; -NTIPAliasStat["teleportcharges"] = [204,3461]; - -NTIPAliasStat["unused204"] = 205; -NTIPAliasStat["unused205"] = 206; -NTIPAliasStat["unused206"] = 207; -NTIPAliasStat["unused207"] = 208; -NTIPAliasStat["unused208"] = 209; -NTIPAliasStat["unused209"] = 210; -NTIPAliasStat["unused210"] = 211; -NTIPAliasStat["unused211"] = 212; -NTIPAliasStat["unused212"] = 213; -NTIPAliasStat["itemarmorperlevel"] = 214; -NTIPAliasStat["itemarmorpercentperlevel"] = 215; -NTIPAliasStat["itemhpperlevel"] = 216; -NTIPAliasStat["itemmanaperlevel"] = 217; -NTIPAliasStat["itemmaxdamageperlevel"] = 218; -NTIPAliasStat["itemmaxdamagepercentperlevel"] = 219; -NTIPAliasStat["itemstrengthperlevel"] = 220; -NTIPAliasStat["itemdexterityperlevel"] = 221; -NTIPAliasStat["itemenergyperlevel"] = 222; -NTIPAliasStat["itemvitalityperlevel"] = 223; -NTIPAliasStat["itemtohitperlevel"] = 224; -NTIPAliasStat["itemtohitpercentperlevel"] = 225; -NTIPAliasStat["itemcolddamagemaxperlevel"] = 226; -NTIPAliasStat["itemfiredamagemaxperlevel"] = 227; -NTIPAliasStat["itemltngdamagemaxperlevel"] = 228; -NTIPAliasStat["itempoisdamagemaxperlevel"] = 229; -NTIPAliasStat["itemresistcoldperlevel"] = 230; -NTIPAliasStat["itemresistfireperlevel"] = 231; -NTIPAliasStat["itemresistltngperlevel"] = 232; -NTIPAliasStat["itemresistpoisperlevel"] = 233; -NTIPAliasStat["itemabsorbcoldperlevel"] = 234; -NTIPAliasStat["itemabsorbfireperlevel"] = 235; -NTIPAliasStat["itemabsorbltngperlevel"] = 236; -NTIPAliasStat["itemabsorbpoisperlevel"] = 237; -NTIPAliasStat["itemthornsperlevel"] = 238; -NTIPAliasStat["itemfindgoldperlevel"] = 239; -NTIPAliasStat["itemfindmagicperlevel"] = 240; -NTIPAliasStat["itemregenstaminaperlevel"] = 241; -NTIPAliasStat["itemstaminaperlevel"] = 242; -NTIPAliasStat["itemdamagedemonperlevel"] = 243; -NTIPAliasStat["itemdamageundeadperlevel"] = 244; -NTIPAliasStat["itemtohitdemonperlevel"] = 245; -NTIPAliasStat["itemtohitundeadperlevel"] = 246; -NTIPAliasStat["itemcrushingblowperlevel"] = 247; -NTIPAliasStat["itemopenwoundsperlevel"] = 248; -NTIPAliasStat["itemkickdamageperlevel"] = 249; -NTIPAliasStat["itemdeadlystrikeperlevel"] = 250; -NTIPAliasStat["itemfindgemsperlevel"] = 251; -NTIPAliasStat["itemreplenishdurability"] = 252; -NTIPAliasStat["itemreplenishquantity"] = 253; -NTIPAliasStat["itemextrastack"] = 254; -NTIPAliasStat["itemfinditem"] = 255; -NTIPAliasStat["itemslashdamage"] = 256; -NTIPAliasStat["itemslashdamagepercent"] = 257; -NTIPAliasStat["itemcrushdamage"] = 258; -NTIPAliasStat["itemcrushdamagepercent"] = 259; -NTIPAliasStat["itemthrustdamage"] = 260; -NTIPAliasStat["itemthrustdamagepercent"] = 261; -NTIPAliasStat["itemabsorbslash"] = 262; -NTIPAliasStat["itemabsorbcrush"] = 263; -NTIPAliasStat["itemabsorbthrust"] = 264; -NTIPAliasStat["itemabsorbslashpercent"] = 265; -NTIPAliasStat["itemabsorbcrushpercent"] = 266; -NTIPAliasStat["itemabsorbthrustpercent"] = 267; -NTIPAliasStat["itemarmorbytime"] = 268; -NTIPAliasStat["itemarmorpercentbytime"] = 269; -NTIPAliasStat["itemhpbytime"] = 270; -NTIPAliasStat["itemmanabytime"] = 271; -NTIPAliasStat["itemmaxdamagebytime"] = 272; -NTIPAliasStat["itemmaxdamagepercentbytime"] = 273; -NTIPAliasStat["itemstrengthbytime"] = 274; -NTIPAliasStat["itemdexteritybytime"] = 275; -NTIPAliasStat["itemenergybytime"] = 276; -NTIPAliasStat["itemvitalitybytime"] = 277; -NTIPAliasStat["itemtohitbytime"] = 278; -NTIPAliasStat["itemtohitpercentbytime"] = 279; -NTIPAliasStat["itemcolddamagemaxbytime"] = 280; -NTIPAliasStat["itemfiredamagemaxbytime"] = 281; -NTIPAliasStat["itemltngdamagemaxbytime"] = 282; -NTIPAliasStat["itempoisdamagemaxbytime"] = 283; -NTIPAliasStat["itemresistcoldbytime"] = 284; -NTIPAliasStat["itemresistfirebytime"] = 285; -NTIPAliasStat["itemresistltngbytime"] = 286; -NTIPAliasStat["itemresistpoisbytime"] = 287; -NTIPAliasStat["itemabsorbcoldbytime"] = 288; -NTIPAliasStat["itemabsorbfirebytime"] = 289; -NTIPAliasStat["itemabsorbltngbytime"] = 290; -NTIPAliasStat["itemabsorbpoisbytime"] = 291; -NTIPAliasStat["itemfindgoldbytime"] = 292; -NTIPAliasStat["itemfindmagicbytime"] = 293; -NTIPAliasStat["itemregenstaminabytime"] = 294; -NTIPAliasStat["itemstaminabytime"] = 295; -NTIPAliasStat["itemdamagedemonbytime"] = 296; -NTIPAliasStat["itemdamageundeadbytime"] = 297; -NTIPAliasStat["itemtohitdemonbytime"] = 298; -NTIPAliasStat["itemtohitundeadbytime"] = 299; -NTIPAliasStat["itemcrushingblowbytime"] = 300; -NTIPAliasStat["itemopenwoundsbytime"] = 301; -NTIPAliasStat["itemkickdamagebytime"] = 302; -NTIPAliasStat["itemdeadlystrikebytime"] = 303; -NTIPAliasStat["itemfindgemsbytime"] = 304; -NTIPAliasStat["itempiercecold"] = 305; -NTIPAliasStat["itempiercefire"] = 306; -NTIPAliasStat["itempierceltng"] = 307; -NTIPAliasStat["itempiercepois"] = 308; -NTIPAliasStat["itemdamagevsmonster"] = 309; -NTIPAliasStat["itemdamagepercentvsmonster"] = 310; -NTIPAliasStat["itemtohitvsmonster"] = 311; -NTIPAliasStat["itemtohitpercentvsmonster"] = 312; -NTIPAliasStat["itemacvsmonster"] = 313; -NTIPAliasStat["itemacpercentvsmonster"] = 314; -NTIPAliasStat["firelength"] = 315; -NTIPAliasStat["burningmin"] = 316; -NTIPAliasStat["burningmax"] = 317; -NTIPAliasStat["progressivedamage"] = 318; -NTIPAliasStat["progressivesteal"] = 319; -NTIPAliasStat["progressiveother"] = 320; -NTIPAliasStat["progressivefire"] = 321; -NTIPAliasStat["progressivecold"] = 322; -NTIPAliasStat["progressivelightning"] = 323; -NTIPAliasStat["itemextracharges"] = 324; -NTIPAliasStat["progressivetohit"] = 325; -NTIPAliasStat["poisoncount"] = 326; -NTIPAliasStat["damageframerate"] = 327; -NTIPAliasStat["pierceidx"] = 328; -NTIPAliasStat["passivefiremastery"] = 329; -NTIPAliasStat["passiveltngmastery"] = 330; -NTIPAliasStat["passivecoldmastery"] = 331; -NTIPAliasStat["passivepoismastery"] = 332; -NTIPAliasStat["passivefirepierce"] = 333; -NTIPAliasStat["passiveltngpierce"] = 334; -NTIPAliasStat["passivecoldpierce"] = 335; -NTIPAliasStat["passivepoispierce"] = 336; -NTIPAliasStat["passivecriticalstrike"] = 337; -NTIPAliasStat["passivedodge"] = 338; -NTIPAliasStat["passiveavoid"] = 339; -NTIPAliasStat["passiveevade"] = 340; -NTIPAliasStat["passivewarmth"] = 341; -NTIPAliasStat["passivemasterymeleeth"] = 342; -NTIPAliasStat["passivemasterymeleedmg"] = 343; -NTIPAliasStat["passivemasterymeleecrit"] = 344; -NTIPAliasStat["passivemasterythrowth"] = 345; -NTIPAliasStat["passivemasterythrowdmg"] = 346; -NTIPAliasStat["passivemasterythrowcrit"] = 347; -NTIPAliasStat["passiveweaponblock"] = 348; -NTIPAliasStat["passivesummonresist"] = 349; -NTIPAliasStat["modifierlistskill"] = 350; -NTIPAliasStat["modifierlistlevel"] = 351; -NTIPAliasStat["lastsenthppct"] = 352; -NTIPAliasStat["sourceunittype"] = 353; -NTIPAliasStat["sourceunitid"] = 354; -NTIPAliasStat["shortparam1"] = 355; -NTIPAliasStat["questitemdifficulty"] = 356; -NTIPAliasStat["passivemagmastery"] = 357; -NTIPAliasStat["passivemagpierce"] = 358; - - -// Doesnt really exists, but is calculated in getStatEx -NTIPAliasStat["allres"] = 555; diff --git a/d2bs/kolbot/libs/NTItemParser.dbl b/d2bs/kolbot/libs/NTItemParser.dbl deleted file mode 100644 index 4dcaec420..000000000 --- a/d2bs/kolbot/libs/NTItemParser.dbl +++ /dev/null @@ -1,606 +0,0 @@ -/** -* @filename NTItemParser.dbl -* @author kolton -* @credit d2nt -* @desc nip file parser for kolbots pickit system -* -* @Item-parser Syntax Information -* 1. [Keyword] separates into two groups -* - [Property Keywords] : [Type], [Name], [Class], [Quality], [Flag], [Level], [Prefix], [Suffix] -* - [Stat Keywords] : [Number or Alias] -* 2. [Keyword] must be surrounded by '[' and ']' -* 3. [Property Keywords] must be placed first -* 4. Insert '#' symbol between [Property Keywords] and [Stat Keywords] -* 5. Use '+', '-', '*', '/', '(', ')', '&&', '||', '>', '>=', '<', '<=', '==', '!=' symbols for comparison -* 6. Use '//' symbol for comment -* -* @Example: [name] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 // Perfect Raven Frost -* -*/ - -include("NTItemAlias.dbl"); - -const NTIP = {}; -const NTIP_CheckList = []; -const NTIP_CheckListNoTier = []; -let stringArray = []; - -NTIP.OpenFile = function (filepath, notify) { - if (!FileTools.exists(filepath)) { - if (notify) { - Misc.errorReport("ÿc1NIP file doesn't exist: ÿc0" + filepath); - } - - return false; - } - - let nipfile, lines; - let tick = getTickCount(); - let filename = filepath.substring(filepath.lastIndexOf("/") + 1, filepath.length); - let entries = 0; - - try { - nipfile = File.open(filepath, 0); - } catch (fileError) { - if (notify) { - Misc.errorReport("ÿc1Failed to load NIP: ÿc0" + filename); - } - } - - if (!nipfile) { - return false; - } - - lines = nipfile.readAllLines(); - nipfile.close(); - - for (let i = 0; i < lines.length; i += 1) { - let info = { - line: i + 1, - file: filename, - string: lines[i] - }; - - let line = NTIP.ParseLineInt(lines[i], info); - - if (line) { - entries += 1; - - NTIP_CheckList.push(line); - - if (!lines[i].toLowerCase().match("tier")) { - NTIP_CheckListNoTier.push(line); - } - - stringArray.push(info); - } - } - - if (notify) { - print("ÿc4Loaded NIP: ÿc2" + filename + "ÿc4. Lines: ÿc2" + lines.length + "ÿc4. Valid entries: ÿc2" + entries + ". ÿc4Time: ÿc2" + (getTickCount() - tick) + " ms"); - } - - return true; -}; - -NTIP.CheckQuantityOwned = function (item_type, item_stats) { - let item; - let num = 0; - let items = me.getItemsEx(); - - if (!items.length) { - print("I can't find my items!"); - - return 0; - } - - for (let i = 0; i < items.length; i += 1) { - if (items[i].mode === sdk.items.mode.inStorage && items[i].location === sdk.storage.Stash) { - item = items[i]; - - if ((item_type !== null && typeof item_type === "function" && item_type(item)) || item_type === null) { - if ((item_stats !== null && typeof item_stats === "function" && item_stats(item)) || item_stats === null) { - num += 1; - } - } - } else if (items[i].mode === sdk.items.mode.inStorage && items[i].location === sdk.storage.Inventory) { // inv check - item = items[i]; - - if ((item_type !== null && typeof item_type === "function" && item_type(item)) || item_type === null) { - if ((item_stats !== null && typeof item_stats === "function" && item_stats(item)) || item_stats === null) { - //if (Config.Inventory[items[i].y][items[i].x] > 0) { // we check only space that is supposed to be free - num += 1; - //} - } - } - } - } - - //print("I have "+num+" of these."); - - return num; -}; - -NTIP.Clear = function () { - NTIP_CheckList.length = 0; - NTIP_CheckListNoTier.length = 0; - stringArray = []; -}; - -/** @return {function({Unit} item)} */ -NTIP.generateTierFunc = function (tierType) { - return function (item) { - let tier = -1; - - const updateTier = (wanted) => { - const tmpTier = wanted[tierType](item); - - if (tier < tmpTier) { - tier = tmpTier; - } - }; - - // Go through ALL lines that describe the item - for (let i = 0; i < NTIP_CheckList.length; i += 1) { - if (NTIP_CheckList[i].length !== 3) { - continue; - } - - let [type, stat, wanted] = NTIP_CheckList[i]; - - // If the line doesnt have a tier of this type, we dont need to call it - if (typeof wanted === "object" && wanted && typeof wanted[tierType] === "function") { - try { - if (typeof type === "function") { - if (type(item)) { - if (typeof stat === "function") { - if (stat(item)) { - updateTier(wanted); - } - } else { - updateTier(wanted); - } - } - } else if (typeof stat === "function") { - if (stat(item)) { - updateTier(wanted); - } - } - } catch (e) { - const info = stringArray[i]; - Misc.errorReport("ÿc1Pickit Tier (" + tierType + ") error! Line # ÿc2" + info.line + " ÿc1Entry: ÿc0" + info.string + " (" + info.file + ") Error message: " + e.message); - } - } - } - - return tier; - }; -}; - -/**@function - * @param item */ -NTIP.GetTier = NTIP.generateTierFunc("Tier"); - -/**@function - * @param item */ -NTIP.GetMercTier = NTIP.generateTierFunc("Merctier"); - -NTIP.CheckItem = function (item, entryList, verbose) { - let i, num; - let rval = {}; - let result = 0; - - let list = entryList ? entryList : NTIP_CheckList; - let identified = item.getFlag(sdk.items.flags.Identified); - - for (i = 0; i < list.length; i++) { - try { - // Get the values in separated variables (its faster) - const [type, stat, wanted] = list[i]; - - if (typeof type === "function") { - if (type(item)) { - if (typeof stat === "function") { - if (stat(item)) { - if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) { - num = NTIP.CheckQuantityOwned(type, stat); - - if (num < wanted.MaxQuantity) { - result = 1; - - break; - } else { - // attempt at inv fix for maxquantity - if (item.getParent() && item.getParent().name === me.name && item.isInStorage && num === wanted.MaxQuantity) { - result = 1; - - break; - } - } - } else { - result = 1; - - break; - } - } else if (!identified && result === 0) { - result = -1; - verbose && (rval.line = stringArray[i].file + " #" + stringArray[i].line); - } - } else { - if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) { - num = NTIP.CheckQuantityOwned(type, null); - - if (num < wanted.MaxQuantity) { - result = 1; - - break; - } else { - // attempt at inv fix for maxquantity - if (item.getParent() && item.getParent().name === me.name && item.isInStorage && num === wanted.MaxQuantity) { - result = 1; - - break; - } - } - } else { - result = 1; - - break; - } - } - } - } else if (typeof stat === "function") { - if (stat(item)) { - if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) { - num = NTIP.CheckQuantityOwned(null, stat); - - if (num < wanted.MaxQuantity) { - result = 1; - - break; - } else { - // attempt at inv fix for maxquantity - if (item.getParent() && item.getParent().name === me.name && item.isInStorage && num === wanted.MaxQuantity) { - result = 1; - - break; - } - } - } else { - result = 1; - - break; - } - } else if (!identified && result === 0) { - result = -1; - verbose && (rval.line = stringArray[i].file + " #" + stringArray[i].line); - } - } - } catch (pickError) { - showConsole(); - - if (!entryList) { - Misc.errorReport("ÿc1Pickit error! Line # ÿc2" + stringArray[i].line + " ÿc1Entry: ÿc0" + stringArray[i].string + " (" + stringArray[i].file + ") Error message: " + pickError.message + " Trigger item: " + item.fname.split("\n").reverse().join(" ")); - - NTIP_CheckList.splice(i, 1); // Remove the element from the list - } else { - Misc.errorReport("ÿc1Pickit error in runeword config!"); - } - - result = 0; - } - } - - if (verbose) { - switch (result) { - case -1: - break; - case 1: - rval.line = stringArray[i].file + " #" + stringArray[i].line; - - break; - default: - rval.line = null; - - break; - } - - rval.result = result; - - return rval; - } - - return result; -}; - -NTIP.IsSyntaxInt = function (ch) { - return (ch === "!" || ch === "%" || ch === "&" || (ch >= "(" && ch <= "+") || ch === "-" || ch === "/" || (ch >= ":" && ch <= "?") || ch === "|"); -}; - -NTIP.ParseLineInt = function (input, info) { - let i, property, p_start, p_end, p_section, p_keyword, p_result, value; - - p_end = input.indexOf("//"); - - if (p_end !== -1) { - input = input.substring(0, p_end); - } - - input = input.replace(/\s+/g, "").toLowerCase(); - - if (input.length < 5) { - return null; - } - - p_result = input.split("#"); - - if (p_result[0] && p_result[0].length > 4) { - p_section = p_result[0].split("["); - - p_result[0] = p_section[0]; - - for (i = 1; i < p_section.length; i += 1) { - p_end = p_section[i].indexOf("]") + 1; - property = p_section[i].substring(0, p_end - 1); - - switch (property) { - case "color": - p_result[0] += "item.getColor()"; - - break; - case "type": - p_result[0] += "item.itemType"; - - break; - case "name": - p_result[0] += "item.classid"; - - break; - case "class": - p_result[0] += "item.itemclass"; - - break; - case "quality": - p_result[0] += "item.quality"; - - break; - case "flag": - if (p_section[i][p_end] === "!") { - p_result[0] += "!item.getFlag("; - } else { - p_result[0] += "item.getFlag("; - } - - p_end += 2; - - break; - case "level": - p_result[0] += "item.ilvl"; - - break; - case "prefix": - if (p_section[i][p_end] === "!") { - p_result[0] += "!item.getPrefix("; - } else { - p_result[0] += "item.getPrefix("; - } - - p_end += 2; - - break; - case "suffix": - if (p_section[i][p_end] === "!") { - p_result[0] += "!item.getSuffix("; - } else { - p_result[0] += "item.getSuffix("; - } - - p_end += 2; - - break; - case "europe": - case "uswest": - case "useast": - case "asia": - p_result[0] += '("' + me.realm.toLowerCase() + '"==="' + property.toLowerCase() + '")'; - - break; - case "ladder": - p_result[0] += "me.ladder"; - - break; - case "hardcore": - p_result[0] += "(!!me.playertype)"; - - break; - case "classic": - p_result[0] += "(!me.gametype)"; - - break; - default: - Misc.errorReport("Unknown property: " + property + " File: " + info.file + " Line: " + info.line); - - return false; - } - - for (p_start = p_end; p_end < p_section[i].length; p_end += 1) { - if (!NTIP.IsSyntaxInt(p_section[i][p_end])) { - break; - } - } - - p_result[0] += p_section[i].substring(p_start, p_end); - - if (p_section[i].substring(p_start, p_end) === "=") { - Misc.errorReport("Unexpected = at line " + info.line + " in " + info.file); - - return false; - } - - for (p_start = p_end; p_end < p_section[i].length; p_end += 1) { - if (NTIP.IsSyntaxInt(p_section[i][p_end])) { - break; - } - } - - p_keyword = p_section[i].substring(p_start, p_end); - - if (isNaN(p_keyword)) { - switch (property) { - case "color": - if (NTIPAliasColor[p_keyword] === undefined) { - Misc.errorReport("Unknown color: " + p_keyword + " File: " + info.file + " Line: " + info.line); - - return false; - } - - p_result[0] += NTIPAliasColor[p_keyword]; - - break; - case "type": - if (NTIPAliasType[p_keyword] === undefined) { - Misc.errorReport("Unknown type: " + p_keyword + " File: " + info.file + " Line: " + info.line); - - return false; - } - - p_result[0] += NTIPAliasType[p_keyword]; - - break; - case "name": - if (NTIPAliasClassID[p_keyword] === undefined) { - Misc.errorReport("Unknown name: " + p_keyword + " File: " + info.file + " Line: " + info.line); - - return false; - } - - p_result[0] += NTIPAliasClassID[p_keyword]; - - break; - case "class": - if (NTIPAliasClass[p_keyword] === undefined) { - Misc.errorReport("Unknown class: " + p_keyword + " File: " + info.file + " Line: " + info.line); - - return false; - } - - p_result[0] += NTIPAliasClass[p_keyword]; - - break; - case "quality": - if (NTIPAliasQuality[p_keyword] === undefined) { - Misc.errorReport("Unknown quality: " + p_keyword + " File: " + info.file + " Line: " + info.line); - - return false; - } - - p_result[0] += NTIPAliasQuality[p_keyword]; - - break; - case "flag": - if (NTIPAliasFlag[p_keyword] === undefined) { - Misc.errorReport("Unknown flag: " + p_keyword + " File: " + info.file + " Line: " + info.line); - - return false; - } - - p_result[0] += NTIPAliasFlag[p_keyword] + ")"; - - break; - case "prefix": - case "suffix": - p_result[0] += "\"" + p_keyword + "\")"; - - break; - } - } else { - if (property === "flag" || property === "prefix" || property === "suffix") { - p_result[0] += p_keyword + ")"; - } else { - p_result[0] += p_keyword; - } - } - - p_result[0] += p_section[i].substring(p_end); - } - } else { - p_result[0] = ""; - } - - if (p_result[1] && p_result[1].length > 4) { - p_section = p_result[1].split("["); - p_result[1] = p_section[0]; - - for (i = 1; i < p_section.length; i += 1) { - p_end = p_section[i].indexOf("]"); - p_keyword = p_section[i].substring(0, p_end); - - if (isNaN(p_keyword)) { - if (NTIPAliasStat[p_keyword] === undefined) { - Misc.errorReport("Unknown stat: " + p_keyword + " File: " + info.file + " Line: " + info.line); - - return false; - } - - p_result[1] += "item.getStatEx(" + NTIPAliasStat[p_keyword] + ")"; - } else { - p_result[1] += "item.getStatEx(" + p_keyword + ")"; - } - - p_result[1] += p_section[i].substring(p_end + 1); - } - } else { - p_result[1] = ""; - } - - if (p_result[2] && p_result[2].length > 0) { - p_section = p_result[2].split("["); - p_result[2] = {}; - - for (i = 1; i < p_section.length; i += 1) { - p_end = p_section[i].indexOf("]"); - p_keyword = p_section[i].substring(0, p_end); - - let keyword = p_keyword.toLowerCase(); - switch (keyword) { - case "maxquantity": - value = Number(p_section[i].split("==")[1].match(/\d+/g)); - - if (!isNaN(value)) { - p_result[2].MaxQuantity = value; - } - - break; - case "merctier": - case "tier": - try { - // p_result[2].Tier = function(item) { return value }; - p_result[2][keyword.charAt(0).toUpperCase() + keyword.slice(1)] = (new Function("return function(item) { return " + p_section[i].split("==")[1] + ";}")).call(null); // generate function out of - } catch (e) { - Misc.errorReport("ÿc1Pickit Tier (" + keyword + ") error! Line # ÿc2" + info.line + " ÿc1Entry: ÿc0" + info.string + " (" + info.file + ") Error message: " + e.message); - } - break; - - default: - Misc.errorReport("Unknown 3rd part keyword: " + p_keyword.toLowerCase() + " File: " + info.file + " Line: " + info.line); - return false; - } - } - } - // Compile the line, to 1) remove the eval lines, and 2) increase the speed - for (let i = 0; i < 2; i++) { - if (p_result[i].length) { - try { - p_result[i] = (new Function("return function(item) { return " + p_result[i] + ";}")).call(null); // generate function out of - } catch (e) { - Misc.errorReport("ÿc1Pickit error! Line # ÿc2" + info.line + " ÿc1Entry: ÿc0" + info.string + " (" + info.file + ") Error message: " + e.message); - - return null ; // failed load this line so return false - } - } else { - p_result[i] = undefined; - } - - } - return p_result; -}; diff --git a/d2bs/kolbot/libs/OOG.js b/d2bs/kolbot/libs/OOG.js index 2f0cc62e4..aee5e7b6c 100644 --- a/d2bs/kolbot/libs/OOG.js +++ b/d2bs/kolbot/libs/OOG.js @@ -1,2155 +1,1799 @@ +/* eslint-disable max-len */ /** * @filename OOG.js * @author kolton, D3STROY3R, theBGuy -* @desc handle out of game operations like creating characters/accounts, maintaining profile datafiles, d2bot# logging etc. +* @desc handle out of game operations, interacting with controls, location actions, and starter settings * */ -!isIncluded("Polyfill.js") && include("Polyfill.js"); -!isIncluded("common/Util.js") && include("common/Util.js"); - -let sdk = require("./modules/sdk"); -let Controls = require("./modules/Control"); - -const D2Bot = { - handle: 0, - - init: function () { - let handle = DataFile.getStats().handle; - - if (handle) { - this.handle = handle; - } - - return this.handle; - }, - - sendMessage: function (handle, mode, msg) { - sendCopyData(null, handle, mode, msg); - }, - - printToConsole: function (msg, color, tooltip, trigger) { - let printObj = { - msg: msg, - color: color || 0, - tooltip: tooltip || "", - trigger: trigger || "" - }; - - let obj = { - profile: me.profile, - func: "printToConsole", - args: [JSON.stringify(printObj)] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - printToItemLog: function (itemObj) { - let obj = { - profile: me.profile, - func: "printToItemLog", - args: [JSON.stringify(itemObj)] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - uploadItem: function (itemObj) { - let obj = { - profile: me.profile, - func: "uploadItem", - args: [JSON.stringify(itemObj)] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - writeToFile: function (filename, msg) { - let obj = { - profile: me.profile, - func: "writeToFile", - args: [filename, msg] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - postToIRC: function (ircProfile, recepient, msg) { - let obj = { - profile: me.profile, - func: "postToIRC", - args: [ircProfile, recepient, msg] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - ircEvent: function (mode) { - let obj = { - profile: me.profile, - func: "ircEvent", - args: [mode ? "true" : "false"] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - notify: function (msg) { - let obj = { - profile: me.profile, - func: "notify", - args: [msg] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - saveItem: function (itemObj) { - let obj = { - profile: me.profile, - func: "saveItem", - args: [JSON.stringify(itemObj)] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - updateStatus: function (msg) { - let obj = { - profile: me.profile, - func: "updateStatus", - args: [msg] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - updateRuns: function () { - let obj = { - profile: me.profile, - func: "updateRuns", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - updateChickens: function () { - let obj = { - profile: me.profile, - func: "updateChickens", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - updateDeaths: function () { - let obj = { - profile: me.profile, - func: "updateDeaths", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - requestGameInfo: function () { - let obj = { - profile: me.profile, - func: "requestGameInfo", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - restart: function (keySwap) { - let obj = { - profile: me.profile, - func: "restartProfile", - args: arguments.length > 0 ? [me.profile, keySwap] : [me.profile] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - CDKeyInUse: function () { - let obj = { - profile: me.profile, - func: "CDKeyInUse", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - CDKeyDisabled: function () { - let obj = { - profile: me.profile, - func: "CDKeyDisabled", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - CDKeyRD: function () { - let obj = { - profile: me.profile, - func: "CDKeyRD", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - stop: function (profile, release) { - !profile && (profile = me.profile); - - let obj = { - profile: me.profile, - func: "stop", - args: [profile, release ? "True" : "False"] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - start: function (profile) { - let obj = { - profile: me.profile, - func: "start", - args: [profile] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - startSchedule: function (profile) { - let obj = { - profile: me.profile, - func: "startSchedule", - args: [profile] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - stopSchedule: function (profile) { - let obj = { - profile: me.profile, - func: "stopSchedule", - args: [profile] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - updateCount: function () { - let obj = { - profile: me.profile, - func: "updateCount", - args: ["1"] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - shoutGlobal: function (msg, mode) { - let obj = { - profile: me.profile, - func: "shoutGlobal", - args: [msg, mode] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - heartBeat: function () { - let obj = { - profile: me.profile, - func: "heartBeat", - args: [] - }; - - //print("ÿc1Heart beat " + this.handle); - sendCopyData(null, this.handle, 0xbbbb, JSON.stringify(obj)); - }, - - sendWinMsg: function (wparam, lparam) { - let obj = { - profile: me.profile, - func: "winmsg", - args: [wparam, lparam] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - ingame: function () { - this.sendWinMsg(0x0086, 0x0000); - this.sendWinMsg(0x0006, 0x0002); - this.sendWinMsg(0x001c, 0x0000); - }, - - // Profile to profile communication - joinMe: function (profile, gameName, gameCount, gamePass, isUp) { - let obj = { - gameName: gameName + gameCount, - gamePass: gamePass, - inGame: isUp === "yes" - }; - - sendCopyData(null, profile, 1, JSON.stringify(obj)); - }, - - requestGame: function (profile) { - let obj = { - profile: me.profile - }; - - sendCopyData(null, profile, 3, JSON.stringify(obj)); - }, - - getProfile: function () { - let obj = { - profile: me.profile, - func: "getProfile", - args: [] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - setProfile: function (account, password, character, difficulty, realm, infoTag, gamePath) { - let obj = { - profile: me.profile, - func: "setProfile", - args: [account, password, character, difficulty, realm, infoTag, gamePath] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - setTag: function (tag) { - let obj = { - profile: me.profile, - func: "setTag", - args: [JSON.stringify(tag)] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - // Store info in d2bot# cache - store: function (info) { - this.remove(); - - let obj = { - profile: me.profile, - func: "store", - args: [me.profile, info] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - // Get info from d2bot# cache - retrieve: function () { - let obj = { - profile: me.profile, - func: "retrieve", - args: [me.profile] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - }, - - // Delete info from d2bot# cache - remove: function () { - let obj = { - profile: me.profile, - func: "delete", - args: [me.profile] - }; - - sendCopyData(null, this.handle, 0, JSON.stringify(obj)); - } -}; - -const DataFile = { - create: function () { - let obj = { - runs: 0, - experience: 0, - deaths: 0, - lastArea: "", - gold: 0, - level: 0, - name: "", - gameName: "", - ingameTick: 0, - handle: 0, - nextGame: "" - }; - - let string = JSON.stringify(obj); - - Misc.fileAction("data/" + me.profile + ".json", 1, string); - - return obj; - }, - - getObj: function () { - !FileTools.exists("data/" + me.profile + ".json") && DataFile.create(); - - let obj; - let string = Misc.fileAction("data/" + me.profile + ".json", 0); - - try { - obj = JSON.parse(string); - } catch (e) { - // If we failed, file might be corrupted, so create a new one - obj = this.create(); - } - - if (obj) { - return obj; - } - - print("Error reading DataFile. Using null values."); - - return {runs: 0, experience: 0, lastArea: "", gold: 0, level: 0, name: "", gameName: "", ingameTick: 0, handle: 0, nextGame: ""}; - }, - - getStats: function () { - let obj = this.getObj(); - - return Misc.clone(obj); - }, - - updateStats: function (arg, value) { - while (me.ingame && !me.gameReady) { - delay(100); - } - - let statArr = []; - - typeof arg === "object" && (statArr = arg.slice()); - typeof arg === "string" && statArr.push(arg); - - let obj = this.getObj(); - - for (let i = 0; i < statArr.length; i += 1) { - switch (statArr[i]) { - case "experience": - obj.experience = me.getStat(sdk.stats.Experience); - obj.level = me.getStat(sdk.stats.Level); - - break; - case "lastArea": - if (obj.lastArea === Pather.getAreaName(me.area)) { - return; - } - - obj.lastArea = Pather.getAreaName(me.area); - - break; - case "gold": - if (!me.gameReady) { - break; - } - - obj.gold = me.getStat(sdk.stats.Gold) + me.getStat(sdk.stats.GoldBank); - - break; - case "name": - obj.name = me.name; - - break; - case "ingameTick": - obj.ingameTick = getTickCount(); - - break; - case "deaths": - obj.deaths = (obj.deaths || 0) + 1; - - break; - default: - obj[statArr[i]] = value; - - break; - } - } - - let string = JSON.stringify(obj); - - Misc.fileAction("data/" + me.profile + ".json", 1, string); - } -}; - -const ControlAction = { - mutedKey: false, - - timeoutDelay: function (text, time, stopfunc, arg) { - let currTime = 0; - let endTime = getTickCount() + time; - - while (getTickCount() < endTime) { - if (typeof stopfunc === "function" && stopfunc(arg)) { - break; - } - - if (currTime !== Math.floor((endTime - getTickCount()) / 1000)) { - currTime = Math.floor((endTime - getTickCount()) / 1000); - - D2Bot.updateStatus(text + " (" + Math.max(currTime, 0) + "s)"); - } - - delay(10); - } - }, - - click: function (type, x, y, xsize, ysize, targetx, targety) { - let control = getControl(type, x, y, xsize, ysize); - - if (!control) { - print("control not found " + type + " " + x + " " + y + " " + xsize + " " + ysize + " location " + getLocation()); - - return false; - } - - control.click(targetx, targety); - - return true; - }, - - setText: function (type, x, y, xsize, ysize, text) { - if (!text) return false; - - let control = getControl(type, x, y, xsize, ysize); - if (!control) return false; - - let currText = control.text; - if (currText && currText === text) return true; - - currText = control.getText(); - - if (currText && ((typeof currText === "string" && currText === text) || (typeof currText === "object" && currText.includes(text)))) { - return true; - } - - control.setText(text); - - return true; - }, - - getText: function (type, x, y, xsize, ysize) { - let control = getControl(type, x, y, xsize, ysize); - - return (!!control ? control.getText() : false); - }, - - joinChannel: function (channel) { - me.blockMouse = true; - - let tick; - let rval = false; - let timeout = 5000; - MainLoop: - while (true) { - switch (getLocation()) { - case sdk.game.locations.Lobby: - Controls.LobbyEnterChat.click(); - - break; - case sdk.game.locations.LobbyChat: - let currChan = Controls.LobbyChannelName.getText(); // returns array - - if (currChan) { - for (let i = 0; i < currChan.length; i += 1) { - if (currChan[i].split(" (") && currChan[i].split(" (")[0].toLowerCase() === channel.toLowerCase()) { - rval = true; - - break MainLoop; - } - } - } - - !tick && Controls.LobbyChannel.click() && (tick = getTickCount()); - - break; - case sdk.game.locations.ChannelList: // Channel - Controls.LobbyChannelText.setText(channel); - Controls.LobbyChannelOk.click(); - - break; - } - - if (getTickCount() - tick >= timeout) { - break; - } - - delay(100); - } - - me.blockMouse = false; - - return rval; - }, - - createGame: function (name, pass, diff, delay) { - Controls.CreateGameName.setText(name); - Controls.CreateGamePass.setText(pass); - - switch (diff) { - case "Normal": - Controls.Normal.click(); - - break; - case "Nightmare": - Controls.Nightmare.click(); - - break; - case "Highest": - if (Controls.Hell.disabled !== 4 && Controls.Hell.click()) { - break; - } - - if (Controls.Nightmare.disabled !== 4 && Controls.Nightmare.click()) { - break; - } - - Controls.Normal.click(); - - break; - default: - Controls.Hell.click(); - - break; - } - - !!delay && this.timeoutDelay("Make Game Delay", delay); - - if (Starter.chanInfo.announce) { - Starter.sayMsg("Next game is " + name + (pass === "" ? "" : "//" + pass)); - } - - me.blockMouse = true; - - print("Creating Game: " + name); - Controls.CreateGame.click(); - - me.blockMouse = false; - }, - - clickRealm: function (realm) { - if (realm === undefined || typeof realm !== "number" || realm < 0 || realm > 3) { - throw new Error("clickRealm: Invalid realm!"); - } - - let currentRealm, retry = 0; - - me.blockMouse = true; - - MainLoop: - while (true) { - switch (getLocation()) { - case sdk.game.locations.MainMenu: - let control = Controls.Gateway.control; - if (!control) { - if (retry > 3) return false; - retry++; - - break; - } - - switch (control.text.split(getLocaleString(sdk.locale.text.Gateway).substring(0, getLocaleString(sdk.locale.text.Gateway).length - 2))[1]) { - case "U.S. EAST": - currentRealm = 1; - - break; - case "U.S. WEST": - currentRealm = 0; - - break; - case "ASIA": - currentRealm = 2; - - break; - case "EUROPE": - currentRealm = 3; - - break; - } - - if (currentRealm === realm) { - break MainLoop; - } - - Controls.Gateway.click(); - - break; - case sdk.game.locations.GatewaySelect: - this.click(4, 257, 500, 292, 160, 403, 350 + realm * 25); - Controls.GatewayOk.click(); +!isIncluded("Polyfill.js") && include("Polyfill.js"); +includeIfNotIncluded("oog/D2Bot.js"); // required +includeIfNotIncluded("core/Me.js"); - break; - } - - delay(500); - } - - me.blockMouse = false; - - return true; - }, - - loginAccount: function (info) { - me.blockMouse = true; - - let locTick; - let realms = { - "uswest": 0, - "useast": 1, - "asia": 2, - "europe": 3 - }; - - let tick = getTickCount(); - - MainLoop: - while (true) { - switch (getLocation()) { - case sdk.game.locations.PreSplash: - break; - case sdk.game.locations.MainMenu: - info.realm && ControlAction.clickRealm(realms[info.realm]); - Controls.BattleNet.click(); - - break; - case sdk.game.locations.Login: - Controls.LoginUsername.setText(info.account); - Controls.LoginPassword.setText(info.password); - Controls.Login.click(); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.RealmDown: - // Unable to connect, let the caller handle it. - me.blockMouse = false; - - return false; - case sdk.game.locations.CharSelect: - break MainLoop; - case sdk.game.locations.SplashScreen: - Controls.SplashScreen.click(); - - break; - case sdk.game.locations.CharSelectPleaseWait: - case sdk.game.locations.MainMenuConnecting: - case sdk.game.locations.CharSelectConnecting: - break; - case sdk.game.locations.CharSelectNoChars: - // make sure we're not on connecting screen - locTick = getTickCount(); - - while (getTickCount() - locTick < 3000 && getLocation() === sdk.game.locations.CharSelectNoChars) { - delay(25); - } - - if (getLocation() === sdk.game.locations.CharSelectConnecting) { - break; - } - - break MainLoop; // break if we're sure we're on empty char screen - default: - print(getLocation()); - - me.blockMouse = false; - - return false; - } - - if (getTickCount() - tick >= 20000) { - return false; - } - - delay(100); - } - - delay(1000); - - me.blockMouse = false; - - return getLocation() === sdk.game.locations.CharSelect || getLocation() === sdk.game.locations.CharSelectNoChars; - }, - - setEmail: function (email = "", domain = "@email.com") { - if (getLocation() !== sdk.game.locations.RegisterEmail) return false; - if (!email || !email.length) { - email = Starter.randomString(null, true); - } - - while (getLocation() !== sdk.game.locations.CharSelect) { - switch (getLocation()) { - case sdk.game.locations.RegisterEmail: - if (Controls.EmailSetEmail.setText(email + domain) && Controls.EmailVerifyEmail.setText(email + domain)) { - Controls.EmailRegister.click(); - delay(100); - } - - break; - case sdk.game.locations.LoginError: - // todo test what conditions get here other than email not matching - D2Bot.printToConsole("Failed to set email"); - Controls.LoginErrorOk.click(); - - return false; - case sdk.game.locations.CharSelectNoChars: - // fresh acc - return true; - } - } - - return true; - }, - - makeAccount: function (info) { - me.blockMouse = true; - - let openBnet = Profile().type === sdk.game.profiletype.OpenBattlenet; - let realms = { - "uswest": 0, - "useast": 1, - "asia": 2, - "europe": 3 - }; - // cycle until in empty char screen - MainLoop: - while (getLocation() !== sdk.game.locations.CharSelectNoChars) { - switch (getLocation()) { - case sdk.game.locations.MainMenu: - ControlAction.clickRealm(realms[info.realm]); - if (openBnet) { - Controls.OtherMultiplayer.click() && Controls.OpenBattleNet.click(); - } else { - Controls.BattleNet.click(); - } - - break; - case sdk.game.locations.Login: - Controls.CreateNewAccount.click(); - - break; - case sdk.game.locations.SplashScreen: - Controls.SplashScreen.click(); - - break; - case sdk.game.locations.CharacterCreate: - Controls.CharSelectExit.click(); - - break; - case sdk.game.locations.TermsOfUse: - Controls.TermsOfUseAgree.click(); - - break; - case sdk.game.locations.CreateNewAccount: - Controls.CreateNewAccountName.setText(info.account); - Controls.CreateNewAccountPassword.setText(info.password); - Controls.CreateNewAccountConfirmPassword.setText(info.password); - Controls.CreateNewAccountOk.click(); - - break; - case sdk.game.locations.PleaseRead: - Controls.PleaseReadOk.click(); - - break; - case sdk.game.locations.RegisterEmail: - Controls.EmailDontRegisterContinue.control ? Controls.EmailDontRegisterContinue.click() : Controls.EmailDontRegister.click(); - - break; - case sdk.game.locations.CharSelect: - if (openBnet) { - break MainLoop; - } - - break; - default: - break; - } - - delay(100); - } - - me.blockMouse = false; - - return true; - }, - - findCharacter: function (info) { - let count = 0; - let tick = getTickCount(); - - while (getLocation() !== sdk.game.locations.CharSelect) { - if (getTickCount() - tick >= 5000) { - break; - } - - delay(25); - } - - // start from beginning of the char list - sendKey(0x24); - - while (getLocation() === sdk.game.locations.CharSelect && count < 24) { - let control = Controls.CharSelectCharInfo0.control; - - if (control) { - do { - let text = control.getText(); - - if (text instanceof Array && typeof text[1] === "string") { - count++; - - if (text[1].toLowerCase() === info.charName.toLowerCase()) { - return true; - } - } - } while (count < 24 && control.getNext()); - } - - // check for additional characters up to 24 - if (count === 8 || count === 16) { - if (Controls.CharSelectChar6.click()) { - me.blockMouse = true; - - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - - me.blockMouse = false; - } - } else { - // no further check necessary - break; - } - } - - return false; - }, - - // get all characters - getCharacters: function () { - let count = 0; - let list = []; - - // start from beginning of the char list - sendKey(0x24); - - while (getLocation() === sdk.game.locations.CharSelect && count < 24) { - let control = Controls.CharSelectCharInfo0.control; - - if (control) { - do { - let text = control.getText(); - - if (text instanceof Array && typeof text[1] === "string") { - count++; - - if (list.indexOf(text[1]) === -1) { - list.push(text[1]); - } - } - } while (count < 24 && control.getNext()); - } - - // check for additional characters up to 24 - if (count === 8 || count === 16) { - if (Controls.CharSelectChar6.click()) { - me.blockMouse = true; - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - - me.blockMouse = false; - } - } else { - // no further check necessary - break; - } - } - - // back to beginning of the char list - sendKey(0x24); - - return list; - }, - - getPermStatus: function (info) { - let count = 0; - let tick = getTickCount(); - let expireStr = getLocaleString(sdk.locale.text.ExpiresIn); - expireStr = expireStr.slice(0, expireStr.indexOf("%")).trim(); - - while (getLocation() !== sdk.game.locations.CharSelect) { - if (getTickCount() - tick >= 5000) { - break; - } - - delay(25); - } - - // start from beginning of the char list - sendKey(0x24); - - while (getLocation() === sdk.game.locations.CharSelect && count < 24) { - let control = Controls.CharSelectCharInfo0.control; - - if (control) { - do { - let text = control.getText(); - - if (text instanceof Array && typeof text[1] === "string") { - count++; - - if (text[1].toLowerCase() === info.charName.toLowerCase()) { - return !text.some(el => el.includes(expireStr)); - } - } - } while (count < 24 && control.getNext()); - } - - // check for additional characters up to 24 - if (count === 8 || count === 16) { - if (Controls.CharSelectChar6.click()) { - me.blockMouse = true; - - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - - me.blockMouse = false; - } - } else { - // no further check necessary - break; - } - } - - return false; - }, - - // get character position - getPosition: function () { - let position = 0; - - if (getLocation() === sdk.game.locations.CharSelect) { - let control = Controls.CharSelectCharInfo0.control; - - if (control) { - do { - let text = control.getText(); - - if (text instanceof Array && typeof text[1] === "string") { - position += 1; - } - } while (control.getNext()); - } - } - - return position; - }, - - loginCharacter: function (info, startFromTop = true) { - me.blockMouse = true; - - let count = 0; - - // start from beginning of the char list - startFromTop && sendKey(0x24); - - MainLoop: - // cycle until in lobby or in game - while (getLocation() !== sdk.game.locations.Lobby) { - switch (getLocation()) { - case sdk.game.locations.CharSelect: - let control = Controls.CharSelectCharInfo0.control; - - if (control) { - do { - let text = control.getText(); - - if (text instanceof Array && typeof text[1] === "string") { - count++; - - if (text[1].toLowerCase() === info.charName.toLowerCase()) { - control.click(); - Controls.CreateNewAccountOk.click(); - me.blockMouse = false; - - if (getLocation() === sdk.game.locations.SelectDifficultySP) { - try { - login(info.profile); - } catch (err) { - break MainLoop; - } - - if (me.ingame) { - return true; - } - } - - return true; - } - } - } while (control.getNext()); - } - - // check for additional characters up to 24 - if (count === 8 || count === 16) { - if (Controls.CharSelectChar6.click()) { - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - } - } else { - // no further check necessary - break MainLoop; - } - - break; - case sdk.game.locations.CharSelectNoChars: - Controls.CharSelectExit.click(); - - break; - case sdk.game.locations.Disconnected: - case sdk.game.locations.OkCenteredErrorPopUp: - break MainLoop; - default: - break; - } - - delay(100); - } - - me.blockMouse = false; - - return false; - }, - - makeCharacter: function (info) { - me.blockMouse = true; - !info.charClass && (info.charClass = "barbarian"); - - if (info.charName.match(/\d+/g)) { - console.warn("Invalid character name, cannot contain numbers"); - - return false; - } - - let clickCoords = []; - - // cycle until in lobby - while (getLocation() !== sdk.game.locations.Lobby) { - switch (getLocation()) { - case sdk.game.locations.CharSelect: - case sdk.game.locations.CharSelectNoChars: - // Create Character greyed out - if (Controls.CharSelectCreate.disabled === sdk.game.controls.Disabled) { - me.blockMouse = false; - - return false; - } - - Controls.CharSelectCreate.click(); - - break; - case sdk.game.locations.CharacterCreate: - switch (info.charClass) { - case "barbarian": - clickCoords = [400, 280]; - - break; - case "amazon": - clickCoords = [100, 280]; - - break; - case "necromancer": - clickCoords = [300, 290]; - - break; - case "sorceress": - clickCoords = [620, 270]; - - break; - case "assassin": - clickCoords = [200, 280]; - - break; - case "druid": - clickCoords = [700, 280]; - - break; - case "paladin": - clickCoords = [521, 260]; - - break; - } - - // coords: - // zon: 100, 280 - // barb: 400, 280 - // necro: 300, 290 - // sin: 200, 280 - // paladin: 521 260 - // sorc: 620, 270 - // druid: 700, 280 - - getControl().click(clickCoords[0], clickCoords[1]); - delay(500); - - break; - case sdk.game.locations.NewCharSelected: - if (Controls.CharCreateHCWarningOk.control) { - Controls.CharCreateHCWarningOk.click(); - } else { - Controls.CharCreateCharName.setText(info.charName); - - if (!info.expansion) { - switch (info.charClass) { - case "druid": - case "assassin": - D2Bot.printToConsole("Error in profile name. Expansion characters cannot be made in classic", sdk.colors.D2Bot.Red); - D2Bot.stop(); - - break; - default: - break; - } - - Controls.CharCreateExpansion.click(); - } - - !info.ladder && Controls.CharCreateLadder.click(); - info.hardcore && Controls.CharCreateHardcore.click(); - - Controls.CreateNewAccountOk.click(); - } - - break; - case sdk.game.locations.OkCenteredErrorPopUp: - // char name exists (text box 4, 268, 320, 264, 120) - Controls.OkCentered.click(); - Controls.CharSelectExit.click(); - - me.blockMouse = false; - - return false; - default: - break; - } - - // Singleplayer loop break fix. - if (me.ingame) { - break; - } - - delay(500); - } - - me.blockMouse = false; - - return true; - }, - - // Test version - modified core only - getGameList: function () { - let text = Controls.JoinGameList.getText(); - - if (text) { - let gameList = []; - - for (let i = 0; i < text.length; i += 1) { - gameList.push({ - gameName: text[i][0], - players: text[i][1] - }); - } - - return gameList; - } - - return false; - }, - - deleteCharacter: function (info) { - me.blockMouse = true; - - // start from beginning of the char list - sendKey(0x24); - - // cycle until in lobby - while (getLocation() === sdk.game.locations.CharSelect) { - let count = 0; - let control = Controls.CharSelectCharInfo0.control; - - if (control) { - do { - let text = control.getText(); - - if (text instanceof Array && typeof text[1] === "string") { - count++; - - if (text[1].toLowerCase() === info.charName.toLowerCase()) { - print("delete character " + info.charName); - - control.click(); - Controls.CharSelectDelete.click(); - delay(500); - Controls.CharDeleteYes.click(); - delay(500); - me.blockMouse = false; - - return true; - } - } - } while (control.getNext()); - } - - // check for additional characters up to 24 - if (count === 8 || count === 16) { - if (Controls.CharSelectChar6.click()) { - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - sendKey(0x28); - } - } else { - // no further check necessary - break; - } - - delay(100); - } - - me.blockMouse = false; - - return false; - }, - - getQueueTime: function() { - // You are in line to create a game.,Try joining a game to avoid waiting.,,Your position in line is: ÿc02912 - const text = Controls.CreateGameInLine.getText(); - if (text && text.indexOf(getLocaleString(sdk.locale.text.YourPositionInLineIs)) > -1) { - const result = /ÿc0(\d*)/gm.exec(text); - if (result && typeof result[1] === "string") { - return parseInt(result[1]) || 0; - } - } - - return 0; // You're in line 0, aka no queue - }, - - loginOtherMultiplayer: function () { - MainLoop: - while (true) { - switch (getLocation()) { - case sdk.game.locations.CharSelect: - if (Controls.CharSelectCurrentRealm.control) { - console.log("Not in single player character select screen"); - Controls.CharSelectExit.click(); - - break; - } - - Starter.LocationEvents.login(false); - - break; - case sdk.game.locations.SelectDifficultySP: - Starter.LocationEvents.selectDifficultySP(); - - break; - case sdk.game.locations.SplashScreen: - ControlAction.click(); - - break; - case sdk.game.locations.MainMenu: - if (Profile().type === sdk.game.profiletype.OpenBattlenet) { - // check we are on the correct gateway - let realms = {"west": 0, "east": 1, "asia": 2, "europe": 3}; - ControlAction.clickRealm(realms[Profile().gateway.toLowerCase()]); - try { - login(me.profile); - } catch (e) { - print(e); - } - - break; - } - - Controls.OtherMultiplayer.click(); - - break; - case sdk.game.locations.OtherMultiplayer: - Starter.LocationEvents.otherMultiplayerSelect(); - - break; - case sdk.game.locations.TcpIp: - // handle this in otherMultiplayerSelect - // not sure how to handle enter ip though, should that be left to the starter to decide? - Controls.TcpIpCancel.click(); - - break; - case sdk.game.locations.TcpIpEnterIp: - break MainLoop; - case sdk.game.locations.Login: - login(me.profile); - - break; - case sdk.game.locations.LoginUnableToConnect: - case sdk.game.locations.TcpIpUnableToConnect: - Starter.LocationEvents.unableToConnect(); - - break; - case sdk.game.locations.Lobby: - case sdk.game.locations.LobbyChat: - D2Bot.updateStatus("Lobby"); - - if (me.charname !== Starter.profileInfo.charName) { - Controls.LobbyQuit.click(); - - break; - } - - me.blockKeys = false; - !Starter.firstLogin && (Starter.firstLogin = true); - - break MainLoop; - default: - if (me.ingame) { - break MainLoop; - } - - break; - } - } - - // handling Enter Ip inside entry for now so that location === sucess - return (me.ingame || getLocation() === [sdk.game.locations.TcpIpEnterIp]); - } -}; - -const ShitList = { - create: function () { - let obj = { - shitlist: [] - }; - - let string = JSON.stringify(obj); - - //FileTools.writeText("shitlist.json", string); - Misc.fileAction("shitlist.json", 1, string); - - return obj; - }, - - getObj: function () { - let obj; - let string = Misc.fileAction("shitlist.json", 0); - //string = FileTools.readText("shitlist.json"); - - try { - obj = JSON.parse(string); - } catch (e) { - obj = this.create(); - } - - if (obj) { - return obj; - } - - print("Failed to read ShitList. Using null values"); - - return {shitlist: []}; - }, - - read: function () { - !FileTools.exists("shitlist.json") && this.create(); - - let obj = this.getObj(); - - return obj.shitlist; - }, - - add: function (name) { - let obj = this.getObj(); - - obj.shitlist.push(name); - - let string = JSON.stringify(obj); - - //FileTools.writeText("shitlist.json", string); - Misc.fileAction("shitlist.json", 1, string); - } -}; - -const Starter = { - Config: {}, - useChat: false, - pingQuit: false, - inGame: false, - firstLogin: true, - firstRun: false, - isUp: "no", - loginRetry: 0, - deadCheck: false, - chatActionsDone: false, - gameStart: 0, - gameCount: 0, - lastGameStatus: "ready", - handle: undefined, - connectFail: false, - connectFailRetry: 0, - makeAccount: false, - channelNotify: false, - chanInfo: { - joinChannel: "", - firstMsg: "", - afterMsg: "", - announce: false - }, - gameInfo: {}, - joinInfo: {}, - profileInfo: {}, - - sayMsg: function (string) { - if (!this.useChat) return; - say(string); - }, - - timer: function (tick) { - return " (" + new Date(getTickCount() - tick).toISOString().slice(11, -5) + ")"; - }, - - locationTimeout: function (time, location) { - let endtime = getTickCount() + time; - - while (!me.ingame && getLocation() === location && endtime > getTickCount()) { - delay(500); - } - - return (getLocation() !== location); - }, - - setNextGame: function (gameInfo = {}) { - let nextGame = (gameInfo.gameName || this.randomString(null, true)); - - if ((this.gameCount + 1 >= Starter.Config.ResetCount) || (nextGame.length + this.gameCount + 1 > 15)) { - nextGame += "1"; - } else { - nextGame += (this.gameCount + 1); - } - - DataFile.updateStats("nextGame", nextGame); - }, - - updateCount: function () { - D2Bot.updateCount(); - delay(1000); - Controls.BattleNet.click(); - - try { - login(me.profile); - } catch (e) { - return; - } - - delay(1000); - Controls.CharSelectExit.click(); - }, - - scriptMsgEvent: function (msg) { - if (msg && typeof msg !== "string") return; - switch (msg) { - case "mule": - AutoMule.check = true; - - break; - case "muleTorch": - AutoMule.torchAnniCheck = 1; - - break; - case "muleAnni": - AutoMule.torchAnniCheck = 2; - - break; - case "torch": - TorchSystem.check = true; - - break; - case "crafting": - CraftingSystem.check = true; - - break; - case "getMuleMode": - if (AutoMule.torchAnniCheck === 2) { - scriptBroadcast("2"); - } else if (AutoMule.torchAnniCheck === 1) { - scriptBroadcast("1"); - } else if (AutoMule.check) { - scriptBroadcast("0"); - } - - break; - case "pingquit": - this.pingQuit = true; - - break; - } - }, - - receiveCopyData: function (mode, msg) { - let obj; - - msg === "Handle" && typeof mode === "number" && (Starter.handle = mode); - - switch (mode) { - case 1: // JoinInfo - obj = JSON.parse(msg); - Object.assign(Starter.joinInfo, obj); - - break; - case 2: // Game info - print("Recieved Game Info"); - obj = JSON.parse(msg); - Object.assign(Starter.gameInfo, obj); - - break; - case 3: // Game request - // in case someone is using a lightweight entry like blank/map to play manually and these aren't included - if (typeof AutoMule !== "undefined") { - // Don't let others join mule/torch/key/gold drop game - if (AutoMule.inGame || Gambling.inGame || TorchSystem.inGame || CraftingSystem.inGame) { - break; - } - } - - if (Object.keys(Starter.gameInfo).length) { - obj = JSON.parse(msg); - - if ([sdk.game.profiletype.TcpIpHost, sdk.game.profiletype.TcpIpJoin].includes(Profile().type)) { - me.gameReady && D2Bot.joinMe(obj.profile, me.gameserverip.toString(), "", "", Starter.isUp); - } else { - if (me.gameReady) { - D2Bot.joinMe(obj.profile, me.gamename.toLowerCase(), "", me.gamepassword.toLowerCase(), Starter.isUp); - } else { - D2Bot.joinMe(obj.profile, Starter.gameInfo.gameName.toLowerCase(), Starter.gameCount, Starter.gameInfo.gamePass.toLowerCase(), Starter.isUp); - } - } - } - - break; - case 4: // Heartbeat ping - msg === "pingreq" && sendCopyData(null, me.windowtitle, 4, "pingrep"); - - break; - case 61732: // Cached info retreival - msg !== "null" && (Starter.gameInfo.crashInfo = JSON.parse(msg)); - - break; - case 1638: // getProfile - try { - obj = JSON.parse(msg); - Starter.profileInfo.profile = me.profile; - Starter.profileInfo.account = obj.account; - Starter.profileInfo.charName = obj.Character; - obj.Realm = obj.Realm.toLowerCase(); - Starter.profileInfo.realm = ["east", "west"].includes(obj.Realm) ? "us" + obj.Realm : obj.Realm; - } catch (e) { - print(e); - } - - break; - } - }, - - randomString: function (len, useNumbers = false) { - !len && (len = rand(5, 14)); - - let rval = ""; - let letters = useNumbers ? "abcdefghijklmnopqrstuvwxyz0123456789" : "abcdefghijklmnopqrstuvwxyz"; - - for (let i = 0; i < len; i += 1) { - rval += letters[rand(0, letters.length - 1)]; - } - - return rval; - }, - - randomNumberString: function (len) { - !len && (len = rand(2, 5)); - - let rval = ""; - let vals = "0123456789"; - - for (let i = 0; i < len; i += 1) { - rval += vals[rand(0, vals.length - 1)]; - } - - return rval; - }, - - LocationEvents: { - selectDifficultySP: function () { - let diff = (Starter.gameInfo.difficulty || "Highest"); - diff === "Highest" && (diff = "Hell"); // starts from top with fall-through to select highest - - switch (diff) { - case "Hell": - if (Controls.HellSP.click() && Starter.locationTimeout(1e3, sdk.game.locations.SelectDifficultySP)) { - break; - } - // eslint-disable-next-line no-fallthrough - case "Nightmare": - if (Controls.NightmareSP.click() && Starter.locationTimeout(1e3, sdk.game.locations.SelectDifficultySP)) { - break; - } - // eslint-disable-next-line no-fallthrough - case "Normal": - Controls.NormalSP.click(); - - break; - } - return Starter.locationTimeout(5e3, sdk.game.locations.SelectDifficultySP); - }, - - loginError: function () { - let cdkeyError = false; - let defaultPrint = true; - let string = ""; - let text = (Controls.LoginErrorText.getText() || Controls.LoginInvalidCdKey.getText()); - - if (text) { - for (let i = 0; i < text.length; i += 1) { - string += text[i]; - i !== text.length - 1 && (string += " "); - } - - switch (string) { - case getLocaleString(sdk.locale.text.UsernameIncludedIllegalChars): - case getLocaleString(sdk.locale.text.UsernameIncludedDisallowedwords): - case getLocaleString(sdk.locale.text.UsernameMustBeAtLeast): - case getLocaleString(sdk.locale.text.PasswordMustBeAtLeast): - case getLocaleString(sdk.locale.text.AccountMustBeAtLeast): - case getLocaleString(sdk.locale.text.PasswordCantBeMoreThan): - case getLocaleString(sdk.locale.text.AccountCantBeMoreThan): - D2Bot.printToConsole(string); - D2Bot.stop(); - - break; - case getLocaleString(sdk.locale.text.InvalidPassword): - D2Bot.printToConsole("Invalid Password"); - ControlAction.timeoutDelay("Invalid password delay", Starter.Config.InvalidPasswordDelay * 6e4); - D2Bot.printToConsole("Invalid Password - Restart"); - D2Bot.restart(); - - break; - case getLocaleString(sdk.locale.text.AccountDoesNotExist): - if (!!Starter.Config.MakeAccountOnFailure) { - Starter.makeAccount = true; - Controls.LoginErrorOk.click(); - - return; - } else { - D2Bot.printToConsole(string); - D2Bot.updateStatus(string); - } - - break; - case getLocaleString(sdk.locale.text.AccountIsCorrupted): - case getLocaleString(sdk.locale.text.UnableToCreateAccount): - D2Bot.printToConsole(string); - D2Bot.updateStatus(string); - - break; - case getLocaleString(sdk.locale.text.Disconnected): - D2Bot.updateStatus("Disconnected"); - D2Bot.printToConsole("Disconnected"); - Controls.OkCentered.click(); - Controls.LoginErrorOk.click(); - - return; - case getLocaleString(sdk.locale.text.CdKeyIntendedForAnotherProduct): - case getLocaleString(sdk.locale.text.LoDKeyIntendedForAnotherProduct): - case getLocaleString(sdk.locale.text.CdKeyDisabled): - case getLocaleString(sdk.locale.text.LoDKeyDisabled): - cdkeyError = true; - - break; - case getLocaleString(sdk.locale.text.CdKeyInUseBy): - string += (" " + Controls.LoginCdKeyInUseBy.getText()); - D2Bot.printToConsole(Starter.gameInfo.mpq + " " + string, sdk.colors.D2Bot.Gold); - D2Bot.CDKeyInUse(); - - if (Starter.gameInfo.switchKeys) { - cdkeyError = true; - } else { - Controls.UnableToConnectOk.click(); - ControlAction.timeoutDelay("LoD key in use", Starter.Config.CDKeyInUseDelay * 6e4); - - return; - } - - break; - case getLocaleString(sdk.locale.text.LoginError): - case getLocaleString(sdk.locale.text.OnlyOneInstanceAtATime): - Controls.LoginErrorOk.click(); - Controls.LoginExit.click(); - D2Bot.printToConsole(string); - ControlAction.timeoutDelay("Login Error Delay", 5 * 6e4); - D2Bot.printToConsole("Login Error - Restart"); - D2Bot.restart(); - - break; - default: - D2Bot.updateStatus("Login Error"); - D2Bot.printToConsole("Login Error - " + string); - cdkeyError = true; - defaultPrint = false; - - break; - } - - if (cdkeyError) { - defaultPrint && D2Bot.printToConsole(string + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); - defaultPrint && D2Bot.updateStatus(string); - D2Bot.CDKeyDisabled(); - if (Starter.gameInfo.switchKeys) { - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } else { - D2Bot.stop(); - } - } - - Controls.LoginErrorOk.click(); - delay(1000); - Controls.CharSelectExit.click(); - - while (true) { - delay(1000); - } - } - }, - - charSelectError: function () { - let string = ""; - let text = Controls.CharSelectError.getText(); - let currentLoc = getLocation(); - - if (text) { - for (let i = 0; i < text.length; i += 1) { - string += text[i]; - i !== text.length - 1 && (string += " "); - } - - if (string === getLocaleString(sdk.locale.text.CdKeyDisabledFromRealm)) { - D2Bot.updateStatus("Realm Disabled CDKey"); - D2Bot.printToConsole("Realm Disabled CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); - D2Bot.CDKeyDisabled(); - - if (Starter.gameInfo.switchKeys) { - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } else { - D2Bot.stop(); - } - } - } - - if (!Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, currentLoc)) { - // Click create char button on infinite "connecting" screen - Controls.CharSelectCreate.click(); - delay(1000); - - Controls.CharSelectExit.click(); - delay(1000); - - if (getLocation() !== sdk.game.locations.CharSelectConnecting) return true; - - Controls.CharSelectExit.click(); - Starter.gameInfo.rdBlocker && D2Bot.restart(); - - return false; - } - - return true; - }, - - realmDown: function () { - D2Bot.updateStatus("Realm Down"); - delay(1000); - - if (!Controls.CharSelectExit.click()) return; - - Starter.updateCount(); - ControlAction.timeoutDelay("Realm Down", Starter.Config.RealmDownDelay * 6e4); - D2Bot.CDKeyRD(); - - if (Starter.gameInfo.switchKeys && !Starter.gameInfo.rdBlocker) { - D2Bot.printToConsole("Realm Down - Changing CD-Key"); - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } else { - D2Bot.printToConsole("Realm Down - Restart"); - D2Bot.restart(); - } - }, - - waitingInLine: function () { - let queue = ControlAction.getQueueTime(); - let currentLoc = getLocation(); - - if (queue > 0) { - switch (true) { - case (queue < 10000): - D2Bot.updateStatus("Waiting line... Queue: " + queue); - - // If stuck here for too long, game creation likely failed. Exit to char selection and try again. - if (queue < 10) { - if (!Starter.locationTimeout(Starter.Config.WaitInLineTimeout * 1e3, currentLoc)) { - print("Failed to create game"); - Controls.CancelCreateGame.click(); - Controls.LobbyQuit.click(); - delay(1000); - } - } - - break; - case (queue > 10000): - if (Starter.Config.WaitOutQueueRestriction) { - D2Bot.updateStatus("Waiting out Queue restriction: " + queue); - } else { - print("Restricted... Queue: " + queue); - D2Bot.printToConsole("Restricted... Queue: " + queue, sdk.colors.D2Bot.Red); - Controls.CancelCreateGame.click(); - - if (Starter.Config.WaitOutQueueExitToMenu) { - Controls.LobbyQuit.click(); - delay(1000); - Controls.CharSelectExit.click(); - } - - // Wait out each queue as 1 sec and add extra 10 min - ControlAction.timeoutDelay("Restricted", (queue + 600) * 1000); - } - - break; - } - } - }, - - gameDoesNotExist: function () { - let currentLoc = getLocation(); - console.log("Game doesn't exist"); - - if (Starter.gameInfo.rdBlocker) { - D2Bot.printToConsole(Starter.gameInfo.mpq + " is probably flagged.", sdk.colors.D2Bot.Gold); - - if (Starter.gameInfo.switchKeys) { - ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); - D2Bot.restart(true); - } - } else { - Starter.locationTimeout(Starter.Config.GameDoesNotExistTimeout * 1e3, currentLoc); - } - - Starter.lastGameStatus = "ready"; - }, - - unableToConnect: function () { - let currentLoc = getLocation(); - - if (getLocation() === sdk.game.locations.TcpIpUnableToConnect) { - D2Bot.updateStatus("Unable To Connect TCP/IP"); - Starter.connectFail && ControlAction.timeoutDelay("Unable to Connect", Starter.Config.TCPIPNoHostDelay * 1e3); - Controls.OkCentered.click(); - Starter.connectFail = !Starter.connectFail; - } else { - D2Bot.updateStatus("Unable To Connect"); - - if (Starter.connectFailRetry < 2) { - Starter.connectFailRetry++; - Controls.UnableToConnectOk.click(); - - return; - } - - Starter.connectFailRetry >= 2 && (Starter.connectFail = true); - - if (Starter.connectFail && !Starter.locationTimeout(10e4, currentLoc)) { - let string = ""; - let text = Controls.LoginUnableToConnect.getText(); - - if (text) { - for (let i = 0; i < text.length; i++) { - string += text[i]; - i !== text.length - 1 && (string += " "); - } - } - - switch (string) { - case getLocaleString(sdk.locale.text.UnableToIndentifyVersion): - Controls.UnableToConnectOk.click(); - ControlAction.timeoutDelay("Version error", Starter.Config.VersionErrorDelay * 1000); - - break; - default: // Regular UTC and everything else - Controls.UnableToConnectOk.click(); - ControlAction.timeoutDelay("Unable to Connect", Starter.Config.UnableToConnectDelay * 1000 * 60); - - break; - } - - Starter.connectFail = false; - } - - if (!Controls.UnableToConnectOk.click()) { - return; - } - - Starter.connectFail = true; - Starter.connectFailRetry = 0; - } - }, - - openCreateGameWindow: function () { - let currentLoc = getLocation(); - - if (!Controls.CreateGameWindow.click()) { - return true; - } - - // dead HardCore character - if (Controls.CreateGameWindow.control && Controls.CreateGameWindow.disabled === sdk.game.controls.Disabled) { - if (Starter.Config.StopOnDeadHardcore) { - D2Bot.printToConsole(Profile().character + " has died. They shall be remembered...maybe. Shutting down, better luck next time", sdk.colors.D2Bot.Gold); - D2Bot.stop(); - } else { - D2Bot.printToConsole(Profile().character + " has died. They shall be remembered...maybe. Better luck next time", sdk.colors.D2Bot.Gold); - D2Bot.updateStatus(Profile().character + " has died. They shall be remembered...maybe. Better luck next time"); - Starter.deadCheck = true; - Controls.LobbyQuit.click(); - } - - return false; - } - - // in case create button gets bugged - if (!Starter.locationTimeout(5000, currentLoc)) { - if (!Controls.CreateGameWindow.click()) { - return true; - } - - if (!Controls.JoinGameWindow.click()) { - return true; - } - } - - return (getLocation() === sdk.game.locations.CreateGame); - }, - - openJoinGameWindow: function () { - let currentLoc = getLocation(); - - if (!Controls.JoinGameWindow.click()) { - return; - } - - // in case create button gets bugged - if (!Starter.locationTimeout(5000, currentLoc)) { - if (!Controls.CreateGameWindow.click()) { - return; - } - - if (!Controls.JoinGameWindow.click()) { - return; - } - } - }, - - login: function (otherMultiCheck = false) { - Starter.inGame && (Starter.inGame = false); - if (otherMultiCheck && [sdk.game.gametype.SinglePlayer, sdk.game.gametype.BattleNet].indexOf(Profile().type) === -1) { - return ControlAction.loginOtherMultiplayer(); - } - - if (getLocation() === sdk.game.locations.MainMenu) { - if (Profile().type === sdk.game.profiletype.SinglePlayer - && Starter.firstRun - && Controls.SinglePlayer.click()) { - return true; - } - } - - // Wrong char select screen fix - if (getLocation() === sdk.game.locations.CharSelect) { - hideConsole(); // seems to fix odd crash with single-player characters if the console is open to type in - if ((Profile().type === sdk.game.profiletype.Battlenet && !Controls.CharSelectCurrentRealm.control) - || ((Profile().type !== sdk.game.profiletype.Battlenet && Controls.CharSelectCurrentRealm.control))) { - Controls.CharSelectExit.click(); - - return false; - } - } - - // Multiple realm botting fix in case of R/D or disconnect - Starter.firstLogin && getLocation() === sdk.game.locations.Login && Controls.CharSelectExit.click(); - - D2Bot.updateStatus("Logging In"); - - try { - login(me.profile); - } catch (e) { - if (getLocation() === sdk.game.locations.CharSelect && Starter.loginRetry < 2) { - if (!ControlAction.findCharacter(Starter.profileInfo)) { - // dead hardcore character on sp - if (getLocation() === sdk.game.locations.OkCenteredErrorPopUp) { - // Exit from that pop-up - Controls.OkCentered.click(); - D2Bot.printToConsole("Character died", sdk.colors.D2Bot.Red); - D2Bot.stop(); - } else { - Starter.loginRetry++; - } - } else { - login(me.profile); - } - } else if (getLocation() === sdk.game.locations.TcpIpEnterIp && Profile().type === sdk.game.profiletype.TcpIpJoin) { - return true; // handled in its own case - } else { - print(e + " " + getLocation()); - } - } - - return true; - }, - - otherMultiplayerSelect: function () { - if ([sdk.game.profiletype.TcpIpHost, sdk.game.profiletype.TcpIpJoin].includes(Profile().type)) { - Controls.TcpIp.click() && (Profile().type === sdk.game.profiletype.TcpIpHost ? Controls.TcpIpHost.click() : Controls.TcpIpJoin.click()); - } else if (Profile().type === sdk.game.profiletype.OpenBattlenet) { - Controls.OpenBattleNet.click(); - } else { - Controls.OtherMultiplayerCancel.click(); - } - } - }, -}; +/** + * ControlAction and Starter are very closely related, how should this be handled? + * Starter can probably be cleaned up, maybe taking out LocationEvents as that is mostly what + * interfaces with ControlAction + */ + +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + const v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define(["require", "./modules/Control"], factory); + } else { + Object.assign(root, factory()); + } +}([].filter.constructor("return this")(), function () { + const Controls = require("./modules/Control"); + + const ControlAction = { + mutedKey: false, + realms: { + "uswest": 0, + "west": 0, + "useast": 1, + "east": 1, + "asia": 2, + "europe": 3 + }, + + /** + * @param {string} text + * @param {number} time - in milliseconds + * @param {(arg: any) => boolean} [stopfunc] + * @param {any} [arg] + */ + timeoutDelay: function (text, time, stopfunc, arg) { + let currTime = 0; + let endTime = getTickCount() + time; + Starter.delay = time; + + while (getTickCount() < endTime) { + if (typeof stopfunc === "function" && stopfunc(arg)) { + break; + } + + Starter.delay = Math.max(0, (endTime - getTickCount())); + if (currTime !== Math.floor((endTime - getTickCount()) / 1000)) { + currTime = Math.floor((endTime - getTickCount()) / 1000); + + D2Bot.updateStatus(text + " (" + Math.max(currTime, 0) + "s)"); + } + + delay(10); + } + + Starter.delay = 0; + }, + + /** + * @param {number} type + * @param {number} x + * @param {number} y + * @param {number} xsize + * @param {number} ysize + * @param {number} targetx + * @param {number} targety + * @returns {boolean} + */ + click: function (type, x, y, xsize, ysize, targetx, targety) { + let control = getControl(type, x, y, xsize, ysize); + + if (!control) { + console.error( + "control not found " + type + " " + + x + " " + y + " " + xsize + " " + ysize + + " location " + getLocation() + ); + return false; + } + + control.click(targetx, targety); + + return true; + }, + + /** + * @param {number} type + * @param {number} x + * @param {number} y + * @param {number} xsize + * @param {number} ysize + * @param {string} text + * @returns {boolean} + */ + setText: function (type, x, y, xsize, ysize, text) { + if (!text) return false; + + let control = getControl(type, x, y, xsize, ysize); + if (!control) return false; + + let currText = control.text; + if (currText && currText === text) return true; + + currText = control.getText(); + + if (currText) { + if ((typeof currText === "string" && currText === text) + || (typeof currText === "object" && currText.includes(text))) { + return true; + } + } + + control.setText(text); + + return true; + }, + + /** + * @param {number} type + * @param {number} x + * @param {number} y + * @param {number} xsize + * @param {number} ysize + * @returns {string[] | false} + */ + getText: function (type, x, y, xsize, ysize) { + let control = getControl(type, x, y, xsize, ysize); + + return (!!control ? control.getText() : false); + }, + + /** + * @param {number} type + * @param {number} x + * @param {number} y + * @param {number} xsize + * @param {number} ysize + * @returns {string} + */ + parseText: function (type, x, y, xsize, ysize) { + let control = getControl(type, x, y, xsize, ysize); + if (!control) return ""; + let text = control.getText(); + if (!text || !text.length) return ""; + return text.join(" "); + }, + + // ~~~ Start of general functions ~~~ // + scrollDown: function () { + me.blockMouse = true; + for (let i = 0; i < 4; i++) { + sendKey(sdk.keys.code.DownArrow); + } + me.blockMouse = false; + }, + + clickRealm: function (realm) { + if (realm === undefined || typeof realm !== "number" || realm < 0 || realm > 3) { + throw new Error("clickRealm: Invalid realm!"); + } + + let retry = 0; + + me.blockMouse = true; + + MainLoop: + while (true) { + switch (getLocation()) { + case sdk.game.locations.MainMenu: + let control = Controls.Gateway.control; + if (!control) { + if (retry > 3) return false; + retry++; + + break; + } + + let gateText = getLocaleString(sdk.locale.text.Gateway); + let currentRealm = (() => { + switch (control.text.split(gateText.substring(0, gateText.length - 2))[1]) { + case "U.S. WEST": + return 0; + case "ASIA": + return 2; + case "EUROPE": + return 3; + case "U.S. EAST": + default: + return 1; + } + })(); + + if (currentRealm === realm) { + break MainLoop; + } + + Controls.Gateway.click(); + + break; + case sdk.game.locations.GatewaySelect: + this.click(4, 257, 500, 292, 160, 403, 350 + realm * 25); + Controls.GatewayOk.click(); + + break; + } + + delay(500); + } + + me.blockMouse = false; + + return true; + }, + + /** + * @typedef {Object} CharacterInfo + * @property {string} charName + * @property {string} charClass + * @property {number} charLevel + * @property {boolean} expansion + * @property {boolean} hardcore + * @property {boolean} ladder + */ + + /** + * @param {CharacterInfo} info + * @param {boolean} [startFromTop] + * @returns {Control | false} + */ + findCharacter: function (info, startFromTop = true) { + const singlePlayer = ![sdk.game.gametype.OpenBattlenet, sdk.game.gametype.BattleNet].includes(Profile().type); + // offline doesn't have a character limit cap + const cap = singlePlayer ? 999 : 24; + let count = 0; + let tick = getTickCount(); + let firstCheck; + + while (getLocation() !== sdk.game.locations.CharSelect) { + if (getTickCount() - tick >= 5000) { + break; + } + + delay(25); + } + + // start from beginning of the char list + startFromTop && sendKey(sdk.keys.code.Home); + + while (getLocation() === sdk.game.locations.CharSelect && count < 24) { + let control = Controls.CharSelectCharInfo0.control; + + if (control) { + firstCheck = control.getText(); + do { + let text = control.getText(); + + if (Array.isArray(text) && typeof text[1] === "string") { + count++; + + if (String.isEqual(text[1], info.charName)) { + if (info.ladder && !text.some(el => el.includes("LADDER"))) continue; + // how to check hardcore? + return control; + } + } + } while (count < cap && control.getNext()); + } + + // check for additional characters up to 24 (online) or 999 offline (no character limit cap) + if (count > 0 && count % 8 === 0) { + if (Controls.CharSelectChar6.click()) { + this.scrollDown(); + let check = Controls.CharSelectCharInfo0.control; + + if (firstCheck && check) { + let nameCheck = check.getText(); + + if (String.isEqual(firstCheck[1], nameCheck[1])) { + return false; + } + } + } + } else { + // no further check necessary + break; + } + } + + return false; + }, + + getCharacters: function () { + const singlePlayer = ![sdk.game.gametype.OpenBattlenet, sdk.game.gametype.BattleNet].includes(Profile().type); + // offline doesn't have a character limit cap + const cap = singlePlayer ? 999 : 24; + let count = 0; + let list = []; + let firstCheck; + + // start from beginning of the char list + sendKey(sdk.keys.code.Home); + + while (getLocation() === sdk.game.locations.CharSelect && count < cap) { + let control = Controls.CharSelectCharInfo0.control; + + if (control) { + firstCheck = control.getText(); + do { + let text = control.getText(); + + if (text instanceof Array && typeof text[1] === "string") { + count++; + + if (list.indexOf(text[1]) === -1) { + list.push(text[1]); + } + } + } while (count < cap && control.getNext()); + } + + // check for additional characters up to 24 + if (count > 0 && count % 8 === 0) { + if (Controls.CharSelectChar6.click()) { + this.scrollDown(); + let check = Controls.CharSelectCharInfo0.control; + + if (firstCheck && check) { + let nameCheck = check.getText(); + + if (String.isEqual(firstCheck[1], nameCheck[1])) { + break; + } + } + } + } else { + // no further check necessary + break; + } + } + + // back to beginning of the char list + sendKey(sdk.keys.code.Home); + + return list; + }, + + /** + * @param {CharacterInfo} info + * @returns {boolean} + */ + getPermStatus: function (info) { + let expireStr = getLocaleString(sdk.locale.text.ExpiresIn); + expireStr = expireStr.slice(0, expireStr.indexOf("%")).trim(); + + let control = this.findCharacter(info); + if (!control) return false; + + let text = control.getText(); + if (!Array.isArray(text) || typeof text[1] !== "string") return false; + + return !text.some(el => el.includes(expireStr)); + }, + + /** + * get character position - useless? this doesn't take any arguments to even check the character + * @returns {number} + */ + getPosition: function () { + let position = 0; + + if (getLocation() === sdk.game.locations.CharSelect) { + let control = Controls.CharSelectCharInfo0.control; + + if (control) { + do { + let text = control.getText(); + + if (text instanceof Array && typeof text[1] === "string") { + position += 1; + } + } while (control.getNext()); + } + } + + return position; + }, + + /** + * @param {CharacterInfo} info + * @param {boolean} [randNameOnFail] + * @returns {boolean} + */ + makeCharacter: function (info, randNameOnFail = false) { + try { + me.blockMouse = true; + !info.charClass && (info.charClass = "barbarian"); + if (!info.charName || info.charName.length < 2 || info.charName.length > 15) { + info.charName = Starter.randomString(8, false); + } + info.charName.match(/\d+/g) && (info.charName.replace(/\d+/g, "")); + if (!info.expansion && ["druid", "assassin"].includes(info.charClass)) { + info.expansion = true; + } + + let clickCoords = []; + /** @type {Map el.toLowerCase().includes("expansion"))) { + console.warn(info.charName + " already expansion"); + console.debug(control, "\n", control.getText()); + + return false; + } + + try { + me.blockMouse = true; + console.log("converting character to expansion " + info.charName); + control.click(); + Controls.CharSelectConvert.click(); + delay(500); + Controls.PopupYes.click(); + delay(500); + + return true; + } catch (e) { + console.error(e); + + return false; + } finally { + me.blockMouse = false; + } + }, + + /** + * @param {CharacterInfo} info + * @param {boolean} startFromTop + * @returns {boolean} + */ + loginCharacter: function (info, startFromTop = true) { + me.blockMouse = true; + + try { + MainLoop: + // cycle until in lobby or in game + while (getLocation() !== sdk.game.locations.Lobby) { + switch (getLocation()) { + case sdk.game.locations.CharSelect: + let control = this.findCharacter(info, startFromTop); + if (!control) return false; + + control.click(); + Controls.BottomRightOk.click(); + Starter.locationTimeout(sdk.game.locations.CharSelect, 5000); + + return getLocation() === sdk.game.locations.SelectDifficultySP + ? login(info.profile) + : true; + case sdk.game.locations.CharSelectNoChars: + Controls.BottomLeftExit.click(); + + break; + case sdk.game.locations.Disconnected: + case sdk.game.locations.OkCenteredErrorPopUp: + break MainLoop; + default: + break; + } + + delay(100); + } + + return true; + } catch (e) { + console.error(e); + + return false; + } finally { + me.blockMouse = false; + } + }, + + setEmail: function (email = "", domain = "@email.com") { + if (getLocation() !== sdk.game.locations.RegisterEmail) return false; + if (!email || !email.length) { + email = Starter.randomString(null, true); + } + + while (getLocation() !== sdk.game.locations.CharSelect) { + switch (getLocation()) { + case sdk.game.locations.RegisterEmail: + if (Controls.EmailSetEmail.setText(email + domain) + && Controls.EmailVerifyEmail.setText(email + domain)) { + Controls.EmailRegister.click(); + delay(100); + } + + break; + case sdk.game.locations.LoginError: + // todo test what conditions get here other than email not matching + D2Bot.printToConsole("Failed to set email"); + Controls.LoginErrorOk.click(); + + return false; + case sdk.game.locations.CharSelectNoChars: + // fresh acc + return true; + } + } + + return true; + }, + + makeAccount: function (info) { + me.blockMouse = true; + + let openBnet = Profile().type === sdk.game.profiletype.OpenBattlenet; + + // cycle until in empty char screen + MainLoop: + while (getLocation() !== sdk.game.locations.CharSelectNoChars) { + switch (getLocation()) { + case sdk.game.locations.MainMenu: + ControlAction.clickRealm(this.realms[info.realm]); + if (openBnet) { + Controls.OtherMultiplayer.click() && Controls.OpenBattleNet.click(); + } else { + Controls.BattleNet.click(); + } + + break; + case sdk.game.locations.Login: + Controls.CreateNewAccount.click(); + + break; + case sdk.game.locations.SplashScreen: + Controls.SplashScreen.click(); + + break; + case sdk.game.locations.CharacterCreate: + Controls.BottomLeftExit.click(); + + break; + case sdk.game.locations.TermsOfUse: + Controls.TermsOfUseAgree.click(); + + break; + case sdk.game.locations.CreateNewAccount: + Controls.EnterAccountName.setText(info.account); + Controls.EnterAccountPassword.setText(info.password); + Controls.ConfirmPassword.setText(info.password); + Controls.BottomRightOk.click(); + + break; + case sdk.game.locations.PleaseRead: + Controls.PleaseReadOk.click(); + + break; + case sdk.game.locations.RegisterEmail: + Controls.EmailDontRegisterContinue.control + ? Controls.EmailDontRegisterContinue.click() + : Controls.EmailDontRegister.click(); + + break; + case sdk.game.locations.CharSelect: + if (openBnet) { + break MainLoop; + } + + break; + case sdk.game.locations.LoginError: + Controls.LoginErrorOk.click(); + + return false; + default: + break; + } + + delay(100); + } + + me.blockMouse = false; + + return true; + }, + + loginAccount: function (info) { + me.blockMouse = true; + + let locTick; + let tick = getTickCount(); + + MainLoop: + while (true) { + switch (getLocation()) { + case sdk.game.locations.PreSplash: + break; + case sdk.game.locations.MainMenu: + info.realm && ControlAction.clickRealm(this.realms[info.realm]); + Controls.BattleNet.click(); + + break; + case sdk.game.locations.Login: + Controls.EnterAccountName.setText(info.account); + Controls.EnterAccountPassword.setText(info.password); + Controls.Login.click(); + + break; + case sdk.game.locations.CreateNewAccount: + Controls.BottomLeftExit.click(); + + break; + case sdk.game.locations.LoginUnableToConnect: + case sdk.game.locations.RealmDown: + // Unable to connect, let the caller handle it. + me.blockMouse = false; + + return false; + case sdk.game.locations.CharSelect: + break MainLoop; + case sdk.game.locations.SplashScreen: + Controls.SplashScreen.click(); + + break; + case sdk.game.locations.CharSelectPleaseWait: + case sdk.game.locations.MainMenuConnecting: + case sdk.game.locations.CharSelectConnecting: + break; + case sdk.game.locations.CharSelectNoChars: + // make sure we're not on connecting screen + locTick = getTickCount(); + + while (getTickCount() - locTick < 3000 && getLocation() === sdk.game.locations.CharSelectNoChars) { + delay(25); + } + + if (getLocation() === sdk.game.locations.CharSelectConnecting) { + break; + } + + break MainLoop; // break if we're sure we're on empty char screen + default: + print(getLocation()); + + me.blockMouse = false; + + return false; + } + + if (getTickCount() - tick >= 20000) { + return false; + } + + delay(100); + } + + delay(1000); + + me.blockMouse = false; + + return getLocation() === sdk.game.locations.CharSelect || getLocation() === sdk.game.locations.CharSelectNoChars; + }, + + joinChannel: function (channel) { + me.blockMouse = true; + + let tick; + let rval = false; + let timeout = 5000; + + MainLoop: + while (true) { + switch (getLocation()) { + case sdk.game.locations.Lobby: + Controls.LobbyEnterChat.click(); + + break; + case sdk.game.locations.LobbyChat: + let currChan = Controls.LobbyChannelName.getText(); // returns array + + if (currChan) { + for (let i = 0; i < currChan.length; i += 1) { + if (currChan[i].split(" (") && String.isEqual(currChan[i].split(" (")[0], channel)) { + rval = true; + + break MainLoop; + } + } + } + + !tick && Controls.LobbyChannel.click() && (tick = getTickCount()); + + break; + case sdk.game.locations.ChannelList: // Channel + Controls.LobbyChannelText.setText(channel); + Controls.LobbyChannelOk.click(); + + break; + } + + if (getTickCount() - tick >= timeout) { + break; + } + + delay(100); + } + + me.blockMouse = false; + + return rval; + }, + + createGame: function (name, pass, diff, delay) { + Controls.CreateGameName.setText(name); + Controls.CreateGamePass.setText(pass); + Controls.CreateGameDescription.setText(Starter.Config.GameDescription); + + switch (diff) { + case "Normal": + Controls.Normal.click(); + + break; + case "Nightmare": + Controls.Nightmare.click(); + + break; + case "Highest": + if (Controls.Hell.disabled !== 4 && Controls.Hell.click()) { + diff = "Hell"; + break; + } + + if (Controls.Nightmare.disabled !== 4 && Controls.Nightmare.click()) { + diff = "Nightmare"; + break; + } + + diff = "Normal"; + Controls.Normal.click(); + + break; + default: + Controls.Hell.click(); + + break; + } + + !!delay && this.timeoutDelay("Make Game Delay", delay); + + if (Starter.chanInfo.announce) { + const pType = me.hardcore ? "hc" : "sc"; + const ladder = me.ladder ? "l" : "nl"; + Starter.sayMsg( + "Next game is " + name + + (pass === "" ? "" : "//" + pass) + + " in " + diff + + " on " + (pType + ladder) + ); + } + + me.blockMouse = true; + + console.log("Creating Game: " + name); + Controls.CreateGame.click(); + + me.blockMouse = false; + }, + + getGameList: function () { + let text = Controls.JoinGameList.getText(); + + if (text) { + let gameList = []; + + for (let i = 0; i < text.length; i += 1) { + gameList.push({ + gameName: text[i][0], + players: text[i][1] + }); + } + + return gameList; + } + + return false; + }, + + getQueueTime: function () { + // You are in line to create a game.,Try joining a game to avoid waiting.,,Your position in line is: ÿc02912 + const text = Controls.CreateGameInLine.getText(); + if (text && text.indexOf(getLocaleString(sdk.locale.text.YourPositionInLineIs)) > -1) { + const result = /ÿc0(\d*)/gm.exec(text); + if (result && typeof result[1] === "string") { + return parseInt(result[1]) || 0; + } + } + + return 0; // You're in line 0, aka no queue + }, + + loginOtherMultiplayer: function () { + MainLoop: + while (true) { + switch (getLocation()) { + case sdk.game.locations.CharSelect: + if (Controls.CharSelectCurrentRealm.control) { + console.log("Not in single player character select screen"); + Controls.BottomLeftExit.click(); + + break; + } + + Starter.LocationEvents.login(false); + + break; + case sdk.game.locations.SelectDifficultySP: + Starter.LocationEvents.selectDifficultySP(); + + break; + case sdk.game.locations.SplashScreen: + ControlAction.click(); + + break; + case sdk.game.locations.MainMenu: + if (Profile().type === sdk.game.profiletype.OpenBattlenet) { + // check we are on the correct gateway + let realms = { "west": 0, "east": 1, "asia": 2, "europe": 3 }; + ControlAction.clickRealm(realms[Profile().gateway.toLowerCase()]); + try { + login(me.profile); + } catch (e) { + print(e); + } + + break; + } + + Controls.OtherMultiplayer.click(); + + break; + case sdk.game.locations.OtherMultiplayer: + Starter.LocationEvents.otherMultiplayerSelect(); + + break; + case sdk.game.locations.TcpIp: + // handle this in otherMultiplayerSelect + // not sure how to handle enter ip though, should that be left to the starter to decide? + Controls.TcpIpCancel.click(); + + break; + case sdk.game.locations.TcpIpEnterIp: + break MainLoop; + case sdk.game.locations.Login: + login(me.profile); + + break; + case sdk.game.locations.LoginUnableToConnect: + case sdk.game.locations.TcpIpUnableToConnect: + Starter.LocationEvents.unableToConnect(); + + break; + case sdk.game.locations.Lobby: + case sdk.game.locations.LobbyChat: + D2Bot.updateStatus("Lobby"); + + if (me.charname !== Starter.profileInfo.charName) { + Controls.LobbyQuit.click(); + + break; + } + + me.blockKeys = false; + !Starter.firstLogin && (Starter.firstLogin = true); + + break MainLoop; + default: + if (me.ingame) { + break MainLoop; + } + + break; + } + } + + // handling Enter Ip inside entry for now so that location === sucess + return (me.ingame || getLocation() === [sdk.game.locations.TcpIpEnterIp]); + } + }; + + const Starter = { + Config: require("./starter/StarterConfig"), + AdvancedConfig: require("./starter/AdvancedConfig"), + useChat: false, + pingQuit: false, + inGame: false, + firstLogin: true, + firstRun: false, + isUp: "no", + delay: 0, + loginRetry: 0, + deadCheck: false, + chatActionsDone: false, + gameStart: 0, + gameCount: 0, + lastGameStatus: "ready", + handle: null, + connectFail: false, + connectFailRetry: 0, + makeAccount: false, + channelNotify: false, + chanInfo: { + joinChannel: "", + firstMsg: "", + afterMsg: "", + announce: false + }, + gameInfo: {}, + joinInfo: {}, + profileInfo: {}, + + sayMsg: function (string) { + if (!this.useChat) return; + say(string); + }, + + timer: function (tick) { + return " (" + new Date(getTickCount() - tick).toISOString().slice(11, -5) + ")"; + }, + + locationTimeout: function (time, location) { + let endtime = getTickCount() + time; + + while (!me.ingame && getLocation() === location && endtime > getTickCount()) { + delay(500); + } + + return (getLocation() !== location); + }, + + setNextGame: function (gameInfo = {}) { + let nextGame = (gameInfo.gameName || this.randomString(null, true)); + + if ((this.gameCount + 1 >= Starter.Config.ResetCount) + || (nextGame.length + this.gameCount + 1 > 15)) { + nextGame += "1"; + } else { + nextGame += (this.gameCount + 1); + } + + DataFile.updateStats("nextGame", nextGame); + }, + + updateCount: function () { + D2Bot.updateCount(); + delay(1000); + Controls.BattleNet.click(); + + try { + login(me.profile); + } catch (e) { + return; + } + + delay(1000); + Controls.BottomLeftExit.click(); + }, + + waypointCache: {}, + + scriptMsgEvent: function (msg) { + if (typeof msg === "object" + && msg.hasOwnProperty("type") + && msg.type === "cache-waypoints" + && msg.hasOwnProperty("data") + && Array.isArray(msg.data)) { + + // Upsert array so it exists + let arr = typeof Starter.waypointCache[me.charname] === "object" + ? Starter.waypointCache[me.charname] + // 3 elements of nothing + : Starter.waypointCache[me.charname] = [undefined, undefined, undefined]; + arr[me.diff] = msg.data; + + return; + } + + if (msg && typeof msg !== "string") return; + switch (msg) { + case "mule": + AutoMule.check = true; + + break; + case "muleTorch": + AutoMule.torchAnniCheck = 1; + + break; + case "muleAnni": + AutoMule.torchAnniCheck = 2; + + break; + case "torch": + TorchSystem.check = true; + + break; + case "crafting": + CraftingSystem.check = true; + + break; + case "getMuleMode": + if (AutoMule.torchAnniCheck === 2) { + scriptBroadcast("2"); + } else if (AutoMule.torchAnniCheck === 1) { + scriptBroadcast("1"); + } else if (AutoMule.check) { + scriptBroadcast("0"); + } + + break; + case "pingquit": + Starter.pingQuit = true; + + break; + + case "get-cached-waypoints": + if (!me.ingame) { + break; + } + + if (typeof Starter.waypointCache[me.charname] === "object" + && Starter.waypointCache[me.charname].length === 3) { + let arr = Starter.waypointCache[me.charname]; + const cache = arr[me.diff]; + if (cache) { + scriptBroadcast({ type: "wp-cache", data: cache }); + } + } + + break; + } + }, + + /** + * Handle copy data event + * @param {number} mode + * @param {object | string} msg + */ + receiveCopyData: function (mode, msg) { + if (msg === "Handle" && typeof mode === "number") { + // console.debug("Recieved Handle :: " + mode); + Starter.handle = mode; + + return; + } + + let obj = null; + + switch (mode) { + case 1: // JoinInfo + obj = JSON.parse(msg); + Object.assign(Starter.joinInfo, obj); + + break; + case 2: // Game info + obj = JSON.parse(msg); + Object.assign(Starter.gameInfo, obj); + + break; + case 3: // Game request + // in case someone is using a lightweight entry like blank/map to play manually and these aren't included + if (typeof AutoMule !== "undefined") { + // Don't let others join mule/torch/key/gold drop game + if (AutoMule.inGame || Gambling.inGame || TorchSystem.inGame || CraftingSystem.inGame) { + break; + } + } + + if (Starter.gameInfo.hasOwnProperty("gameName")) { + obj = JSON.parse(msg); + console.debug("Recieved Game Request :: ", obj.profile); + + if ([sdk.game.profiletype.TcpIpHost, sdk.game.profiletype.TcpIpJoin].includes(Profile().type)) { + me.gameReady && D2Bot.joinMe(obj.profile, me.gameserverip.toString(), "", "", Starter.isUp); + } else { + if (me.gameReady) { + D2Bot.joinMe(obj.profile, me.gamename, "", me.gamepassword, Starter.isUp); + } else { + // If we haven't made it to the lobby yet but are already getting game requests, stop the spam by telling followers to delay + let delay = (Starter.delay === 0 && !me.ingame && getLocation() !== sdk.game.locations.CreateGame) + ? 3000 + : Starter.delay; + D2Bot.joinMe( + obj.profile, + Starter.gameInfo.gameName, + Starter.gameCount, + Starter.gameInfo.gamePass, + Starter.isUp, + delay + ); + } + } + } + + break; + case 4: // Heartbeat ping + msg === "pingreq" && sendCopyData(null, me.windowtitle, 4, "pingrep"); + + break; + case 61732: // Cached info retreival + obj = JSON.parse(msg); + msg !== "null" && (Starter.gameInfo.crashInfo = obj); + + break; + case 1638: // getProfile + try { + /** + * @typedef {object} ProfileInfo + * @property {string} Name + * @property {string} Status + * @property {string} Account + * @property {string} Character + * @property {string} Difficulty + * @property {string} Realm + * @property {string} Game + * @property {string} Entry + * @property {string} Tag + */ + /** @type {ProfileInfo} */ + let pObj = JSON.parse(msg); + Starter.profileInfo.profile = me.profile; + Starter.profileInfo.account = pObj.Account || ""; + Starter.profileInfo.charName = pObj.Character || ""; + Starter.profileInfo.difficulty = pObj.Difficulty || ""; + Starter.profileInfo.tag = pObj.Tag || ""; + pObj.Realm = pObj.Realm.toLowerCase(); + Starter.profileInfo.realm = ["east", "west"].includes(pObj.Realm) + ? "us" + pObj.Realm + : pObj.Realm; + } catch (e) { + console.error(e); + } + + break; + } + }, + + randomString: function (len, useNumbers = false) { + !len && (len = rand(5, 14)); + + let rval = ""; + let letters = useNumbers + ? "abcdefghijklmnopqrstuvwxyz0123456789" + : "abcdefghijklmnopqrstuvwxyz"; + + for (let i = 0; i < len; i += 1) { + rval += letters[rand(0, letters.length - 1)]; + } + + return rval; + }, + + randomNumberString: function (len) { + !len && (len = rand(2, 5)); + + let rval = ""; + let vals = "0123456789"; + + for (let i = 0; i < len; i += 1) { + rval += vals[rand(0, vals.length - 1)]; + } + + return rval; + }, + + LocationEvents: (function () { + /** + * @param {Control} control + * @returns {string} + */ + const parseControlText = function (control) { + if (!control) return ""; + let text = control.getText(); + if (!text || !text.length) return ""; + return text.join(" "); + }; + + const _locMap = new Map([ + [sdk.game.locations.LoginError, function () { + let string = parseControlText(Controls.LoginErrorText); + + switch (string) { + case getLocaleString(sdk.locale.text.UsernameIncludedIllegalChars): + case getLocaleString(sdk.locale.text.UsernameIncludedDisallowedwords): + D2Bot.updateStatus("Invalid Account Name"); + D2Bot.printToConsole("Invalid Account Name :: " + Starter.profileInfo.account); + D2Bot.stop(true); + + return false; + case getLocaleString(sdk.locale.text.UnableToCreateAccount): + case getLocaleString(5239): // An account name already exists + if (!Starter.accountExists) { + Starter.accountExists = true; + Controls.LoginErrorOk.click(); + Starter.locationTimeout(1000, sdk.game.locations.LoginError); + Controls.BottomLeftExit.click(); + Starter.locationTimeout(1000, sdk.game.locations.CreateNewAccount); + return true; + } + D2Bot.updateStatus("Account name already exists :: " + Starter.profileInfo.account); + D2Bot.printToConsole("Account name already exists :: " + Starter.profileInfo.account); + D2Bot.stop(true); + + return false; + case getLocaleString(sdk.locale.text.InvalidPassword): + case getLocaleString(5208): // Invalid account + case getLocaleString(sdk.locale.text.AccountDoesNotExist): + if (!!Starter.Config.MakeAccountOnFailure) { + Starter.makeAccount = true; + Controls.LoginErrorOk.click(); + + return true; + } + D2Bot.printToConsole(string); + D2Bot.updateStatus(string); + D2Bot.stop(true); + + return false; + case getLocaleString(sdk.locale.text.CdKeyIntendedForAnotherProduct): + case getLocaleString(sdk.locale.text.LoDKeyIntendedForAnotherProduct): + case getLocaleString(sdk.locale.text.CdKeyDisabled): + case getLocaleString(sdk.locale.text.LoDKeyDisabled): + D2Bot.updateStatus("Disabled CDKey"); + D2Bot.printToConsole("Disabled CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); + D2Bot.CDKeyDisabled(); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.stop(); + } + + break; + case getLocaleString(sdk.locale.text.Disconnected): + ControlAction.timeoutDelay("Disconnected from battle.net", Time.minutes(1)); + return Controls.LoginErrorOk.click(); + case getLocaleString(sdk.locale.text.BattlenetNotResponding): + case getLocaleString(sdk.locale.text.BattlenetNotResponding2): + ControlAction.timeoutDelay("[R/D] - " + string, Time.minutes(10)); + return Controls.LoginErrorOk.click(); + default: + D2Bot.updateStatus("Login Error"); + D2Bot.printToConsole("Login Error - " + string); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.stop(); + } + + break; + } + + return Controls.LoginErrorOk.click(); + }], + [sdk.game.locations.OkCenteredErrorPopUp, function () { + let string = parseControlText(Controls.OkCenteredText); + + switch (string) { + case getLocaleString(sdk.locale.text.CannotCreateGamesDeadHCChar): + Starter.deadCheck = true; + return Controls.OkCentered.click(); + case getLocaleString(sdk.locale.text.UsernameMustBeAtLeast): + case getLocaleString(sdk.locale.text.PasswordMustBeAtLeast): + case getLocaleString(sdk.locale.text.AccountMustBeAtLeast): + case getLocaleString(sdk.locale.text.PasswordCantBeMoreThan): + case getLocaleString(sdk.locale.text.AccountCantBeMoreThan): + case getLocaleString(sdk.locale.text.InvalidPassword): + D2Bot.printToConsole(string); + D2Bot.stop(); + + break; + default: + D2Bot.updateStatus("Error"); + D2Bot.printToConsole("Error - " + string); + + break; + } + Controls.OkCentered.click(); + ControlAction.timeoutDelay("Error", Time.minutes(1)); + + return true; + }], + [sdk.game.locations.CdKeyInUse, function () { + let string = parseControlText(Controls.LoginCdKeyInUseBy); + + if (string === getLocaleString(sdk.locale.text.CdKeyInUseBy)) { + let who = Controls.LoginCdKeyInUseBy.getText(); + D2Bot.printToConsole(Starter.gameInfo.mpq + " " + string + " " + who, sdk.colors.D2Bot.Gold); + D2Bot.CDKeyInUse(); + Controls.UnableToConnectOk.click(); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + ControlAction.timeoutDelay("Cd key in use by: " + who, Starter.Config.CDKeyInUseDelay * 6e4); + } + } + return true; + }], + [sdk.game.locations.InvalidCdKey, function () { + let string = parseControlText(Controls.LoginInvalidCdKey); + + if (string === getLocaleString(sdk.locale.text.CdKeyIntendedForAnotherProduct) + || string === getLocaleString(sdk.locale.text.LoDKeyIntendedForAnotherProduct)) { + D2Bot.updateStatus("Invalid CDKey"); + D2Bot.printToConsole("Invalid CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); + D2Bot.CDKeyDisabled(); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.stop(); + } + } + return true; + }], + ]); + + return { + selectDifficultySP: function () { + let diff = (Starter.gameInfo.difficulty || "Highest"); + diff === "Highest" && (diff = "Hell"); // starts from top with fall-through to select highest + + switch (diff) { + case "Hell": + if (Controls.HellSP.click() + && Starter.locationTimeout(1e3, sdk.game.locations.SelectDifficultySP)) { + break; + } + // eslint-disable-next-line no-fallthrough + case "Nightmare": + if (Controls.NightmareSP.click() + && Starter.locationTimeout(1e3, sdk.game.locations.SelectDifficultySP)) { + break; + } + // eslint-disable-next-line no-fallthrough + case "Normal": + Controls.NormalSP.click(); + + break; + } + return Starter.locationTimeout(5e3, sdk.game.locations.SelectDifficultySP); + }, + + loginError: function () { + let _loc = getLocation(); + + if (_locMap.has(_loc)) { + _locMap.get(_loc)(); + } else { + D2Bot.printToConsole("Unhandled location: " + _loc); + ControlAction.timeoutDelay("Unhandled location: " + _loc, Time.minutes(10)); + D2Bot.restart(); + } + }, + + charSelectError: function () { + let string = parseControlText(Controls.CharSelectError); + let currentLoc = getLocation(); + + if (string) { + if (string === getLocaleString(sdk.locale.text.CdKeyDisabledFromRealm)) { + D2Bot.updateStatus("Realm Disabled CDKey"); + D2Bot.printToConsole("Realm Disabled CDKey: " + Starter.gameInfo.mpq, sdk.colors.D2Bot.Gold); + D2Bot.CDKeyDisabled(); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.stop(); + } + } + } + + if (!Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, currentLoc)) { + // Click create char button on infinite "connecting" screen + Controls.CharSelectCreate.click(); + delay(1000); + + Controls.BottomLeftExit.click(); + delay(1000); + + if (getLocation() !== sdk.game.locations.CharSelectConnecting) return true; + + Controls.BottomLeftExit.click(); + Starter.gameInfo.rdBlocker && D2Bot.restart(); + + return false; + } + + return true; + }, + + realmDown: function () { + D2Bot.updateStatus("Realm Down"); + delay(1000); + + if (!Controls.BottomLeftExit.click()) return; + + Starter.updateCount(); + ControlAction.timeoutDelay("Realm Down", Starter.Config.RealmDownDelay * 6e4); + D2Bot.CDKeyRD(); + + if (Starter.gameInfo.switchKeys && !Starter.gameInfo.rdBlocker) { + D2Bot.printToConsole("Realm Down - Changing CD-Key"); + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } else { + D2Bot.printToConsole("Realm Down - Restart"); + D2Bot.restart(); + } + }, + + waitingInLine: function () { + let queue = ControlAction.getQueueTime(); + let currentLoc = getLocation(); + + if (queue > 0) { + switch (true) { + case (queue < 10000): + D2Bot.updateStatus("Waiting line... Queue: " + queue); + + // If stuck here for too long, game creation likely failed. Exit to char selection and try again. + if (queue < 10) { + if (!Starter.locationTimeout(Starter.Config.WaitInLineTimeout * 1e3, currentLoc)) { + print("Failed to create game"); + Controls.CancelCreateGame.click(); + Controls.LobbyQuit.click(); + delay(1000); + } + } + + break; + case (queue > 10000): + if (Starter.Config.WaitOutQueueRestriction) { + D2Bot.updateStatus("Waiting out Queue restriction: " + queue); + } else { + print("Restricted... Queue: " + queue); + D2Bot.printToConsole("Restricted... Queue: " + queue, sdk.colors.D2Bot.Red); + Controls.CancelCreateGame.click(); + + if (Starter.Config.WaitOutQueueExitToMenu) { + Controls.LobbyQuit.click(); + delay(1000); + Controls.BottomLeftExit.click(); + } + + // Wait out each queue as 1 sec and add extra 10 min + ControlAction.timeoutDelay("Restricted", (queue + 600) * 1000); + } + + break; + } + } + }, + + gameDoesNotExist: function () { + let currentLoc = getLocation(); + console.log("Game doesn't exist"); + + if (Starter.gameInfo.rdBlocker) { + D2Bot.printToConsole(Starter.gameInfo.mpq + " is probably flagged.", sdk.colors.D2Bot.Gold); + + if (Starter.gameInfo.switchKeys) { + ControlAction.timeoutDelay("Key switch delay", Starter.Config.SwitchKeyDelay * 1000); + D2Bot.restart(true); + } + } else { + Starter.locationTimeout(Starter.Config.GameDoesNotExistTimeout * 1e3, currentLoc); + } + + Starter.lastGameStatus = "ready"; + }, + + unableToConnect: function () { + let currentLoc = getLocation(); + + if (getLocation() === sdk.game.locations.TcpIpUnableToConnect) { + D2Bot.updateStatus("Unable To Connect TCP/IP"); + Starter.connectFail && ControlAction.timeoutDelay("Unable to Connect", Starter.Config.TCPIPNoHostDelay * 1e3); + Controls.OkCentered.click(); + Starter.connectFail = !Starter.connectFail; + } else { + D2Bot.updateStatus("Unable To Connect"); + + if (Starter.connectFailRetry < 2) { + Starter.connectFailRetry++; + Controls.UnableToConnectOk.click(); + + return; + } + + Starter.connectFailRetry >= 2 && (Starter.connectFail = true); + + if (Starter.connectFail && !Starter.locationTimeout(10e4, currentLoc)) { + let string = parseControlText(Controls.LoginUnableToConnect); + + switch (string) { + case getLocaleString(sdk.locale.text.UnableToIndentifyVersion): + Controls.UnableToConnectOk.click(); + ControlAction.timeoutDelay("Version error", Starter.Config.VersionErrorDelay * 1000); + + break; + default: // Regular UTC and everything else + Controls.UnableToConnectOk.click(); + ControlAction.timeoutDelay("Unable to Connect", Starter.Config.UnableToConnectDelay * 1000 * 60); + + break; + } + + Starter.connectFail = false; + } + + if (!Controls.UnableToConnectOk.click()) { + return; + } + + Starter.connectFail = true; + Starter.connectFailRetry = 0; + } + }, + + openCreateGameWindow: function () { + let currentLoc = getLocation(); + + if (!Controls.CreateGameWindow.click()) { + return true; + } + + // dead HC character + if (Controls.CreateGameWindow.control + && Controls.CreateGameWindow.disabled === sdk.game.controls.Disabled) { + if (Starter.Config.StopOnDeadHardcore) { + D2Bot.printToConsole( + Profile().character + " has died. They shall be remembered...maybe. Shutting down, better luck next time", + sdk.colors.D2Bot.Gold + ); + D2Bot.stop(); + } else { + D2Bot.printToConsole( + Profile().character + " has died. They shall be remembered...maybe. Better luck next time", + sdk.colors.D2Bot.Gold + ); + D2Bot.updateStatus(Profile().character + " has died. They shall be remembered...maybe. Better luck next time"); + Starter.deadCheck = true; + Controls.LobbyQuit.click(); + } + + return false; + } + + // in case create button gets bugged + if (!Starter.locationTimeout(5000, currentLoc)) { + if (!Controls.CreateGameWindow.click()) { + return true; + } + + if (!Controls.JoinGameWindow.click()) { + return true; + } + } + + return (getLocation() === sdk.game.locations.CreateGame); + }, + + openJoinGameWindow: function () { + let currentLoc = getLocation(); + + if (!Controls.JoinGameWindow.click()) { + return; + } + + // in case create button gets bugged + if (!Starter.locationTimeout(5000, currentLoc)) { + if (!Controls.CreateGameWindow.click()) { + return; + } + + if (!Controls.JoinGameWindow.click()) { + return; + } + } + }, + + login: function (otherMultiCheck = false) { + Starter.inGame && (Starter.inGame = false); + let currLocation = getLocation(); + + if (otherMultiCheck && currLocation === sdk.game.locations.OtherMultiplayer) { + return ControlAction.loginOtherMultiplayer(); + } + + if (currLocation === sdk.game.locations.MainMenu) { + if (Profile().type === sdk.game.profiletype.SinglePlayer + && Starter.firstRun + && Controls.SinglePlayer.click()) { + return true; + } + } + + // Wrong char select screen fix + if (getLocation() === sdk.game.locations.CharSelect) { + hideConsole(); // seems to fix odd crash with single-player characters if the console is open to type in + if ((Profile().type === sdk.game.profiletype.Battlenet && !Controls.CharSelectCurrentRealm.control) + || ((Profile().type !== sdk.game.profiletype.Battlenet && Controls.CharSelectCurrentRealm.control))) { + Controls.BottomLeftExit.click(); + + return false; + } + } + + // Multiple realm botting fix in case of R/D or disconnect + if (Starter.firstLogin && getLocation() === sdk.game.locations.Login) { + Controls.BottomLeftExit.click(); + } + + D2Bot.updateStatus("Logging In"); + + try { + login(me.profile); + } catch (e) { + if (getLocation() === sdk.game.locations.CharSelect && Starter.loginRetry < 2) { + if (!ControlAction.findCharacter(Starter.profileInfo)) { + // dead hardcore character on sp + if (getLocation() === sdk.game.locations.OkCenteredErrorPopUp) { + // Exit from that pop-up + Controls.OkCentered.click(); + D2Bot.printToConsole("Character died", sdk.colors.D2Bot.Red); + D2Bot.stop(); + } else { + Starter.loginRetry++; + } + } else { + login(me.profile); + } + } else if (getLocation() === sdk.game.locations.TcpIpEnterIp && Profile().type === sdk.game.profiletype.TcpIpJoin) { + return true; // handled in its own case + } else { + console.error(e, " " + getLocation()); + } + } + + return true; + }, + + otherMultiplayerSelect: function () { + const pType = Profile().type; + if ([sdk.game.profiletype.TcpIpHost, sdk.game.profiletype.TcpIpJoin].includes(pType)) { + if (Controls.TcpIp.click()) { + pType === sdk.game.profiletype.TcpIpHost + ? Controls.TcpIpHost.click() + : Controls.TcpIpJoin.click(); + } + } else if (pType === sdk.game.profiletype.OpenBattlenet) { + Controls.OpenBattleNet.click(); + } else { + Controls.OtherMultiplayerCancel.click(); + } + } + }; + })(), + }; + + return { + ControlAction: ControlAction, + Starter: Starter, + }; +})); diff --git a/d2bs/kolbot/libs/Polyfill.js b/d2bs/kolbot/libs/Polyfill.js index ad64001b3..8072b41fb 100644 --- a/d2bs/kolbot/libs/Polyfill.js +++ b/d2bs/kolbot/libs/Polyfill.js @@ -1,670 +1,1221 @@ /** * @filename Polyfill.js -* @author Jaenster (probably) -* @desc Some polyfills since we run an old spidermonkey +* @author Jaenster, theBGuy +* @desc Some polyfills since we run old spidermonkey (61f7ebb) * */ +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~ String Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - String.prototype.lcsGraph + * - String.prototype.diffCount + * - String.prototype.includes + * - String.prototype.capitalize + * - String.prototype.padEnd + * - String.prototype.padStart + * - String.prototype.repeat + * - String.prototype.trim + * - String.prototype.startsWith + * - String.prototype.endsWith + * - String.prototype.isEqual + * - String.prototype.format + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + + String.prototype.lcsGraph = function (compareToThis) { - if (!this.length || !compareToThis || !compareToThis.length) { - return null; - } - - let stringA = this.toString().toLowerCase(), stringB = compareToThis.toLowerCase(), graph = Array(this.length), x, - y; - let check = (i, j) => (i < 0 || j < 0 || i >= stringA.length || j >= stringB.length) ? 0 : graph[i][j]; - - for (x = 0; x < stringA.length; x++) { - graph[x] = new Uint16Array(stringB.length); - - for (y = 0; y < stringB.length; y++) { - if (stringA[x] === stringB[y]) { - graph[x][y] = check(x - 1, y - 1) + 1; - } else { - graph[x][y] = Math.max(check(x - 1, y), check(x, y - 1)); - } - } - } - - return {a: this.toString(), b: compareToThis, graph: graph}; + if (!this.length || !compareToThis || !compareToThis.length) { + return null; + } + + let stringA = this.toString().toLowerCase(); + let stringB = compareToThis.toLowerCase(); + let graph = Array(this.length); + let check = (i, j) => (i < 0 || j < 0 || i >= stringA.length || j >= stringB.length) ? 0 : graph[i][j]; + + for (let x = 0; x < stringA.length; x++) { + graph[x] = new Uint16Array(stringB.length); + + for (let y = 0; y < stringB.length; y++) { + if (stringA[x] === stringB[y]) { + graph[x][y] = check(x - 1, y - 1) + 1; + } else { + graph[x][y] = Math.max(check(x - 1, y), check(x, y - 1)); + } + } + } + + return { + a: this.toString(), + b: compareToThis, + graph: graph + }; }; String.prototype.diffCount = function (stringB) { - try { - if (typeof stringB !== "string" || !stringB) { - return this.length; - } + try { + if (typeof stringB !== "string" || !stringB) { + return this.length; + } - if (!this.length) { - return stringB.length; - } + if (!this.length) { + return stringB.length; + } - let graph = this.lcsGraph(stringB); + let graph = this.lcsGraph(stringB); - return (Math.max(graph.a.length, graph.b.length) - graph.graph[graph.a.length - 1][graph.b.length - 1]); - } catch (err) { - print(err.stack); - } + return (Math.max(graph.a.length, graph.b.length) - graph.graph[graph.a.length - 1][graph.b.length - 1]); + } catch (err) { + console.log(err.stack); + } - return Infinity; + return Infinity; }; if (!String.prototype.includes) { - String.prototype.includes = function (search, start) { - "use strict"; - if (typeof start !== "number") { - start = 0; - } - - if (start + search.length > this.length) { - return false; - } else { - return this.indexOf(search, start) !== -1; - } - }; + String.prototype.includes = function (search, start) { + "use strict"; + if (typeof start !== "number") { + start = 0; + } + + if (start + search.length > this.length) { + return false; + } else { + return this.indexOf(search, start) !== -1; + } + }; } String.prototype.capitalize = function (downcase = false) { - return this.charAt(0).toUpperCase() + (downcase ? this.slice(1).toLowerCase() : this.slice(1)); + return this.charAt(0).toUpperCase() + (downcase ? this.slice(1).toLowerCase() : this.slice(1)); }; -Array.prototype.isEqual = function (t) { - return this.map((x, i) => t.hasOwnProperty(i) && x === t[i]).reduce((a, c) => c & a, true); +String.prototype.padEnd = function padEnd (targetLength, padString) { + targetLength = targetLength >> 0; //floor if number or convert non-number to 0; + padString = String(typeof padString !== "undefined" ? padString : " "); + if (this.length > targetLength) { + return String(this); + } else { + targetLength = targetLength - this.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed + } + return String(this) + padString.slice(0, targetLength); + } }; -Array.prototype.filterHighDistance = function (step = 0) { - if (step > 10) return this; // If we took 10 steps, give up - const distances = this.map( - (x, i) => this - .filter((_, index) => index !== i) // Not this element - .map(y => Math.abs(y - this[i])).reduce((a, c) => c + a || 0, 0) / (this.length - 1) // Avg of distance to others - ); - const distancesAvg = distances.reduce((a, c) => c + a || 0, 0) / this.length; - - // Recursion until only viable areas are in the list - if (distancesAvg > 30) return this.filter((x, i) => distances[i] < distancesAvg * 0.75 || this[i] < distancesAvg).filterHighDistance(step++); - - return this; // Everything is relatively the same +String.prototype.padStart = function padStart (targetLength, padString) { + targetLength = targetLength >> 0; //floor if number or convert non-number to 0; + padString = String(typeof padString !== "undefined" ? padString : " "); + if (this.length > targetLength) { + return String(this); + } else { + targetLength = targetLength - this.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed + } + return padString.slice(0, targetLength) + String(this); + } }; -// https://tc39.github.io/ecma262/#sec-array.prototype.findindex -if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, "findIndex", { - value: function (predicate) { - // 1. Let O be ? ToObject(this value). - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } - - let o = Object(this); - - // 2. Let len be ? ToLength(? Get(O, "length")). - let len = o.length >>> 0; - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (typeof predicate !== "function") { - throw new TypeError("predicate must be a function"); - } - - // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. - let thisArg = arguments[1]; - - // 5. Let k be 0. - let k = 0; - - // 6. Repeat, while k < len - while (k < len) { - // a. Let Pk be ! ToString(k). - // b. Let kValue be ? Get(O, Pk). - // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). - // d. If testResult is true, return k. - let kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return k; - } - // e. Increase k by 1. - k++; - } - - // 7. Return -1. - return -1; - }, - configurable: true, - writable: true - }); -} +String.prototype.repeat = function (count) { + "use strict"; + if (this == null) throw new TypeError("can't convert " + this + " to object"); + let str = "" + this; + count = +count; + // eslint-disable-next-line no-self-compare + if (count !== count) { + count = 0; + } + if (count < 0) throw new RangeError("repeat count must be non-negative"); + if (count === Infinity) throw new RangeError("repeat count must be less than infinity"); + + count = Math.floor(count); + if (str.length === 0 || count === 0) { + return ""; + } + if (str.length * count >= 1 << 28) { + throw new RangeError( + "repeat count must not overflow maximum string size" + ); + } + let rpt = ""; + for (;;) { + if ((count & 1) === 1) { + rpt += str; + } + count >>>= 1; + if (count === 0) { + break; + } + str += str; + } + return rpt; +}; -// basic remove prototype -if (!Array.prototype.remove) { - Array.prototype.remove = function (val) { - if (this === undefined || !this.length) throw new Error("No Array defined"); - if (val === undefined || !val) throw new Error("Cannot remove and element if there is no element defined"); - let index = this.indexOf(val); - index >= 0 && this.splice(index, 1); - return this; - }; +// Trim String +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g, ""); + }; } if (!String.prototype.startsWith) { - String.prototype.startsWith = function (prefix) { - return !prefix || this.substring(0, prefix.length) === prefix; - }; + String.prototype.startsWith = function (prefix) { + return !prefix || this.substring(0, prefix.length) === prefix; + }; } if (!String.prototype.endsWith) { - String.prototype.endsWith = function (search, this_len) { - if (this_len === undefined || this_len > this.length) { - this_len = this.length; - } - return this.substring(this_len - search.length, this_len) === search; - }; + String.prototype.endsWith = function (search, this_len) { + if (this_len === undefined || this_len > this.length) { + this_len = this.length; + } + return this.substring(this_len - search.length, this_len) === search; + }; } if (!String.isEqual) { - /** - * Check if two strings are equal - * @static - * @param {string} str1 - * @param {string} str2 - * @returns {boolean} - */ - String.isEqual = function (str1, str2) { - return str1.toLowerCase() === str2.toLowerCase(); - }; + /** + * Check if two strings are equal + * @static + * @param {string} str1 + * @param {string} str2 + * @param {boolean} caseSensitive + * @returns {boolean} + */ + String.isEqual = function (str1, str2, caseSensitive = false) { + if (!isType(str1, "string") || !isType(str2, "string")) return false; + if (caseSensitive) { + return str1 === str2; + } + return str1.toLowerCase() === str2.toLowerCase(); + }; +} + +/** + * Use since we don't have template literals + * Replaces placeholders in a string with provided values. + * + * @param {Array>} pairs - An array of arrays, + * where the first item in each inner array is a placeholder in the form of "$placeholder", + * and the second item is the value to replace it with. + * @returns {string} The formatted string. + */ +String.prototype.format = function (...pairs) { + if (!pairs.length) return this; + let newString = this; + pairs.forEach(function (pair) { + let [match, replace] = pair; + if (match === undefined || replace === undefined) return; + newString = newString.replace(match, replace); + }); + return newString; +}; + +if (!String.prototype.at) { + String.prototype.at = function (pos) { + if (pos < 0) { + pos += this.length; + } + if (pos < 0 || pos >= this.length) return undefined; + return this[pos]; + }; +} + +if (!String.prototype.unshift) { + /** @param {string} str */ + String.prototype.unshift = function (str) { + if (typeof str !== "string") return this; + return str + this; + }; +} + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Array Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Array.prototype.isEqual + * - Array.prototype.filterHighDistance + * - Array.prototype.findIndex + * - Array.prototype.remove + * - Array.prototype.from + * - Array.prototype.filterNull + * - Array.prototype.compactMap + * - Array.prototype.random + * - Array.prototype.shuffle + * - Array.prototype.includes + * - Array.prototype.at + * - Array.prototype.contains + * - Array.prototype.intersection + * - Array.prototype.difference + * - Array.prototype.symmetricDifference + * - Array.prototype.find + * - Array.prototype.fill + * - Array.prototype.first + * - Array.prototype.last + * - Array.prototype.flat + * - Array.of + * - Array.prototype.toSorted + * - Array.prototype.toReversed + * - Array.prototype.toSpliced + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + + +Array.prototype.isEqual = function (t) { + return this.map((x, i) => t.hasOwnProperty(i) && x === t[i]).reduce((a, c) => c & a, true); +}; + +Array.prototype.filterHighDistance = function (step = 0) { + if (step > 10) return this; // If we took 10 steps, give up + const distances = this.map( + (x, i) => this + .filter((_, index) => index !== i) // Not this element + .map(y => Math.abs(y - this[i])).reduce((a, c) => c + a || 0, 0) / (this.length - 1) // Avg of distance to others + ); + const distancesAvg = distances.reduce((a, c) => c + a || 0, 0) / this.length; + + // Recursion until only viable areas are in the list + if (distancesAvg > 30) { + return this + .filter((x, i) => distances[i] < distancesAvg * 0.75 || this[i] < distancesAvg) + .filterHighDistance(step++); + } + + return this; // Everything is relatively the same +}; + +// https://tc39.github.io/ecma262/#sec-array.prototype.findindex +if (!Array.prototype.findIndex) { + Object.defineProperty(Array.prototype, "findIndex", { + value: function (predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + let o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + let len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== "function") { + throw new TypeError("predicate must be a function"); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + let thisArg = arguments[1]; + + // 5. Let k be 0. + let k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). + // d. If testResult is true, return k. + let kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return k; + } + // e. Increase k by 1. + k++; + } + + // 7. Return -1. + return -1; + }, + configurable: true, + writable: true + }); +} + +// basic remove prototype +if (!Array.prototype.remove) { + Array.prototype.remove = function (val) { + if (this === undefined || !this.length) throw new Error("No Array defined"); + if (val === undefined || !val) throw new Error("Cannot remove and element if there is no element defined"); + let index = this.indexOf(val); + index >= 0 && this.splice(index, 1); + return this; + }; } // Production steps of ECMA-262, Edition 6, 22.1.2.1 if (!Array.from) { - Array.from = (function () { - let toStr = Object.prototype.toString; - let isCallable = function (fn) { - return typeof fn === "function" || toStr.call(fn) === "[object Function]"; - }; - let toInteger = function (value) { - let number = Number(value); - if (isNaN(number)) { - return 0; - } - if (number === 0 || !isFinite(number)) { - return number; - } - return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); - }; - let maxSafeInteger = Math.pow(2, 53) - 1; - let toLength = function (value) { - let len = toInteger(value); - return Math.min(Math.max(len, 0), maxSafeInteger); - }; - - // The length property of the from method is 1. - return function from(arrayLike/*, mapFn, thisArg */) { - // 1. Let C be the this value. - let C = this; - - // 2. Let items be ToObject(arrayLike). - let items = Object(arrayLike); - - // 3. ReturnIfAbrupt(items). - if (arrayLike == null) { - throw new TypeError("Array.from requires an array-like object - not null or undefined"); - } - - // 4. If mapfn is undefined, then let mapping be false. - let mapFn = arguments.length > 1 ? arguments[1] : void undefined; - let T; - if (typeof mapFn !== "undefined") { - // 5. else - // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. - if (!isCallable(mapFn)) { - throw new TypeError("Array.from: when provided, the second argument must be a function"); - } - - // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. - if (arguments.length > 2) { - T = arguments[2]; - } - } - - // 10. Let lenValue be Get(items, "length"). - // 11. Let len be ToLength(lenValue). - let len = toLength(items.length); - - // 13. If IsConstructor(C) is true, then - // 13. a. Let A be the result of calling the [[Construct]] internal method - // of C with an argument list containing the single item len. - // 14. a. Else, Let A be ArrayCreate(len). - let A = isCallable(C) ? Object(new C(len)) : new Array(len); - - // 16. Let k be 0. - let k = 0; - // 17. Repeat, while k < len… (also steps a - h) - let kValue; - while (k < len) { - kValue = items[k]; - if (mapFn) { - A[k] = typeof T === "undefined" ? mapFn(kValue, k) : mapFn.call(T, kValue, k); - } else { - A[k] = kValue; - } - k += 1; - } - // 18. Let putStatus be Put(A, "length", len, true). - A.length = len; - // 20. Return A. - return A; - }; - }()); + Array.from = (function () { + let toStr = Object.prototype.toString; + let isCallable = function (fn) { + return typeof fn === "function" || toStr.call(fn) === "[object Function]"; + }; + let toInteger = function (value) { + let number = Number(value); + if (isNaN(number)) { + return 0; + } + if (number === 0 || !isFinite(number)) { + return number; + } + return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); + }; + let maxSafeInteger = Math.pow(2, 53) - 1; + let toLength = function (value) { + let len = toInteger(value); + return Math.min(Math.max(len, 0), maxSafeInteger); + }; + + // The length property of the from method is 1. + return function from (arrayLike/*, mapFn, thisArg */) { + // 1. Let C be the this value. + let C = this; + + // 2. Let items be ToObject(arrayLike). + let items = Object(arrayLike); + + // 3. ReturnIfAbrupt(items). + if (arrayLike == null) { + throw new TypeError("Array.from requires an array-like object - not null or undefined"); + } + + // 4. If mapfn is undefined, then let mapping be false. + let mapFn = arguments.length > 1 ? arguments[1] : void undefined; + let T; + if (typeof mapFn !== "undefined") { + // 5. else + // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. + if (!isCallable(mapFn)) { + throw new TypeError("Array.from: when provided, the second argument must be a function"); + } + + // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. + if (arguments.length > 2) { + T = arguments[2]; + } + } + + // 10. Let lenValue be Get(items, "length"). + // 11. Let len be ToLength(lenValue). + let len = toLength(items.length); + + // 13. If IsConstructor(C) is true, then + // 13. a. Let A be the result of calling the [[Construct]] internal method + // of C with an argument list containing the single item len. + // 14. a. Else, Let A be ArrayCreate(len). + let A = isCallable(C) ? Object(new C(len)) : new Array(len); + + // 16. Let k be 0. + let k = 0; + // 17. Repeat, while k < len… (also steps a - h) + let kValue; + while (k < len) { + kValue = items[k]; + if (mapFn) { + A[k] = typeof T === "undefined" ? mapFn(kValue, k) : mapFn.call(T, kValue, k); + } else { + A[k] = kValue; + } + k += 1; + } + // 18. Let putStatus be Put(A, "length", len, true). + A.length = len; + // 20. Return A. + return A; + }; + }()); } // Filter null or undefined objects in array if (!Array.prototype.filterNull) { - Array.prototype.filterNull = function () { - return this.filter(x => x); - }; + Array.prototype.filterNull = function () { + return this.filter(x => x); + }; } // Map the objects with the callback function and filter null values after mapping. if (!Array.prototype.compactMap) { - Array.prototype.compactMap = function (callback) { - return this.map((x, i, array) => { - if (x == null) { - return null; - } - return callback(x, i, array); - }) - .filterNull(); - }; + Array.prototype.compactMap = function (callback) { + return this.map((x, i, array) => { + if (x == null) { + return null; + } + return callback(x, i, array); + }) + .filterNull(); + }; } // Returns a random object in array if (!Array.prototype.random) { - Array.prototype.random = function () { - return this[Math.floor((Math.random() * this.length))]; - }; + Array.prototype.random = function () { + if (this.length === 0) return null; + if (this.length === 1) return this[0]; + return this[Math.floor((Math.random() * this.length))]; + }; } if (!Array.prototype.includes) { - Array.prototype.includes = function (e) { - return this.indexOf(e) > -1; - }; + Array.prototype.includes = function (e) { + return this.indexOf(e) > -1; + }; } if (!Array.prototype.at) { - Array.prototype.at = function (pos) { - if (pos < 0) { - pos += this.length; - } - if (pos < 0 || pos >= this.length) return undefined; - return this[pos]; - }; + Array.prototype.at = function (pos) { + if (pos < 0) { + pos += this.length; + } + if (pos < 0 || pos >= this.length) return undefined; + return this[pos]; + }; } Array.prototype.contains = Array.prototype.includes; if (!Array.prototype.intersection) { - Array.prototype.intersection = function (other) { - return this.filter(e => other.includes(e)); - }; + Array.prototype.intersection = function (other) { + return this.filter(e => other.includes(e)); + }; } if (!Array.prototype.difference) { - Array.prototype.difference = function (other) { - return this.filter(e => !other.includes(e)); - }; + Array.prototype.difference = function (other) { + return this.filter(e => !other.includes(e)); + }; } if (!Array.prototype.symmetricDifference) { - Array.prototype.symmetricDifference = function (other) { - return this - .filter(e => !other.includes(e)) - .concat(other.filter(e => !this.includes(e))); - }; + Array.prototype.symmetricDifference = function (other) { + return this + .filter(e => !other.includes(e)) + .concat(other.filter(e => !this.includes(e))); + }; } -// Returns a random integer between start and end included. -Math.randomIntBetween = function (start, end) { - let min = Math.ceil(start); - let max = Math.floor(end); - return Math.floor(Math.random() * (max - min + 1)) + min; -}; - // Shuffle Array // http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript if (!Array.prototype.shuffle) { - Array.prototype.shuffle = function () { - let temp, index; - let counter = this.length; - - // While there are elements in the array - while (counter > 0) { - // Pick a random index - index = Math.floor(Math.random() * counter); - - // Decrease counter by 1 - counter -= 1; - - // And swap the last element with it - temp = this[counter]; - this[counter] = this[index]; - this[index] = temp; - } - - return this; - }; -} - -// Trim String -if (!String.prototype.trim) { - String.prototype.trim = function () { - return this.replace(/^\s+|\s+$/g, ""); - }; -} - -// Object.assign polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign -if (typeof Object.assign !== "function") { - Object.defineProperty(Object, "assign", { - value: function assign(target) { - if (target === null) { - throw new TypeError("Cannot convert undefined or null to object"); - } - - let to = Object(target); - - for (let index = 1; index < arguments.length; index++) { - let nextSource = arguments[index]; - - if (nextSource !== null) { - for (let nextKey in nextSource) { - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - - return to; - }, - writable: true, - configurable: true - }); + Array.prototype.shuffle = function () { + let temp, index; + let counter = this.length; + + // While there are elements in the array + while (counter > 0) { + // Pick a random index + index = Math.floor(Math.random() * counter); + + // Decrease counter by 1 + counter -= 1; + + // And swap the last element with it + temp = this[counter]; + this[counter] = this[index]; + this[index] = temp; + } + + return this; + }; } // Array.find polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, "find", { - value: function (predicate) { - if (this === null) { - throw new TypeError('"this" is null or not defined'); - } + Object.defineProperty(Array.prototype, "find", { + value: function (predicate) { + if (this === null) { + throw new TypeError('"this" is null or not defined'); + } - let o = Object(this); + let o = Object(this); - let len = o.length >>> 0; + let len = o.length >>> 0; - if (typeof predicate !== "function") { - throw new TypeError("predicate must be a function"); - } + if (typeof predicate !== "function") { + throw new TypeError("predicate must be a function"); + } - let thisArg = arguments[1]; + let thisArg = arguments[1]; - let k = 0; + let k = 0; - while (k < len) { - let kValue = o[k]; + while (k < len) { + let kValue = o[k]; - if (predicate.call(thisArg, kValue, k, o)) { - return kValue; - } + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } - k++; - } + k++; + } - return undefined; - }, - configurable: true, - writable: true - }); + return undefined; + }, + configurable: true, + writable: true + }); } // Fill an array with the same value from start to end indexes. Array.prototype.fill = function (value, start = 0, end = undefined) { - let stop = end || this.length; - for (let i = start; i < stop; i++) { - this[i] = value; - } + let stop = end || this.length; + for (let i = start; i < stop; i++) { + this[i] = value; + } + return this; }; /** * @description Return the first element or undefined - * @return undefined|* + * @return {undefined | *} */ if (!Array.prototype.first) { - Array.prototype.first = function () { - return this.length > 0 ? this[0] : undefined; - }; + Array.prototype.first = function () { + return this.length > 0 ? this[0] : undefined; + }; } /** * @description Return the last element or undefined - * @return undefined|* + * @return {undefined | *} */ if (!Array.prototype.last) { - Array.prototype.last = function () { - return this.length > 0 ? this[this.length - 1] : undefined; - }; + Array.prototype.last = function () { + return this.length > 0 ? this[this.length - 1] : undefined; + }; } /** * @description Flatten an array with depth parameter. * @see https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/flat - * @return array + * @return {Array<*>} */ if (!Array.prototype.flat) { - Object.defineProperty(Array.prototype, "flat", { - value: function flat() { - let depth = arguments.length > 0 ? isNaN(arguments[0]) ? 1 : Number(arguments[0]) : 1; - - return depth ? Array.prototype.reduce.call(this, function (acc, cur) { - if (Array.isArray(cur)) { - acc.push.apply(acc, flat.call(cur, depth - 1)); - } else { - acc.push(cur); - } - - return acc; - }, []) : Array.prototype.slice.call(this); - }, - configurable: true, - writable: true - }); -} -// eslint-disable-next-line block-scoped-var -if (typeof global === "undefined") { - // eslint-disable-next-line no-var - var global = this; -} - -// eslint-disable-next-line block-scoped-var + Object.defineProperty(Array.prototype, "flat", { + value: function flat () { + let depth = arguments.length > 0 ? isNaN(arguments[0]) ? 1 : Number(arguments[0]) : 1; + + return depth ? Array.prototype.reduce.call(this, function (acc, cur) { + if (Array.isArray(cur)) { + acc.push.apply(acc, flat.call(cur, depth - 1)); + } else { + acc.push(cur); + } + + return acc; + }, []) : Array.prototype.slice.call(this); + }, + configurable: true, + writable: true + }); +} + +/** + * @description The Array.of() static method creates a new Array instance from a + * variable number of arguments, regardless of number or type of the arguments. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of + * @return {Array<...args>} + */ +if (!Array.of) { + Object.defineProperty(Array, "of", { + value: function of () { + return Array.prototype.slice.call(arguments); + }, + configurable: true, + writable: true + }); +} + +/** + * @description The toReversed() method of Array instances is the copying counterpart of the reverse() + * method. It returns a new array with the elements in reversed order. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toReversed + * @return {Array} + */ +if (!Array.prototype.toReversed) { + Array.prototype.toReversed = function () { + return this.slice().reverse(); + }; +} + +/** + * Creates a new array with the elements of the original array sorted in ascending order. + * + * @template T + * @param {function(T, T): number} [compareFunction] A function that defines the sort order. + * If omitted, the elements are sorted in ascending order based on their string conversion. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted + * @returns {Array} A new array with the elements sorted in ascending order. + */ +if (!Array.prototype.toSorted) { + Array.prototype.toSorted = function (compareFunction) { + return this.slice().sort(compareFunction); + }; +} + +/** + * Creates a new array by removing or replacing elements from the original array. + * + * @param {number} start The index at which to start changing the array. + * If negative, it is treated as `array.length + start`. + * @param {number} [deleteCount] The number of elements to remove from the array. + * If omitted or greater than `array.length - start`, all elements from `start` to the end of the array are deleted. + * @param {...*} [items] The elements to add to the array starting from the `start` index. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced + * @returns {Array} A new array with the modified elements. + */ +if (!Array.prototype.toSpliced) { + Array.prototype.toSpliced = function (start, deleteCount) { + const newArr = this.slice(); + const items = Array.prototype.slice.call(arguments, 2); + Array.prototype.splice.apply(newArr, [start, deleteCount].concat(items)); + return newArr; + }; +} + +/** + * @description The with() method of Array instances is the copying version of using the bracket notation to change the value of a given index. + * It returns a new array with the element at the given index replaced with the given value. + * @param {number} index - Zero-based index at which to change the array, converted to an integer. + * @param {*} value - Any value to be assigned to the given index. + * @returns {Array} A new array with the element at index replaced with value. + * @throws {RangeError} If index >= array.length or index < -array.length. + */ +if (!Array.prototype.with) { + Array.prototype.with = function (index, value) { + const len = this.length; + const relativeIndex = index < 0 ? len + index : index; + + if (relativeIndex < 0 || relativeIndex >= len) { + throw new RangeError("Index out of range"); + } + + const newArray = this.slice(); + newArray[relativeIndex] = value; + return newArray; + }; +} + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Object Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Object.assign + * - Object.entries + * - Object.values + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + +/** + * @description Copy the values of all enumerable own properties from one or more source objects to a target object. Returns the target object. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + */ +if (typeof Object.assign !== "function") { + Object.defineProperty(Object, "assign", { + value: function assign (target) { + if (target === null) { + throw new TypeError("Cannot convert undefined or null to object"); + } + + let to = Object(target); + + for (let index = 1; index < arguments.length; index++) { + let nextSource = arguments[index]; + + if (nextSource !== null) { + for (let nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + + return to; + }, + writable: true, + configurable: true + }); +} + +if (!Object.values) { + Object.values = function (source) { + return Object.keys(source) + .map(function (k) { + return source[k]; + }); + }; +} + +if (!Object.entries) { + Object.entries = function (source) { + return Object.keys(source) + .map(function (k) { + return [k, source[k]]; + }); + }; +} + +if (!Object.hasOwn) { + Object.hasOwn = function (obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); + }; +} + +// eslint-disable-next-line no-var +if (typeof global === "undefined") var global = [].filter.constructor("return this")(); +// eslint-disable-next-line dot-notation +global["globalThis"] = [].filter.constructor("return this")(); + if (!global.hasOwnProperty("require")) { - let cache; - // eslint-disable-next-line block-scoped-var - Object.defineProperty(global, "require", { - get: function () { - if (cache) return cache; - !isIncluded("require.js") && include("require.js"); - return cache; // cache is loaded by require.js - }, - set: function(v) { - cache = v; - } - }); -} - -String.prototype.padEnd = function padEnd(targetLength, padString) { - targetLength = targetLength >> 0; //floor if number or convert non-number to 0; - padString = String(typeof padString !== "undefined" ? padString : " "); - if (this.length > targetLength) { - return String(this); - } else { - targetLength = targetLength - this.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed - } - return String(this) + padString.slice(0, targetLength); - } + let cache; + Object.defineProperty(global, "require", { + get: function () { + if (cache) return cache; + !isIncluded("require.js") && include("require.js"); + return cache; // cache is loaded by require.js + }, + set: function (v) { + cache = v; + } + }); +} + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Math Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Math.randomIntBetween + * - Math.trunc + * - Math.percentDifference + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + +// Returns a random integer between start and end included. +Math.randomIntBetween = function (start, end) { + let min = Math.ceil(start); + let max = Math.floor(end); + return Math.floor(Math.random() * (max - min + 1)) + min; +}; + +if (!Math.trunc) { + /** + * Polyfill for Math.trunc + * Static method returns the integer part of a number by removing any fractional digits. + * @static + * @param {number} number + * @returns {number} + */ + Math.trunc = function (number) { + return number < 0 ? Math.ceil(number) : Math.floor(number); + }; +} + +Math.percentDifference = function (value1, value2) { + const diff = Math.abs(value1 - value2); + const average = (value1 + value2) / 2; + const percentDiff = (diff / average) * 100; + return Math.trunc(percentDiff); +}; + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Map Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Map.prototype.forEach + * - Map.prototype.toString + * - Map.prototype.keys + * - Map.prototype.values + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + +if (typeof Map.prototype.forEach !== "function") { + Map.prototype.forEach = function (callbackFn, thisArg) { + thisArg = thisArg || this; + for (let [key, value] of this.entries()) { + callbackFn.call(thisArg, value, key, this); + } + }; +} + +Map.prototype.toString = function () { + let obj = {}; + for (let [key, value] of this.entries()) { + obj[key] = value; + } + return JSON.stringify(obj); }; -String.prototype.padStart = function padStart(targetLength, padString) { - targetLength = targetLength >> 0; //floor if number or convert non-number to 0; - padString = String(typeof padString !== "undefined" ? padString : " "); - if (this.length > targetLength) { - return String(this); - } else { - targetLength = targetLength - this.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed - } - return padString.slice(0, targetLength) + String(this); - } +/** + * @returns {Array} + */ +Map.prototype.keys = function () { + let keys = []; + // eslint-disable-next-line no-unused-vars + for (let [key, _value] of this.entries()) { + keys.push(key); + } + return keys; }; -String.prototype.repeat = function(count) { - "use strict"; - if (this == null) throw new TypeError("can't convert " + this + " to object"); - let str = "" + this; - count = +count; - // eslint-disable-next-line no-self-compare - if (count !== count) { - count = 0; - } - if (count < 0) throw new RangeError("repeat count must be non-negative"); - if (count === Infinity) throw new RangeError("repeat count must be less than infinity"); - - count = Math.floor(count); - if (str.length === 0 || count === 0) { - return ""; - } - if (str.length * count >= 1 << 28) { - throw new RangeError( - "repeat count must not overflow maximum string size" - ); - } - let rpt = ""; - for (;;) { - if ((count & 1) === 1) { - rpt += str; - } - count >>>= 1; - if (count === 0) { - break; - } - str += str; - } - return rpt; +Map.prototype.values = function () { + let values = []; + // eslint-disable-next-line no-unused-vars + for (let [_key, value] of this.entries()) { + values.push(value); + } + return values; }; -if (!Object.values) { - Object.values = function (source) { - return Object.keys(source).map(function (k) { return source[k]; }); - }; +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Set Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Set.prototype.forEach + * - Set.prototype.keys + * - Set.prototype.values + * - Set.prototype.entries + * - Set.prototype.isSuperset + * - Set.prototype.union + * - Set.prototype.intersection + * - Set.prototype.difference + * - Set.prototype.symmetricDifference + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + +if (typeof Set.prototype.forEach !== "function") { + Set.prototype.forEach = function (callbackFn, thisArg) { + thisArg = thisArg || this; + for (let item of this) { + callbackFn.call(thisArg, item, item, this); + } + }; } -if (!Object.entries) { - Object.entries = function (source) { - return Object.keys(source).map(function (k) { return [k, source[k]]; }); - }; +if (typeof Set.prototype.keys !== "function") { + Set.prototype.keys = function () { + let keys = []; + for (let item of this) { + keys.push(item); + } + return keys; + }; +} + +if (typeof Set.prototype.values !== "function") { + Set.prototype.values = function () { + let values = []; + for (let item of this) { + values.push(item); + } + return values; + }; } +if (typeof Set.prototype.entries !== "function") { + Set.prototype.entries = function () { + let entries = []; + for (let item of this) { + entries.push([item, item]); + } + return entries; + }; +} + +Set.prototype.isSuperset = function (subset) { + for (let item of subset) { + if (!this.has(item)) { + return false; + } + } + return true; +}; + +Set.prototype.union = function (setB) { + let union = new Set(this); + for (let item of setB) { + union.add(item); + } + return union; +}; + +Set.prototype.intersection = function (setB) { + let intersection = new Set(); + for (let item of setB) { + if (this.has(item)) { + intersection.add(item); + } + } + return intersection; +}; + +Set.prototype.symmetricDifference = function (setB) { + let difference = new Set(this); + for (let item of setB) { + if (difference.has(item)) { + difference.delete(item); + } else { + difference.add(item); + } + } + return difference; +}; + +Set.prototype.difference = function (setB) { + let difference = new Set(this); + for (let item of setB) { + difference.delete(item); + } + return difference; +}; + +Set.prototype.toString = function () { + let arr = []; + for (let item of this) { + arr.push(item); + } + return JSON.stringify(arr); +}; + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~ console Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - console.log + * - console.debug + * - console.warn + * - console.error + * - console.info + * - console.trace + * - console.time + * - console.timeEnd + * - console.table (partial) + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + (function (global, print) { - global.console = global.console || (function () { - const console = {}; - const argMap = el => typeof el === "object" && el /*not null */ && JSON.stringify(el) || el; - - console.log = function (...args) { - // use call to avoid type errors - print.call(null, args.map(argMap).join(",")); - }; - - console.printDebug = true; - console.debug = function (...args) { - if (console.printDebug) { - const stack = new Error().stack.match(/[^\r\n]+/g); - let filenameAndLine = stack && stack.length && stack[1].substr(stack[1].lastIndexOf("\\") + 1) || "unknown:0"; - filenameAndLine = filenameAndLine.replace(":", " :: "); - this.log("[ÿc:Debugÿc0] ÿc:[" + filenameAndLine + "]ÿc0 " + args.map(argMap).join(",")); - } - }; - - console.warn = function (...args) { - const stack = new Error().stack.match(/[^\r\n]+/g); - let filenameAndLine = stack && stack.length && stack[1].substr(stack[1].lastIndexOf("\\") + 1) || "unknown:0"; - filenameAndLine = filenameAndLine.replace(":", " :: "); - this.log("[ÿc9Warningÿc0] ÿc9[" + filenameAndLine + "]ÿc0 " + args.map(argMap).join(",")); - }; - - console.error = function (error = "") { - let msg, source; - - if (typeof error === "string") { - msg = error; - } else { - source = error.fileName.substring(error.fileName.lastIndexOf("\\") + 1, error.fileName.length); - msg = "ÿc1[" + source + " :: " + error.lineNumber + "] ÿc0" + error.message; - } - - this.log("[ÿc1Errorÿc0] " + msg); - }; - - const timers = {}; - console.time = function (name) { - name && (timers[name] = getTickCount()); - }; - - console.timeEnd = function (name) { - let currTimer = timers[name]; - if (currTimer) { - this.log("[ÿc7Timerÿc0] :: ÿc8" + name + " - ÿc4Durationÿc0: " + (getTickCount() - currTimer) + "ms"); - delete timers[name]; - } - }; - - console.trace = function () { - let stackLog = ""; - let stack = new Error().stack; - if (stack) { - stack = stack.split("\n"); - stack && typeof stack === "object" && stack.reverse(); - - for (let i = 0; i < stack.length - 1; i += 1) { - if (stack[i]) { - stackLog += stack[i].substr(0, stack[i].indexOf("@") + 1) + stack[i].substr(stack[i].lastIndexOf("\\") + 1, stack[i].length - 1); - i < stack.length - 1 && (stackLog += ", "); - } - } - - this.log("[ÿc;StackTraceÿc0] :: " + stackLog); - } - }; - - console.info = function (start = false, msg = "", timer = "") { - let stack = new Error().stack.match(/[^\r\n]+/g); - let funcName = stack[1].substr(0, stack[1].indexOf("@")); - let logInfo = start === true ? "[ÿc2Start " : start === false ? "[ÿc1End " : "[ÿc8"; - logInfo += (funcName + "ÿc0] :: " + (msg ? msg : "")); - if (timer) { - let currTimer = timers[timer]; - if (currTimer) { - let tFormat = (getTickCount() - currTimer); - // if less than 1 second, display in ms - tFormat > 1000 ? (tFormat = Time.format(tFormat)) : (tFormat += " ms"); - logInfo += (" - ÿc4Durationÿc0: " + tFormat); - delete timers[timer]; - } - } - this.log(logInfo); - }; - - return console; - - })(); + global.console = global.console || (function () { + const console = {}; + + const argMap = function (el) { + switch (typeof el) { + case "undefined": + return "undefined"; + case "boolean": + return el ? "true" : false; + case "function": + return "function"; + case "object": + if (el === null) return "null"; + if (el instanceof Error) { + return JSON.stringify({ + name: (el.name || "Error"), + fileName: (el.fileName || "unknown"), + lineNumber: (el.lineNumber || ":?"), + message: (el.message || ""), + stack: (el.stack || ""), + }); + } + if (el instanceof Map) { + return el.toString(); + } else if (el instanceof Set) { + return el.toString(); + } + if (Array.isArray(el)) { + // handle multidimensional arrays + return JSON.stringify( + el.map(function (inner) { + return Array.isArray(inner) ? inner.map(argMap) : inner; + }) + ); + } + return JSON.stringify(el); + } + return el; + }; + + console.log = function (...args) { + // use call to avoid type errors + print.call(null, args.map(argMap).join(",")); + }; + + console.printDebug = true; + console.debug = function (...args) { + if (console.printDebug) { + const stack = new Error().stack.match(/[^\r\n]+/g); + let filenameAndLine = stack && stack.length && stack[1].substr(stack[1].lastIndexOf("\\") + 1) || "unknown:0"; + filenameAndLine = filenameAndLine.replace(":", " :: "); + this.log("[ÿc:Debugÿc0] ÿc:[" + filenameAndLine + "]ÿc0 " + args.map(argMap).join(",")); + } + }; + + console.warn = function (...args) { + const stack = new Error().stack.match(/[^\r\n]+/g); + let filenameAndLine = stack && stack.length && stack[1].substr(stack[1].lastIndexOf("\\") + 1) || "unknown:0"; + filenameAndLine = filenameAndLine.replace(":", " :: "); + this.log("[ÿc9Warningÿc0] ÿc9[" + filenameAndLine + "]ÿc0 " + args.map(argMap).join(",")); + }; + + console.error = function (error = "") { + let msg, source; + + if (typeof error === "string") { + msg = error; + } else { + source = error.fileName.substring(error.fileName.lastIndexOf("\\") + 1, error.fileName.length); + msg = "ÿc1[" + source + " :: " + error.lineNumber + "] ÿc0" + error.message; + } + + this.log("[ÿc1Errorÿc0] " + msg); + }; + + /** @type {Map} */ + const timers = new Map(); + + /** + * @param {string} name + */ + console.time = function (name) { + name && timers.set(name, getTickCount()); + }; + + /** + * @param {string} name + */ + console.timeEnd = function (name) { + let currTimer = timers.get(name); + if (currTimer) { + this.log("[ÿc7Timerÿc0] :: ÿc8" + name + " - ÿc4Durationÿc0: " + (getTickCount() - currTimer) + "ms"); + timers.delete(name); + } + }; + + console.trace = function () { + let stackLog = ""; + let stack = new Error().stack; + if (stack) { + stack = stack.split("\n"); + stack && typeof stack === "object" && stack.reverse(); + + for (let i = 0; i < stack.length - 1; i += 1) { + if (stack[i]) { + stackLog += stack[i] + .substr( + 0, stack[i].indexOf("@") + 1) + stack[i].substr(stack[i].lastIndexOf("\\") + 1, stack[i].length - 1 + ); + i < stack.length - 1 && (stackLog += ", "); + } + } + + this.log("[ÿc;StackTraceÿc0] :: " + stackLog); + } + }; + + console.info = function (start = false, msg = "", timer = "") { + const stack = new Error().stack.match(/[^\r\n]+/g); + let funcName = stack[1].substr(0, stack[1].indexOf("@")); + let logInfo = start === true + ? "[ÿc2Start " + : start === false + ? "[ÿc1End " + : "[ÿc8"; + logInfo += (funcName + "ÿc0] :: " + (msg ? msg : "")); + if (timer) { + let currTimer = timers.get(timer); + if (currTimer) { + let tFormat = (getTickCount() - currTimer); + // if less than 1 second, display in ms + tFormat > 1000 ? (tFormat = Time.format(tFormat)) : (tFormat += " ms"); + logInfo += (" - ÿc4Durationÿc0: " + tFormat); + timers.delete(timer); + } else { + this.time(timer); + } + } + this.log(logInfo); + }; + + /** + * @param {object | any[]} data + * @param {string[]} [columns] + */ + console.table = function (data, columns) { + if (data === undefined) return; + + let output = ""; + let table = []; + let row = []; + + // Create table headers + if (!columns) { + columns = Object.keys(data[0]); + } + row = columns; + table.push(row); + + // Create table rows + for (let i = 0; i < data.length; i++) { + row = []; + for (let j = 0; j < columns.length; j++) { + row.push(data[i][columns[j]]); + } + table.push(row); + } + + // todo - get longest element and adjust the output of that column to stay within the header bars + let maxLengths = new Array(table[0].length).fill(0); + + for (let i = 0; i < table.length; i++) { + for (let j = 0; j < table[i].length; j++) { + maxLengths[j] = Math.max(maxLengths[j], table[i][j].toString().length); + } + } + console.log(maxLengths); + + // Create table output + for (let i = 0; i < table.length; i++) { + for (let j = 0; j < table[i].length; j++) { + // output += "| " + table[i][j] + " "; + output += "| " + table[i][j].toString().padEnd(maxLengths[j]) + " "; + } + output += "|\n"; + } + + // // Log table to console + console.log(output); + + // for (let i = 0; i < data.length; i++) { + // let row = "|"; + // for (let j = 0; j < data[i].length; j++) { + // row += " " + data[i][j].toString().padEnd(maxLengths[j]) + " |"; + // } + // console.log(row); + // } + }; + + return console; + + })(); })([].filter.constructor("return this")(), print); + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Date Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Date.prototype.dateStamp + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + +if (!Date.prototype.hasOwnProperty("dateStamp")) { + Object.defineProperty(Date.prototype, "dateStamp", { + value: function () { + let month = this.getMonth() + 1; + let day = this.getDate(); + let year = this.getFullYear(); + return "[" + (month < 10 ? "0" + month : month) + "/" + (day < 10 ? "0" + day : day) + "/" + year + "]"; + } + }); +} diff --git a/d2bs/kolbot/libs/StarterConfig.js b/d2bs/kolbot/libs/StarterConfig.js deleted file mode 100644 index c4517f1af..000000000 --- a/d2bs/kolbot/libs/StarterConfig.js +++ /dev/null @@ -1,78 +0,0 @@ -/** -* @filename StarterConfig.js -* @author theBGuy -* @desc Global settings for entry scripts -* -*/ -!isIncluded("OOG.js") && include("OOG.js"); - -Starter.Config = { - MinGameTime: 360, // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby - PingQuitDelay: 30, // Time in seconds to wait in lobby after quitting due to high ping - CreateGameDelay: rand(5, 15), // Seconds to wait before creating a new game - ResetCount: 999, // Reset game count back to 1 every X games. - CharacterDifference: 99, // Character level difference. Set to false to disable character difference. - MaxPlayerCount: 8, // Max amount of players in game between 1 and 8 - StopOnDeadHardcore: true, // Stop profile character has died on hardcore mode - - // ChannelConfig can override these options for individual profiles. - JoinChannel: "", // Default channel. - FirstJoinMessage: "", // Default join message. Can be an array of messages - ChatActionsDelay: 2, // Seconds to wait in lobby before entering a channel - AnnounceGames: false, // Default value - AfterGameMessage: "", // Default message after a finished game. Can be an array of messages - - InvalidPasswordDelay: 10, // Minutes to wait after getting Invalid Password message - VersionErrorDelay: rand(5, 30), // Seconds to wait after 'unable to identify version' message - SwitchKeyDelay: 5, // Seconds to wait before switching a used/banned key or after realm down - CrashDelay: rand(120, 150), // Seconds to wait after a d2 window crash - FTJDelay: 120, // Seconds to wait after failing to create a game - RealmDownDelay: 3, // Minutes to wait after getting Realm Down message - UnableToConnectDelay: 5, // Minutes to wait after Unable To Connect message - TCPIPNoHostDelay: 5, // Seconds to wait after Cannot Connect To Server message - CDKeyInUseDelay: 5, // Minutes to wait before connecting again if CD-Key is in use. - ConnectingTimeout: 60, // Seconds to wait before cancelling the 'Connecting...' screen - PleaseWaitTimeout: 60, // Seconds to wait before cancelling the 'Please Wait...' screen - WaitInLineTimeout: 3600, // Seconds to wait before cancelling the 'Waiting in Line...' screen - WaitOutQueueRestriction: true, // Wait out queue if we are restricted, queue time > 10000 - WaitOutQueueExitToMenu: false, // Wait out queue restriction at D2 Splash screen if true, else wait out in lobby - GameDoesNotExistTimeout: 30, // Seconds to wait before cancelling the 'Game does not exist.' screen -}; - -// Advanced config - you don't have to edit this unless you need some of the features provided -const AdvancedConfig = { - /* Features: - Override channel for each profile, Override join delay for each profile - Override default values for JoinChannel, FirstJoinMessage, AnnounceGames and AfterGameMessage per profile - - * Format *: - "Profile Name": {JoinDelay: number_of_seconds} - or - "Profile Name": {JoinChannel: "channel name"} - or - "Profile Name": {JoinChannel: "channel name", JoinDelay: number_of_seconds} - - * Example * (don't edit this - it's just an example): - - "MyProfile1": {JoinDelay: 3}, - "MyProfile2": {JoinChannel: "some channel"}, - "MyProfile3": {JoinChannel: "some other channel", JoinDelay: 11} - "MyProfile4": {AnnounceGames: true, AnnounceMessage: "Joining game"} // announce game you are joining - - "Profile Name": { - JoinChannel: "channel name", - FirstJoinMessage: "first message", -OR- ["join msg 1", "join msg 2"], - AnnounceGames: true, - AfterGameMessage: "message after a finished run" -OR- ["msg 1", msg 2"] - } - */ - - // Put your lines under this one. Multiple entries are separated by commas. No comma after the last one. - - "Test": { - JoinChannel: "op nnqry", - JoinDelay: 3, - AnnounceGames: true, - AnnounceMessage: "Joining game" // output: Joining game Baals-23 - }, -}; diff --git a/d2bs/kolbot/libs/TorchSystem.js b/d2bs/kolbot/libs/TorchSystem.js deleted file mode 100644 index 8f07d5060..000000000 --- a/d2bs/kolbot/libs/TorchSystem.js +++ /dev/null @@ -1,373 +0,0 @@ -/** -* @filename TorchSystem.js -* @author kolton -* @desc Works in conjunction with OrgTorch script. Allows the uber killer to get keys from other profiles. -* -*/ - -const TorchSystem = { - FarmerProfiles: { - // ############################ S E T U P ########################################## - - /* Each uber killer profile can have their own army of key finders - Multiple entries are separated with a comma - Example config: - - "Farmer 1": { // Farmer profile name - // Put key finder profiles here. Example - KeyFinderProfiles: ["MF 1", "MF 2"], - KeyFinderProfiles: ["mf 1", "mf 2"], - - // Put the game name of uber killer here (without numbers). Key finders will join this game to drop keys. Example - FarmGame: "Ubers-", - FarmGame: "torch1-" - }, - - "Farmer 2": { // Farmer profile name - // Put key finder profiles here. Example - KeyFinderProfiles: ["MF 1", "MF 2"], - KeyFinderProfiles: ["mf 3", "mf 4"], - - // Put the game name of uber killer here (without numbers). Key finders will join this game to drop keys. Example - FarmGame: "Ubers-", - FarmGame: "torch2-" - } - */ - - // Edit here! - - "PROFILE NAME": { // Farmer profile name - // Put key finder profiles here. Example - KeyFinderProfiles: ["MF 1", "MF 2"], - KeyFinderProfiles: [""], - - // Put the game name of uber killer here (without numbers). Key finders will join this game to drop keys. Example - FarmGame: "Ubers-", - FarmGame: "" - } - - // ################################################################################# - }, - - // Don't touch - inGame: false, - check: false, - - getFarmers: function () { - let list = []; - - for (let i in this.FarmerProfiles) { - if (this.FarmerProfiles.hasOwnProperty(i)) { - for (let j = 0; j < this.FarmerProfiles[i].KeyFinderProfiles.length; j += 1) { - if (this.FarmerProfiles[i].KeyFinderProfiles[j].toLowerCase() === me.profile.toLowerCase()) { - this.FarmerProfiles[i].profile = i; - - list.push(this.FarmerProfiles[i]); - } - } - } - } - - return list.length > 0 ? list : false; - }, - - isFarmer: function () { - if (this.FarmerProfiles.hasOwnProperty(me.profile)) { - this.FarmerProfiles[me.profile].profile = me.profile; - - return this.FarmerProfiles[me.profile]; - } - - return false; - }, - - inGameCheck: function () { - let farmers = this.getFarmers(); - if (!farmers) return false; - - for (let i = 0; i < farmers.length; i += 1) { - if (farmers[i].FarmGame.length > 0 && me.gamename.toLowerCase().match(farmers[i].FarmGame.toLowerCase())) { - print("ÿc4Torch Systemÿc0: In Farm game."); - D2Bot.printToConsole("Torch System: Transfering keys.", sdk.colors.D2Bot.DarkGold); - D2Bot.updateStatus("Torch System: In game."); - Town.goToTown(1); - - if (Town.openStash()) { - let neededItems = this.keyCheck(); - - if (neededItems) { - for (let n in neededItems) { - if (neededItems.hasOwnProperty(n)) { - while (neededItems[n].length) { - neededItems[n].shift().drop(); - } - } - } - } - } - - if (me.getStat(sdk.stats.Gold) >= 100000) { - gold(100000); - } - - delay(5000); - quit(); - - return true; - } - } - - return false; - }, - - keyCheck: function () { - let neededItems = {}; - let farmers = this.getFarmers(); - if (!farmers) return false; - - function keyCheckEvent(mode, msg) { - if (mode === 6) { - let obj = JSON.parse(msg); - - if (obj.name === "neededItems") { - let item; - - for (let i in obj.value) { - if (obj.value.hasOwnProperty(i) && obj.value[i] > 0) { - switch (i) { - case "pk1": - case "pk2": - case "pk3": - item = me.getItem(i); - - if (item) { - do { - if (!neededItems[i]) { - neededItems[i] = []; - } - - neededItems[i].push(copyUnit(item)); - - if (neededItems[i].length >= obj.value[i]) { - break; - } - } while (item.getNext()); - } - - break; - case "rv": - item = me.getItem(); - - if (item) { - do { - if (item.code === "rvs" || item.code === "rvl") { - if (!neededItems[i]) { - neededItems[i] = []; - } - - neededItems[i].push(copyUnit(item)); - - if (neededItems[i].length >= Math.min(2, obj.value[i])) { - break; - } - } - } while (item.getNext()); - } - - break; - } - } - } - } - } - } - - addEventListener("copydata", keyCheckEvent); - - // TODO: one mfer for multiple farmers handling - for (let i = 0; i < farmers.length; i += 1) { - sendCopyData(null, farmers[i].profile, 6, JSON.stringify({name: "keyCheck", profile: me.profile})); - delay(250); - - if (neededItems.hasOwnProperty("pk1") || neededItems.hasOwnProperty("pk2") || neededItems.hasOwnProperty("pk3")) { - removeEventListener("copydata", keyCheckEvent); - - return neededItems; - } - } - - removeEventListener("copydata", keyCheckEvent); - - return false; - }, - - outOfGameCheck: function () { - if (!this.check) return false; - this.check = false; - - let game; - - function checkEvent(mode, msg) { - let farmers = TorchSystem.getFarmers(); - - if (mode === 6) { - let obj = JSON.parse(msg); - - if (obj && obj.name === "gameName") { - for (let i = 0; i < farmers.length; i += 1) { - if (obj.value.gameName.toLowerCase().match(farmers[i].FarmGame.toLowerCase())) { - game = [obj.value.gameName, obj.value.password]; - } - } - } - } - - return true; - } - - let farmers = this.getFarmers(); - if (!farmers) return false; - - addEventListener("copydata", checkEvent); - - for (let i = 0; i < farmers.length; i += 1) { - sendCopyData(null, farmers[i].profile, 6, JSON.stringify({name: "gameCheck", profile: me.profile})); - delay(500); - - if (game) { - break; - } - } - - removeEventListener("copydata", checkEvent); - - if (game) { - delay(2000); - - this.inGame = true; - me.blockMouse = true; - - joinGame(game[0], game[1]); - - me.blockMouse = false; - - delay(5000); - - while (me.ingame) { - delay(1000); - } - - this.inGame = false; - - return true; - } - - return false; - }, - - waitForKeys: function () { - let timer = getTickCount(); - let busy = false; - let busyTick; - let tkeys = me.findItems("pk1", sdk.items.mode.inStorage).length || 0; - let hkeys = me.findItems("pk2", sdk.items.mode.inStorage).length || 0; - let dkeys = me.findItems("pk3", sdk.items.mode.inStorage).length || 0; - let neededItems = {pk1: 0, pk2: 0, pk3: 0, rv: 0}; - - // Check whether the killer is alone in the game - this.aloneInGame = function () { - return (Misc.getPlayerCount() <= 1); - }; - - // Check if current character is the farmer - let farmer = TorchSystem.isFarmer(); - - this.torchSystemEvent = function (mode, msg) { - let obj, farmer; - - if (mode === 6) { - farmer = TorchSystem.isFarmer(); - - if (farmer) { - obj = JSON.parse(msg); - - if (obj) { - switch (obj.name) { - case "gameCheck": - if (busy) { - break; - } - - if (farmer.KeyFinderProfiles.includes(obj.profile)) { - print("Got game request from: " + obj.profile); - sendCopyData(null, obj.profile, 6, JSON.stringify({name: "gameName", value: {gameName: me.gamename, password: me.gamepassword}})); - - busy = true; - busyTick = getTickCount(); - } - - break; - case "keyCheck": - if (farmer.KeyFinderProfiles.includes(obj.profile)) { - print("Got key count request from: " + obj.profile); - - // Get the number of needed keys - neededItems = {pk1: 3 - tkeys, pk2: 3 - hkeys, pk3: 3 - dkeys, rv: this.juvCheck()}; - sendCopyData(null, obj.profile, 6, JSON.stringify({name: "neededItems", value: neededItems})); - } - - break; - } - } - } - } - }; - - // Register event that will communicate with key hunters, go to Act 1 town and wait by stash - addEventListener("copydata", this.torchSystemEvent); - Town.goToTown(1); - Town.move("stash"); - - while (true) { - // Abort if the current character isn't a farmer - if (!farmer) { - break; - } - - // Free up inventory - Town.needStash() && Town.stash(); - - // Get the number keys - tkeys = me.findItems("pk1", sdk.items.mode.inStorage).length || 0; - hkeys = me.findItems("pk2", sdk.items.mode.inStorage).length || 0; - dkeys = me.findItems("pk3", sdk.items.mode.inStorage).length || 0; - - // Stop the loop if we have enough keys or if wait time expired - if (((tkeys >= 3 && hkeys >= 3 && dkeys >= 3) - || (Config.OrgTorch.WaitTimeout && (getTickCount() - timer > Config.OrgTorch.WaitTimeout * 1000 * 60))) - && this.aloneInGame()) { - removeEventListener("copydata", this.torchSystemEvent); - - break; - } - - if (busy) { - while (getTickCount() - busyTick < 30000) { - if (!this.aloneInGame()) { - break; - } - - delay(100); - } - - if (getTickCount() - busyTick > 30000 || this.aloneInGame()) { - busy = false; - } - } - - // Wait for other characters to leave - while (!this.aloneInGame()) { - delay(500); - } - - delay(1000); - - // Pick the keys after the hunters drop them and leave the game - Pickit.pickItems(); - } - }, -}; diff --git a/d2bs/kolbot/libs/UnitInfo.js b/d2bs/kolbot/libs/UnitInfo.js deleted file mode 100644 index a15422eca..000000000 --- a/d2bs/kolbot/libs/UnitInfo.js +++ /dev/null @@ -1,208 +0,0 @@ -/** -* @filename UnitInfo.js -* @author kolton, theBGuy -* @desc Display unit info -* -*/ -include("common/prototypes.js"); - -const UnitInfo = new function () { - this.x = 200; - this.y = 250; - this.hooks = []; - this.cleared = true; - this.resfix = {x: (me.screensize ? 0 : -160), y: (me.screensize ? 0 : -120)}; - - this.createInfo = function (unit) { - if (typeof unit === "undefined") { - this.remove(); - - return; - } - - switch (unit.type) { - case sdk.unittype.Player: - this.playerInfo(unit); - - break; - case sdk.unittype.Monster: - this.monsterInfo(unit); - - break; - case sdk.unittype.Object: - case sdk.unittype.Stairs: - this.objectInfo(unit); - - break; - case sdk.unittype.Item: - this.itemInfo(unit); - - break; - } - }; - - this.playerInfo = function (unit) { - !this.currentGid && (this.currentGid = unit.gid); - - if (this.currentGid === unit.gid && !this.cleared) { - return; - } - - if (this.currentGid !== unit.gid) { - this.remove(); - this.currentGid = unit.gid; - } - - let string; - let frameXsize = 0; - let frameYsize = 20; - let quality = ["ÿc0", "ÿc0", "ÿc0", "ÿc0", "ÿc3", "ÿc2", "ÿc9", "ÿc4", "ÿc8"]; - let items = unit.getItemsEx(); - - this.hooks.push(new Text("Classid: ÿc0" + unit.classid, this.x, this.y, 4, 13, 2)); - - if (items.length) { - this.hooks.push(new Text("Equipped items:", this.x, this.y + 15, 4, 13, 2)); - frameYsize += 15; - - for (let i = 0; i < items.length; i += 1) { - if (items[i].getFlag(sdk.items.flags.Runeword)) { - string = items[i].fname.split("\n")[1] + "ÿc0 " + items[i].fname.split("\n")[0]; - } else { - string = quality[items[i].quality] + (items[i].quality > 4 && items[i].getFlag(sdk.items.flags.Identified) ? items[i].fname.split("\n").reverse()[0].replace("ÿc4", "") : items[i].name); - } - - this.hooks.push(new Text(string, this.x, this.y + (i + 2) * 15, 0, 13, 2)); - string.length > frameXsize && (frameXsize = string.length); - frameYsize += 15; - } - } - - this.cleared = false; - - this.hooks.push(new Box(this.x + 2, this.y - 15, Math.round(frameXsize * 7.5) - 4, frameYsize, 0x0, 1, 2)); - this.hooks.push(new Frame(this.x, this.y - 15, Math.round(frameXsize * 7.5), frameYsize, 2)); - this.hooks[this.hooks.length - 2].zorder = 0; - }; - - this.monsterInfo = function (unit) { - !this.currentGid && (this.currentGid = unit.gid); - - if (this.currentGid === unit.gid && !this.cleared) { - return; - } - - if (this.currentGid !== unit.gid) { - this.remove(); - this.currentGid = unit.gid; - } - - let frameYsize = 125; - - this.hooks.push(new Text("Classid: ÿc0" + unit.classid, this.x, this.y, 4, 13, 2)); - this.hooks.push(new Text("HP percent: ÿc0" + Math.round(unit.hp * 100 / 128), this.x, this.y + 15, 4, 13, 2)); - this.hooks.push(new Text("Fire resist: ÿc0" + unit.getStat(sdk.stats.FireResist), this.x, this.y + 30, 4, 13, 2)); - this.hooks.push(new Text("Cold resist: ÿc0" + unit.getStat(sdk.stats.ColdResist), this.x, this.y + 45, 4, 13, 2)); - this.hooks.push(new Text("Lightning resist: ÿc0" + unit.getStat(sdk.stats.LightResist), this.x, this.y + 60, 4, 13, 2)); - this.hooks.push(new Text("Poison resist: ÿc0" + unit.getStat(sdk.stats.PoisonResist), this.x, this.y + 75, 4, 13, 2)); - this.hooks.push(new Text("Physical resist: ÿc0" + unit.getStat(sdk.stats.DamageResist), this.x, this.y + 90, 4, 13, 2)); - this.hooks.push(new Text("Magic resist: ÿc0" + unit.getStat(sdk.stats.MagicResist), this.x, this.y + 105, 4, 13, 2)); - - this.cleared = false; - - this.hooks.push(new Box(this.x + 2, this.y - 15, 136, frameYsize, 0x0, 1, 2)); - this.hooks.push(new Frame(this.x, this.y - 15, 140, frameYsize, 2)); - this.hooks[this.hooks.length - 2].zorder = 0; - }; - - this.itemInfo = function (unit) { - !this.currentGid && (this.currentGid = unit.gid); - - if (this.currentGid === unit.gid && !this.cleared) { - return; - } - - if (this.currentGid !== unit.gid) { - this.remove(); - this.currentGid = unit.gid; - } - - let xpos = 60; - let ypos = (me.getMerc() ? 80 : 20) + (-1 * this.resfix.y); - let frameYsize = 50; - - this.hooks.push(new Text("Code: ÿc0" + unit.code, xpos, ypos + 0, 4, 13, 2)); - this.hooks.push(new Text("Classid: ÿc0" + unit.classid, xpos, ypos + 15, 4, 13, 2)); - this.hooks.push(new Text("Item Type: ÿc0" + unit.itemType, xpos, ypos + 30, 4, 13, 2)); - this.hooks.push(new Text("Item level: ÿc0" + unit.ilvl, xpos, ypos + 45, 4, 13, 2)); - - this.cleared = false; - this.socketedItems = unit.getItems(); - - if (this.socketedItems) { - this.hooks.push(new Text("Socketed with:", xpos, ypos + 60, 4, 13, 2)); - frameYsize += 30; - - for (let i = 0; i < this.socketedItems.length; i += 1) { - this.hooks.push(new Text(this.socketedItems[i].fname.split("\n").reverse().join(" "), xpos, ypos + (i + 5) * 15, 0, 13, 2)); - - frameYsize += 15; - } - } - - if (unit.magic && unit.identified) { - this.hooks.push(new Text("Prefix: ÿc0" + unit.prefixnum, xpos, ypos + frameYsize - 5, 4, 13, 2)); - this.hooks.push(new Text("Suffix: ÿc0" + unit.suffixnum, xpos, ypos + frameYsize + 10, 4, 13, 2)); - - frameYsize += 30; - } - - if (unit.runeword) { - this.hooks.push(new Text("Prefix: ÿc0" + unit.prefixnum, xpos, ypos + frameYsize - 5, 4, 13, 2)); - - frameYsize += 15; - } - - this.hooks.push(new Box(xpos + 2, ypos - 15, 116, frameYsize, 0x0, 1, 2)); - this.hooks.push(new Frame(xpos, ypos - 15, 120, frameYsize, 2)); - this.hooks[this.hooks.length - 2].zorder = 0; - }; - - this.objectInfo = function (unit) { - !this.currentGid && (this.currentGid = unit.gid); - - if (this.currentGid === unit.gid && !this.cleared) { - return; - } - - if (this.currentGid !== unit.gid) { - this.remove(); - this.currentGid = unit.gid; - } - - let frameYsize = 35; - - this.hooks.push(new Text("Type: ÿc0" + unit.type, this.x, this.y, 4, 13, 2)); - this.hooks.push(new Text("Classid: ÿc0" + unit.classid, this.x, this.y + 15, 4, 13, 2)); - - if (!!unit.objtype) { - this.hooks.push(new Text("Destination: ÿc0" + unit.objtype, this.x, this.y + 30, 4, 13, 2)); - - frameYsize += 15; - } - - this.cleared = false; - - this.hooks.push(new Box(this.x + 2, this.y - 15, 116, frameYsize, 0x0, 1, 2)); - this.hooks.push(new Frame(this.x, this.y - 15, 120, frameYsize, 2)); - this.hooks[this.hooks.length - 2].zorder = 0; - }; - - this.remove = function () { - while (this.hooks.length > 0) { - this.hooks.shift().remove(); - } - - this.cleared = true; - }; -}; diff --git a/d2bs/kolbot/libs/bots/Abaddon.js b/d2bs/kolbot/libs/bots/Abaddon.js deleted file mode 100644 index 5eb7ff990..000000000 --- a/d2bs/kolbot/libs/bots/Abaddon.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename Abaddon.js -* @author kolton -* @desc clear Abaddon -* -*/ - -function Abaddon() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.FrigidHighlands); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.FrigidHighlands, sdk.unittype.Object, sdk.objects.RedPortal) || !Pather.usePortal(sdk.areas.Abaddon)) { - throw new Error("Failed to move to Abaddon"); - } - - Attack.clearLevel(Config.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/AncientTunnels.js b/d2bs/kolbot/libs/bots/AncientTunnels.js deleted file mode 100644 index bebb6abcd..000000000 --- a/d2bs/kolbot/libs/bots/AncientTunnels.js +++ /dev/null @@ -1,29 +0,0 @@ -/** -* @filename AncientTunnels.js -* @author kolton -* @desc clear Ancient Tunnels -* -*/ - -function AncientTunnels() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.LostCity); - Precast.doPrecast(true); - - try { - Config.AncientTunnels.OpenChest && Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.SuperChest) && Misc.openChests(5) && Pickit.pickItems(); - } catch (e) { - console.error(e); - } - - try { - Config.AncientTunnels.KillDarkElder && Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.DarkElder) && Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.DarkElder)); - } catch (e) { - console.error(e); - } - - if (!Pather.moveToExit(sdk.areas.AncientTunnels, true)) throw new Error("Failed to move to Ancient Tunnels"); - Attack.clearLevel(Config.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Andariel.js b/d2bs/kolbot/libs/bots/Andariel.js deleted file mode 100644 index 2c3b77f48..000000000 --- a/d2bs/kolbot/libs/bots/Andariel.js +++ /dev/null @@ -1,36 +0,0 @@ -/** -* @filename Andariel.js -* @author kolton -* @desc kill Andariel -* -*/ - -function Andariel () { - this.killAndariel = function () { - let target = Game.getMonster(sdk.monsters.Andariel); - if (!target) throw new Error("Andariel not found."); - - Config.MFLeader && Pather.makePortal() && say("kill " + sdk.monsters.Andariel); - - for (let i = 0; i < 300 && target.attackable; i += 1) { - ClassAttack.doAttack(target); - target.distance <= 10 && Pather.moveTo(me.x > 22548 ? 22535 : 22560, 9520); - } - - return target.dead; - }; - - Town.doChores(); - Pather.useWaypoint(sdk.areas.CatacombsLvl2); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true)) throw new Error("Failed to move to Catacombs Level 4"); - - Pather.moveTo(22549, 9520); - me.sorceress && me.classic ? this.killAndariel() : Attack.kill(sdk.monsters.Andariel); - - delay(2000); // Wait for minions to die. - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/AutoBaal.js b/d2bs/kolbot/libs/bots/AutoBaal.js deleted file mode 100644 index 8e59b86bb..000000000 --- a/d2bs/kolbot/libs/bots/AutoBaal.js +++ /dev/null @@ -1,239 +0,0 @@ -/** -* @filename AutoBaal.js -* @author kolton -* @desc Universal Baal leecher by Kolton with Autoleader by Ethic -* Pure leech script for throne and Baal -* Reenters throne/chamber upon death and picks the corpse back up -* Make sure you setup safeMsg and baalMsg accordingly -* -*/ - -/** -* @todo: -* - add silent follow support -* - needs to be in a way that doesn't interfere with normal following -*/ - -function AutoBaal() { - // internal variables - let i, baalCheck, throneCheck, hotCheck, leader; // internal variables - const safeMsg = ["safe", "throne clear", "leechers can come", "tp is up", "1 clear"]; // safe message - casing doesn't matter - const baalMsg = ["baal"]; // baal message - casing doesn't matter - const hotMsg = ["hot", "warm", "dangerous", "lethal"]; // used for shrine hunt - - // chat event handler function, listen to what leader says - addEventListener("chatmsg", function (nick, msg) { - // filter leader messages - if (nick === leader) { - // loop through all predefined messages to find a match - for (let i = 0; i < hotMsg.length; i += 1) { - // leader says a hot tp message - if (msg.toLowerCase().includes(hotMsg[i].toLowerCase()) && Config.AutoBaal.FindShrine === 1) { - hotCheck = true; // safe to enter baal chamber - - break; - } - } - - // loop through all predefined messages to find a match - for (let i = 0; i < safeMsg.length; i += 1) { - // leader says a safe tp message - if (msg.toLowerCase().includes(safeMsg[i].toLowerCase())) { - throneCheck = true; // safe to enter throne - - break; - } - } - - // loop through all predefined messages to find a match - for (let i = 0; i < baalMsg.length; i += 1) { - // leader says a baal message - if (msg.toLowerCase().includes(baalMsg[i].toLowerCase())) { - baalCheck = true; // safe to enter baal chamber - - break; - } - } - } - }); - - // test - maybe factor this out and make it useable for other leecher scripts? - this.longRangeSupport = function () { - switch (me.classid) { - case sdk.player.class.Necromancer: - ClassAttack.raiseArmy(50); - - if (Config.Curse[1] > 0) { - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.attackable && monster.distance < 50 && !checkCollision(me, monster, sdk.collision.Ranged) - && monster.curseable && !monster.isSpecial && ClassAttack.canCurse(monster, Config.Curse[1])) { - Skill.cast(Config.Curse[1], sdk.skills.hand.Right, monster); - } - } while (monster.getNext()); - } - } - - break; - case sdk.player.class.Assassin: - if (Config.UseTraps && ClassAttack.checkTraps({x: 15095, y: 5037})) { - ClassAttack.placeTraps({x: 15095, y: 5037}, 5); - } - - break; - default: - break; - } - - let skills = [ - sdk.skills.ChargedStrike, sdk.skills.Lightning, sdk.skills.FireWall, sdk.skills.Meteor, sdk.skills.Blizzard, - sdk.skills.BoneSpear, sdk.skills.BoneSpirit, sdk.skills.DoubleThrow, sdk.skills.Volcano - ]; - - if (!skills.some(skill => Config.AttackSkill[1] === skill || Config.AttackSkill[3] === skill)) { - return false; - } - - let monster = Game.getMonster(); - let monList = []; - - if (monster) { - do { - if (monster.attackable && monster.distance < 50 && !checkCollision(me, monster, sdk.collision.Ranged)) { - monList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - if (me.inArea(sdk.areas.ThroneofDestruction)) { - [15116, 5026].distance > 10 && Pather.moveTo(15116, 5026); - } - - let oldVal = Skill.usePvpRange; - Skill.usePvpRange = true; - - try { - while (monList.length) { - monList.sort(Sort.units); - monster = copyUnit(monList[0]); - - if (monster && monster.attackable) { - let index = monster.isSpecial ? 1 : 3; - - if (Config.AttackSkill[index] > -1 && Attack.checkResist(monster, Attack.getSkillElement(Config.AttackSkill[index]))) { - ClassAttack.doCast(monster, Config.AttackSkill[index], Config.AttackSkill[index + 1]); - } else { - monList.shift(); - } - } else { - monList.shift(); - } - - delay(5); - } - } finally { - Skill.usePvpRange = oldVal; - } - - return true; - }; - - // critical error - can't reach harrogath - if (!Town.goToTown(5)) throw new Error("Town.goToTown failed."); - - if (Config.Leader) { - leader = Config.Leader; - if (!Misc.poll(() => Misc.inMyParty(leader), Time.seconds(30), Time.seconds(1))) throw new Error("AutoBaal: Leader not partied"); - } - - Config.AutoBaal.FindShrine === 2 && (hotCheck = true); - - Town.doChores(); - Town.move("portalspot"); - - // find the first player in throne of destruction - if (leader || (leader = Misc.autoLeaderDetect({destination: sdk.areas.ThroneofDestruction, quitIf: (area) => [sdk.areas.WorldstoneChamber].includes(area)}))) { - // do our stuff while partied - while (Misc.inMyParty(leader)) { - if (hotCheck) { - if (Config.AutoBaal.FindShrine) { - Pather.useWaypoint(sdk.areas.StonyField); - Precast.doPrecast(true); - - for (i = sdk.areas.StonyField; i > 1; i--) { - if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { - break; - } - } - - if (i === 1) { - Town.goToTown(); - Pather.useWaypoint(sdk.areas.DarkWood); - - for (i = sdk.areas.DarkWood; i < sdk.areas.DenofEvil; i++) { - if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { - break; - } - } - } - } - - Town.goToTown(5); - Town.move("portalspot"); - - hotCheck = false; - } - - // wait for throne signal - leader's safe message - if ((throneCheck || baalCheck) && me.inArea(sdk.areas.Harrogath)) { - print("ÿc4AutoBaal: ÿc0Trying to take TP to throne."); - Pather.usePortal(sdk.areas.ThroneofDestruction, null); - // move to a safe spot - Pather.moveTo(Config.AutoBaal.LeechSpot[0], Config.AutoBaal.LeechSpot[1]); - Precast.doPrecast(true); - Town.getCorpse(); - } - - !baalCheck && me.inArea(sdk.areas.ThroneofDestruction) && Config.AutoBaal.LongRangeSupport && this.longRangeSupport(); - - // wait for baal signal - leader's baal message - if (baalCheck && me.inArea(sdk.areas.ThroneofDestruction)) { - // move closer to chamber portal - Pather.moveTo(15092, 5010); - Precast.doPrecast(false); - - // wait for baal to go through the portal - while (Game.getMonster(sdk.monsters.ThroneBaal)) { - delay(500); - } - - let portal = Game.getObject(sdk.objects.WorldstonePortal); - - delay(2000); // wait for others to enter first - helps with curses and tentacles from spawning around you - print("ÿc4AutoBaal: ÿc0Entering chamber."); - Pather.usePortal(null, null, portal) && Pather.moveTo(15166, 5903); // go to a safe position - Town.getCorpse(); - } - - let baal = Game.getMonster(sdk.monsters.Baal); - - if (baal) { - if (baal.dead) { - break; - } - - this.longRangeSupport(); - } - - me.mode === sdk.player.mode.Dead && me.revive(); - - delay(500); - } - } else { - throw new Error("Empty game."); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Baal.js b/d2bs/kolbot/libs/bots/Baal.js deleted file mode 100644 index 2e5c0270d..000000000 --- a/d2bs/kolbot/libs/bots/Baal.js +++ /dev/null @@ -1,94 +0,0 @@ -/** -* @filename Baal.js -* @author kolton, YGM, theBGuy -* @desc clear Throne of Destruction and kill Baal -* -*/ - -function Baal() { - this.announce = function () { - let count, string, souls, dolls; - let monster = Game.getMonster(); - - if (monster) { - count = 0; - - do { - if (monster.attackable && monster.y < 5094) { - monster.distance <= 40 && (count += 1); - !souls && monster.classid === sdk.monsters.BurningSoul1 && (souls = true); - !dolls && monster.classid === sdk.monsters.SoulKiller && (dolls = true); - } - } while (monster.getNext()); - } - - if (count > 30) { - string = "DEADLY!!!" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; - } else if (count > 20) { - string = "Lethal!" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; - } else if (count > 10) { - string = "Dangerous!" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; - } else if (count > 0) { - string = "Warm" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; - } else { - string = "Cool TP. No immediate monsters."; - } - - if (souls) { - string += " Souls "; - dolls && (string += "and Dolls "); - string += "in area."; - } else if (dolls) { - string += " Dolls in area."; - } - - say(string); - }; - - Town.doChores(); - !!Config.RandomPrecast ? Precast.doRandomPrecast(true, sdk.areas.WorldstoneLvl2) : Pather.useWaypoint(sdk.areas.WorldstoneLvl2) && Precast.doPrecast(true); - !me.inArea(sdk.areas.WorldstoneLvl2) && Pather.useWaypoint(sdk.areas.WorldstoneLvl2); - - if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], true)) { - throw new Error("Failed to move to Throne of Destruction."); - } - - Pather.moveTo(15095, 5029); - - if (Config.Baal.DollQuit && Game.getMonster(sdk.monsters.SoulKiller)) { - say("Dolls found! NG."); - - return true; - } - - if (Config.Baal.SoulQuit && Game.getMonster(sdk.monsters.BurningSoul1)) { - say("Souls found! NG."); - - return true; - } - - if (Config.PublicMode) { - this.announce(); - Pather.moveTo(15118, 5002); - Pather.makePortal(); - say(Config.Baal.HotTPMessage); - Attack.clear(15); - } - - Common.Baal.clearThrone(); - - if (Config.PublicMode) { - Pather.moveTo(15118, 5045); - Pather.makePortal(); - say(Config.Baal.SafeTPMessage); - Precast.doPrecast(true); - } - - if (!Common.Baal.clearWaves()) { - throw new Error("Couldn't clear baal waves"); - } - - Config.Baal.KillBaal && Common.Baal.killBaal(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/BaalAssistant.js b/d2bs/kolbot/libs/bots/BaalAssistant.js deleted file mode 100644 index f80cdbee4..000000000 --- a/d2bs/kolbot/libs/bots/BaalAssistant.js +++ /dev/null @@ -1,433 +0,0 @@ -/** -* @filename BaalAssistant.js -* @author kolton, YGM, theBGuy -* @desc Help or Leech Baal Runs. -* -*/ - -// todo - combine autobaal, baalhelper, and baalassistant into one script -// todo - track leaders area so we can do silent follow - -function BaalAssistant () { - let Leader = Config.Leader; - let Helper = Config.BaalAssistant.Helper; - let hotCheck = false; - let safeCheck = false; - let baalCheck = false; - let ngCheck = false; - let ShrineStatus = false; - let secondAttempt = false; - let throneStatus = false; - let firstAttempt = true; - let killTracker = false; - - // convert all messages to lowercase - Config.BaalAssistant.HotTPMessage.forEach((msg, i) => { - Config.BaalAssistant.HotTPMessage[i] = msg.toLowerCase(); - }); - - Config.BaalAssistant.SafeTPMessage.forEach((msg, i) => { - Config.BaalAssistant.SafeTPMessage[i] = msg.toLowerCase(); - }); - - Config.BaalAssistant.BaalMessage.forEach((msg, i) => { - Config.BaalAssistant.BaalMessage[i] = msg.toLowerCase(); - }); - - Config.BaalAssistant.NextGameMessage.forEach((msg, i) => { - Config.BaalAssistant.NextGameMessage[i] = msg.toLowerCase(); - }); - - addEventListener("chatmsg", - function (nick, msg) { - if (nick === Leader) { - msg = msg.toLowerCase(); - - for (let i = 0; i < Config.BaalAssistant.HotTPMessage.length; i += 1) { - if (msg.includes(Config.BaalAssistant.HotTPMessage[i])) { - hotCheck = true; - break; - } - } - - for (let i = 0; i < Config.BaalAssistant.SafeTPMessage.length; i += 1) { - if (msg.includes(Config.BaalAssistant.SafeTPMessage[i])) { - safeCheck = true; - break; - } - } - - for (let i = 0; i < Config.BaalAssistant.BaalMessage.length; i += 1) { - if (msg.includes(Config.BaalAssistant.BaalMessage[i])) { - baalCheck = true; - break; - } - } - - for (let i = 0; i < Config.BaalAssistant.NextGameMessage.length; i += 1) { - if (msg.includes(Config.BaalAssistant.NextGameMessage[i])) { - ngCheck = true; - killTracker = true; - break; - } - } - } - }); - - this.checkParty = function () { - for (let i = 0; i < Config.BaalAssistant.Wait; i += 1) { - let partycheck = getParty(); - if (partycheck) { - do { - if (partycheck.area === sdk.areas.ThroneofDestruction) return false; - if (partycheck.area === sdk.areas.RiverofFlame || partycheck.area === sdk.areas.ChaosSanctuary) return true; - } while (partycheck.getNext()); - } - - delay(1000); - } - - return false; - }; - - // Start - const Worker = require("../modules/Worker"); - - if (Leader) { - if (!Misc.poll(() => Misc.inMyParty(Leader), Time.seconds(30), 1000)) throw new Error("BaalAssistant: Leader not partied"); - } - - let killLeaderTracker = false; - if (!Leader && (Config.BaalAssistant.KillNihlathak || Config.BaalAssistant.FastChaos)) { - // run background auto detect so we don't miss messages while running add ons - let leadTick = getTickCount(); - - Worker.runInBackground.leaderTracker = function () { - if (killLeaderTracker || killTracker) return false; - // check every 3 seconds - if (getTickCount() - leadTick < 3000) return true; - leadTick = getTickCount(); - - // check again in another 3 seconds if game wasn't ready - if (!me.gameReady) return true; - if (Misc.getPlayerCount() <= 1) return false; - - let party = getParty(); - - if (party) { - do { - // Player is in Throne of Destruction or Worldstone Chamber - if ([sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(party.area)) { - Leader = party.name; - console.log(sdk.colors.DarkGold + "Autodected " + Leader); - return false; - } - } while (party.getNext()); - } - - return true; - }; - } - - Config.BaalAssistant.KillNihlathak && Loader.runScript("Nihlathak"); - Config.BaalAssistant.FastChaos && Loader.runScript("Diablo", () => Config.Diablo.Fast = true); - - Town.goToTown(5); - Town.doChores(); - - if (Leader - || (Leader = Misc.autoLeaderDetect({destination: sdk.areas.WorldstoneLvl3, quitIf: (area) => [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(area)})) - || (Leader = Misc.autoLeaderDetect({destination: sdk.areas.ThroneofDestruction, quitIf: (area) => [sdk.areas.WorldstoneChamber].includes(area)}))) { - print("ÿc hotCheck, Time.seconds(Config.BaalAssistant.Wait), 1000); - - if (!hotCheck) { - print("ÿc1Leader didn't tell me to start hunting for an experience shrine."); - ShrineStatus = true; - } - } - - // don't waste time looking for seal if party is already killing baal, he'll be dead by the time we find one - if (!ShrineStatus && !baalCheck) { - Pather.useWaypoint(sdk.areas.StonyField); - Precast.doPrecast(true); - let i; - - for (i = sdk.areas.StonyField; i > sdk.areas.RogueEncampment; i -= 1) { - if (safeCheck) { - break; - } - if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { - break; - } - } - - if (!safeCheck) { - if (i === sdk.areas.RogueEncampment) { - Town.goToTown(); - Pather.useWaypoint(sdk.areas.DarkWood); - Precast.doPrecast(true); - - for (i = sdk.areas.DarkWood; i < sdk.areas.DenofEvil; i += 1) { - if (safeCheck) { - break; - } - if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { - break; - } - } - } - } - } - - Town.goToTown(5); - ShrineStatus = true; - } - - if (firstAttempt && !secondAttempt && !safeCheck && !baalCheck && !me.inArea(sdk.areas.ThroneofDestruction) && !me.inArea(sdk.areas.WorldstoneChamber)) { - !!Config.RandomPrecast ? Precast.doRandomPrecast(true, sdk.areas.WorldstoneLvl2) : Pather.useWaypoint(sdk.areas.WorldstoneLvl2) && Precast.doPrecast(true); - } - - if (!me.inArea(sdk.areas.ThroneofDestruction) && !me.inArea(sdk.areas.WorldstoneChamber)) { - if (Config.BaalAssistant.SkipTP) { - if (firstAttempt && !secondAttempt) { - !me.inArea(sdk.areas.WorldstoneLvl2) && Pather.useWaypoint(sdk.areas.WorldstoneLvl2); - if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], false)) throw new Error("Failed to move to WSK3."); - - this.checkParty(); - let entrance = Misc.poll(() => Game.getStairs(sdk.exits.preset.NextAreaWorldstone), 1000, 200); - entrance && Pather.moveTo(entrance.x > me.x ? entrance.x - 5 : entrance.x + 5, entrance.y > me.y ? entrance.y - 5 : entrance.y + 5); - - if (!Pather.moveToExit(sdk.areas.WorldstoneLvl3, true) || !Pather.moveTo(15118, 5002)) throw new Error("Failed to move to Throne of Destruction."); - - Pather.moveTo(15095, 5029); - - if ((Config.BaalAssistant.SoulQuit && Game.getMonster(sdk.monsters.BurningSoul1)) || (Config.BaalAssistant.DollQuit && Game.getMonster(sdk.monsters.SoulKiller))) { - print("Burning Souls or Undead Soul Killers found, ending script."); - return true; - } - - Pather.moveTo(15118, 5002); - Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); - - secondAttempt = true; - safeCheck = true; - } else { - if (me.inTown) { - Town.move("portalspot"); - Pather.usePortal(sdk.areas.ThroneofDestruction, null); - Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); - } - } - } else { - if (firstAttempt && !secondAttempt) { - !me.inArea(sdk.areas.Harrogath) && Pather.useWaypoint(sdk.areas.Harrogath); - Town.move("portalspot"); - - if (Config.BaalAssistant.WaitForSafeTP && !Misc.poll(() => safeCheck, Time.seconds(Config.BaalAssistant.Wait), 1000)) { - throw new Error("No safe TP message."); - } - - if (!Misc.poll(() => Pather.usePortal(sdk.areas.ThroneofDestruction, null), Time.seconds(Config.BaalAssistant.Wait), 1000)) { - throw new Error("No portals to Throne."); - } - - if ((Config.BaalAssistant.SoulQuit && Game.getMonster(sdk.monsters.BurningSoul1)) || (Config.BaalAssistant.DollQuit && Game.getMonster(sdk.monsters.SoulKiller))) { - throw new Error("Burning Souls or Undead Soul Killers found, ending script."); - } - - Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); - secondAttempt = true; - safeCheck = true; - } else { - if (me.inTown) { - Town.move("portalspot"); - Pather.usePortal(sdk.areas.ThroneofDestruction, null); - Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); - } - } - } - } - - if (safeCheck && !baalCheck && me.inArea(sdk.areas.ThroneofDestruction)) { - if (!baalCheck && !throneStatus) { - if (Helper) { - Attack.clear(15); - Common.Baal.clearThrone(); - Pather.moveTo(15094, me.paladin ? 5029 : 5038); - Precast.doPrecast(true); - } - - let tick = getTickCount(); - - MainLoop: while (true) { - if (Helper) { - if (getDistance(me, 15094, me.paladin ? 5029 : 5038) > 3) { - Pather.moveTo(15094, me.paladin ? 5029 : 5038); - } - } - - if (!Game.getMonster(sdk.monsters.ThroneBaal)) { - break; - } - - switch (Common.Baal.checkThrone(Helper)) { - case 1: - Helper && Attack.clear(40); - tick = getTickCount(); - - break; - case 2: - Helper && Attack.clear(40); - tick = getTickCount(); - - break; - case 4: - Helper && Attack.clear(40); - tick = getTickCount(); - - break; - case 3: - Helper && Attack.clear(40) && Common.Baal.checkHydra(); - tick = getTickCount(); - - break; - case 5: - if (Helper) { - Attack.clear(40); - } else { - while ([sdk.monsters.ListerTheTormenter, sdk.monsters.Minion1, sdk.monsters.Minion2] - .map((unitId) => Game.getMonster(unitId)) - .filter(Boolean).some((unit) => unit.attackable)) { - delay(1000); - } - - delay(1000); - } - - break MainLoop; - default: - if (getTickCount() - tick < 7e3) { - if (me.paladin && me.getState(sdk.states.Poison) && Skill.setSkill(sdk.skills.Cleansing, sdk.skills.hand.Right)) { - break; - } - } - - if (Helper && !Common.Baal.preattack()) { - delay(100); - } - - break; - } - delay(10); - } - throneStatus = true; - baalCheck = true; - } - } - - if ((throneStatus || baalCheck) && Config.BaalAssistant.KillBaal && me.inArea(sdk.areas.ThroneofDestruction)) { - Helper ? Pather.moveTo(15090, 5008) && delay(2000) : Pather.moveTo(15090, 5010); - Precast.doPrecast(true); - - while (Game.getMonster(sdk.monsters.ThroneBaal)) { - delay(500); - } - - let portal = Game.getObject(sdk.objects.WorldstonePortal); - - if (portal) { - delay((Helper ? 1000 : 4000)); - Pather.usePortal(null, null, portal); - } else { - throw new Error("Couldn't find portal."); - } - - if (Helper) { - delay(1000); - Pather.moveTo(15134, 5923); - Attack.kill(sdk.monsters.Baal); - Pickit.pickItems(); - } else { - Pather.moveTo(15177, 5952); - let baal = Game.getMonster(sdk.monsters.Baal); - - while (!!baal && baal.attackable) { - delay(1000); - } - } - - } else { - while (!ngCheck) { - delay(500); - } - } - - delay(500); - } - } catch (e) { - console.error(e); - } finally { - killTracker = true; - } - } else { - throw new Error("Empty game."); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/BaalHelper.js b/d2bs/kolbot/libs/bots/BaalHelper.js deleted file mode 100644 index 61f8a8241..000000000 --- a/d2bs/kolbot/libs/bots/BaalHelper.js +++ /dev/null @@ -1,77 +0,0 @@ -/** -* @filename BaalHelper.js -* @author kolton, theBGuy -* @desc help the leading player in clearing Throne of Destruction and killing Baal -* -*/ - -function BaalHelper() { - Config.BaalHelper.KillNihlathak && Loader.runScript("Nihlathak"); - Config.BaalHelper.FastChaos && Loader.runScript("Diablo", () => Config.Diablo.Fast = true); - - Town.goToTown(5); - Town.doChores(); - Config.RandomPrecast && Precast.needOutOfTownCast() ? Precast.doRandomPrecast(true, sdk.areas.Harrogath) : Precast.doPrecast(true); - - if (Config.BaalHelper.SkipTP) { - !me.inArea(sdk.areas.WorldstoneLvl2) && Pather.useWaypoint(sdk.areas.WorldstoneLvl2); - - if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], false)) throw new Error("Failed to move to WSK3."); - if (!Misc.poll(() => { - let party = getParty(); - - if (party) { - do { - if ((!Config.Leader || party.name === Config.Leader) && party.area === sdk.areas.ThroneofDestruction) { - return true; - } - } while (party.getNext()); - } - - return false; - }, Time.minutes(Config.BaalHelper.Wait), 1000)) throw new Error("Player wait timed out (" + (Config.Leader ? "Leader not" : "No players") + " found in Throne)"); - - let entrance = Misc.poll(() => Game.getStairs(sdk.exits.preset.NextAreaWorldstone), 1000, 200); - entrance && Pather.moveTo(entrance.x > me.x ? entrance.x - 5 : entrance.x + 5, entrance.y > me.y ? entrance.y - 5 : entrance.y + 5); - - if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], false)) throw new Error("Failed to move to WSK3."); - if (!Pather.moveToExit(sdk.areas.ThroneofDestruction, true)) throw new Error("Failed to move to Throne of Destruction."); - if (!Pather.moveTo(15113, 5040)) D2Bot.printToConsole("path fail"); - } else { - Town.goToTown(5); - Town.move("portalspot"); - - if (!Misc.poll(() => { - if (Pather.getPortal(sdk.areas.ThroneofDestruction, Config.Leader || null) && Pather.usePortal(sdk.areas.ThroneofDestruction, Config.Leader || null)) { - return true; - } - - return false; - }, Time.minutes(Config.BaalHelper.Wait), 1000)) throw new Error("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); - } - - if (Config.BaalHelper.DollQuit && Game.getMonster(sdk.monsters.SoulKiller)) { - print("Undead Soul Killers found."); - - return true; - } - - Precast.doPrecast(false); - Attack.clear(15); - Common.Baal.clearThrone(); - - if (!Common.Baal.clearWaves()) { - throw new Error("Couldn't clear baal waves"); - } - - if (Config.BaalHelper.KillBaal) { - Common.Baal.killBaal(); - } else { - Town.goToTown(); - while (true) { - delay(500); - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/BattleOrders.js b/d2bs/kolbot/libs/bots/BattleOrders.js deleted file mode 100644 index edff2e6c4..000000000 --- a/d2bs/kolbot/libs/bots/BattleOrders.js +++ /dev/null @@ -1,257 +0,0 @@ -/** -* @filename BattleOrders.js -* @author kolton, jmichelsen, theBGuy -* @desc give or receive Battle Orders buff -* -*/ - -// todo - define bo-er name, so bots who are getting bo know who is supposed to give it -// todo - use profile <-> profile communication so we don't need to set char names, Maybe shout global? - -function BattleOrders () { - this.gaveBo = false; - this.totalBoed = []; - - const boMode = { - Give: 0, - Receive: 1 - }; - - // convert all names in getter to lowercase - Config.BattleOrders.Getters.forEach((name, index) => { - Config.BattleOrders.Getters[index] = name.toLowerCase(); - }); - - function checkForPlayers () { - if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); - } - - function log (msg = "") { - console.log(msg); - me.overhead(msg); - } - - function tardy () { - let party; - - AreaInfoLoop: - while (true) { - try { - checkForPlayers(); - } catch (e) { - if (Config.BattleOrders.Wait) { - let counter = 0; - print("Waiting " + Config.BattleOrders.Wait + " seconds for other players..."); - - Misc.poll(() => { - counter++; - me.overhead("Waiting " + Math.round(((tick + Time.seconds(Config.BattleOrders.Wait)) - getTickCount()) / 1000) + " Seconds for other players"); - if (counter % 5 === 0) { - return checkForPlayers(); - } - return false; - }, Time.seconds(Config.BattleOrders.Wait), Time.seconds(1)); - - continue; - } else { - console.error(e); - // emptry game, don't wait - return true; - } - } - - party = getParty(); - - if (party) { - do { - if (party.name !== me.name && party.area) { - break AreaInfoLoop; // Can read player area - } - } while (party.getNext()); - } - - delay(500); - } - - if (party) { - do { - if ([sdk.areas.MooMooFarm, sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(party.area)) { - log("ÿc1I'm late to BOs. Moving on..."); - - return true; - } - } while (party.getNext()); - } - - return false; // Not late; wait. - } - - // bo is AoE, lets build a list of all players near us so we can know who we boed - function giveBO () { - // more players might be showing up, give a moment and lets wait until the nearby player count is static - let nearPlayers = 0; - let tick = getTickCount(); - - // if we haven't already given a bo, lets wait to see if more players show up - if (!BattleOrders.gaveBo) { - nearPlayers = Misc.getNearbyPlayerCount(); - while (nearPlayers !== Config.BattleOrders.Getters.length) { - if (getTickCount() - tick >= Time.seconds(30)) { - log("Begin"); - - break; - } - - me.overhead("Waiting " + Math.round(((tick + Time.seconds(30)) - getTickCount()) / 1000) + " for all players to show up"); - nearPlayers = Misc.getNearbyPlayerCount(); - delay(1000); - } - } - - let boed = false; - let playersToBo = getUnits(sdk.unittype.Player) - .filter(p => Config.BattleOrders.Getters.includes(p.name.toLowerCase()) && p.distance < 20); - playersToBo.forEach(p => { - tick = getTickCount(); - - if (copyUnit(p).x) { - while (!p.getState(sdk.states.BattleOrders) && copyUnit(p).x) { - if (getTickCount() - tick >= Time.minutes(1)) { - log("ÿc1BO timeout fail."); - - if (Config.BattleOrders.QuitOnFailure) { - quit(); - } - - break; - } - - Precast.doPrecast(true); - delay(1000); - } - - this.totalBoed.indexOf(p.name.toLowerCase()) === -1 && this.totalBoed.push(p.name.toLowerCase()); - console.debug("Bo-ed " + p.name); - boed = true; - } - }); - - if (boed) { - delay(5000); - } - - return { - success: boed, - count: playersToBo.length - }; - } - - // START - Town.doChores(); - - try { - Pather.useWaypoint(sdk.areas.CatacombsLvl2, true); - } catch (wperror) { - log("ÿc1Failed to take waypoint."); - Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); - - return false; - } - - // don't bo until we are ready to do so - Precast.enabled = false; - Pather.moveTo(me.x + 6, me.y + 6); - - let tick = getTickCount(); - let failTimer = Time.minutes(2); - let nearPlayer; - - // Ready - Precast.enabled = true; - - MainLoop: - while (true) { - if (Config.BattleOrders.SkipIfTardy && tardy()) { - break; - } - - switch (Config.BattleOrders.Mode) { - case boMode.Give: - // check if anyone is near us - nearPlayer = Game.getPlayer(); - - if (nearPlayer) { - do { - if (nearPlayer.name !== me.name) { - let nearPlayerName = nearPlayer.name.toLowerCase(); - // there is a player near us and they are in the list of players to bo and in my party - if (Config.BattleOrders.Getters.includes(nearPlayerName) && !this.totalBoed.includes(nearPlayerName) && Misc.inMyParty(nearPlayerName)) { - let result = giveBO(); - if (result.success) { - if (result.count === Config.BattleOrders.Getters.length || this.totalBoed.length === Config.BattleOrders.Getters.length) { - // we bo-ed everyone we are set to, don't wait around any longer - break MainLoop; - } - // reset fail tick - tick = getTickCount(); - // shorten waiting time since we've already started giving out bo's - BattleOrders.gaveBo = true; - } - } - } else { - me.overhead("Waiting " + Math.round(((tick + failTimer) - getTickCount()) / 1000) + " Seconds for other players"); - - if (getTickCount() - tick >= failTimer) { - log("ÿc1Give BO timeout fail."); - Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); - - break MainLoop; - } - } - } while (nearPlayer.getNext()); - } else { - me.overhead("Waiting " + Math.round(((tick + failTimer) - getTickCount()) / 1000) + " Seconds for other players"); - - if (getTickCount() - tick >= failTimer) { - log("ÿc1Give BO timeout fail."); - Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); - - break MainLoop; - } - } - - break; - case boMode.Receive: - if (me.getState(sdk.states.BattleOrders)) { - log("Got bo-ed"); - delay(1000); - - break MainLoop; - } - - if (getTickCount() - tick >= failTimer) { - log("ÿc1BO timeout fail."); - Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); - - break MainLoop; - } - - break; - } - - delay(500); - } - - (Pather.useWaypoint(sdk.areas.RogueEncampment) || Town.goToTown()); - - // what's the point of this? - if (Config.BattleOrders.Mode === boMode.Give && Config.BattleOrders.Idle) { - for (let i = 0; i < Config.BattleOrders.Getters.length; i += 1) { - while (Misc.inMyParty(Config.BattleOrders.Getters[i])) { - delay(1000); - } - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/BattlemaidSarina.js b/d2bs/kolbot/libs/bots/BattlemaidSarina.js deleted file mode 100644 index 7c3df77fa..000000000 --- a/d2bs/kolbot/libs/bots/BattlemaidSarina.js +++ /dev/null @@ -1,22 +0,0 @@ -/** -* @filename BattlemaidSarina.js -* @author theBGuy -* @desc kill Battlemaid Sarina -* -*/ - -function BattlemaidSarina() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.KurastBazaar); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.RuinedTemple, true) - || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.LamEsensTomeHolder)) { - throw new Error("Failed to move near Sarina"); - } - - Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.BattlemaidSarina)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Bishibosh.js b/d2bs/kolbot/libs/bots/Bishibosh.js deleted file mode 100644 index b00339252..000000000 --- a/d2bs/kolbot/libs/bots/Bishibosh.js +++ /dev/null @@ -1,18 +0,0 @@ -/** -* @filename Bishibosh.js -* @author theBGuy -* @desc kill Bishibosh -* -*/ - -function Bishibosh() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.ColdPlains); - Precast.doPrecast(true); - - Pather.moveToPreset(sdk.areas.ColdPlains, sdk.unittype.Monster, sdk.monsters.preset.Bishibosh); - Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Bishibosh)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/BoBarbHelper.js b/d2bs/kolbot/libs/bots/BoBarbHelper.js deleted file mode 100644 index 94b3050a6..000000000 --- a/d2bs/kolbot/libs/bots/BoBarbHelper.js +++ /dev/null @@ -1,104 +0,0 @@ -/** -* @filename BoBarbHelper.js -* @author nag0k -* @desc give Battle Orders buff modded for hardcore, with barbarian waiting whole game on Catacombs 2 wp by default -* get the required lines for character config files from ...\libs\config\_BaseConfigFile.js -* -*/ - -function BoBarbHelper () { - if (!me.barbarian && Config.BoBarbHelper.Mode !== 0) return true; - - const townNearbyMonster = true; // go to town if monsters nearby - const townLowMana = 20; // go refill mana if mana drops below this percent - const shouldHealMana = amount => me.mp < Math.floor(me.mpmax * amount / 100); - - const healMana = () => { - Pather.useWaypoint(sdk.areas.RogueEncampment); - Town.initNPC("Heal", "heal"); - Pather.useWaypoint(Config.BoBarbHelper.Wp); - }; - - const shouldBuff = unit => ( - Misc.inMyParty(unit) && - getDistance(me, unit) < 10 && - unit.name !== me.name && - !unit.dead && - !unit.inTown - ); - - const giveBuff = () => { - const unit = Game.getPlayer(); - - do { - if (shouldBuff(unit)) { - Precast.doPrecast(true); - delay(50); - } - } while (unit.getNext()); - }; - - const monsterNear = () => { - const unit = Game.getMonster(); - - if (unit) { - do { - if (unit.attackable && getDistance(me, unit) < 20) { - return true; - } - } while (unit.getNext()); - } - - return false; - }; - - if (!Config.QuitList) { - showConsole(); - print("set Config.QuitList in character settings"); - print("if you don't I will idle indefinitely"); - } - - if (me.hardcore && Config.LifeChicken <= 0) { - showConsole(); - print("on HARDCORE"); - print("you should set Config.LifeChicken"); - print("monsters can find their way to wps ..."); - delay(2000); - hideConsole(); - me.overhead("set LifeChiken to 40"); - Config.LifeChicken = 40; - } - - shouldHealMana(townLowMana) && Town.initNPC("Heal", "heal"); - Town.heal(); // in case our life is low as well - - try { - Pather.useWaypoint(Config.BoBarbHelper.Wp); - } catch (e) { - showConsole(); - print("Failed to move to BO WP"); - print("make sure I have " + Pather.getAreaName(Config.BoBarbHelper.Wp) + " waypoint"); - delay(20000); - - return true; - } - - Pather.moveTo(me.x + 4, me.y + 4); - - while (true) { - giveBuff(); - - if (townNearbyMonster && monsterNear()) { - if (!Pather.useWaypoint(sdk.areas.RogueEncampment)) { - break; - } - } - - shouldHealMana(townLowMana) && healMana(); - delay(25); - } - - Town.goToTown(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/BoneAsh.js b/d2bs/kolbot/libs/bots/BoneAsh.js deleted file mode 100644 index c226c2aa3..000000000 --- a/d2bs/kolbot/libs/bots/BoneAsh.js +++ /dev/null @@ -1,19 +0,0 @@ -/** -* @filename BoneAsh.js -* @author kolton -* @desc kill Bone Ash -* -*/ - -function BoneAsh() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.InnerCloister); - Precast.doPrecast(true); - - if (!Pather.moveTo(20047, 4898)) throw new Error("Failed to move to Bone Ash"); - - Attack.kill(getLocaleString(sdk.locale.monsters.BoneAsh)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Bonesaw.js b/d2bs/kolbot/libs/bots/Bonesaw.js deleted file mode 100644 index be04e3789..000000000 --- a/d2bs/kolbot/libs/bots/Bonesaw.js +++ /dev/null @@ -1,19 +0,0 @@ -/** -* @filename Bonesaw.js -* @author kolton -* @desc kill Bonesaw Breaker -* -*/ - -function Bonesaw() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.GlacialTrail); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.GlacialTrail, sdk.unittype.Object, sdk.objects.LargeSparklyChest, 15, 15)) throw new Error("Failed to move to Bonesaw"); - - Attack.kill(getLocaleString(sdk.locale.monsters.BonesawBreaker)); - Config.Bonesaw.ClearDrifterCavern && Pather.moveToExit(sdk.areas.DrifterCavern, true) && Attack.clearLevel(Config.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/ChestMania.js b/d2bs/kolbot/libs/bots/ChestMania.js deleted file mode 100644 index 9c3dda1d8..000000000 --- a/d2bs/kolbot/libs/bots/ChestMania.js +++ /dev/null @@ -1,31 +0,0 @@ -/** -* @filename ChestMania.js -* @author kolton -* @desc Open chests in configured areas -* -*/ - -// todo - if we have run ghostsbusters before this then some of these areas don't need to be re-run - -function ChestMania() { - Town.doChores(); - - for (let prop in Config.ChestMania) { - if (Config.ChestMania.hasOwnProperty(prop)) { - for (let i = 0; i < Config.ChestMania[prop].length; i += 1) { - const nextArea = Config.ChestMania[prop][i]; - if ([sdk.areas.BloodMoor, sdk.areas.RockyWaste, sdk.areas.SpiderForest, sdk.areas.OuterSteppes, sdk.areas.BloodyFoothills].includes(nextArea)) { - // if we precast as soon as we step out of town it sometimes crashes - so do precast somewhere else first - Precast.doRandomPrecast(false); - } - Pather.journeyTo(Config.ChestMania[prop][i]); - Precast.doPrecast(false); - Misc.openChestsInArea(Config.ChestMania[prop][i]); - } - - Town.doChores(); - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/ClassicChaosAssistant.js b/d2bs/kolbot/libs/bots/ClassicChaosAssistant.js deleted file mode 100644 index 03759b18a..000000000 --- a/d2bs/kolbot/libs/bots/ClassicChaosAssistant.js +++ /dev/null @@ -1,129 +0,0 @@ -/** -* @filename ClassicChaosAssistant.js -* @author YGM -* @desc Assistant to help sorcs in public chaos runs games on classic. -* -*/ - -// redo this, maybe different keys or chat commands instead? - -function ClassicChaosAssistant() { - let stargo, infgo, seisgo, vizgo, infseal, seisseal, vizseal, diablopickup, normalpickup = false; - - addEventListener("keyup", - function (key) { - switch (key) { - case sdk.keys.Numpad1: - stargo = true; - - break; - case sdk.keys.Numpad2: - infgo = true; - - break; - case sdk.keys.Numpad3: - infseal = true; - - break; - case sdk.keys.Numpad4: - seisgo = true; - - break; - case sdk.keys.Numpad5: - seisseal = true; - - break; - case sdk.keys.Numpad6: - vizgo = true; - - break; - case sdk.keys.Numpad7: - vizseal = true; - - break; - case sdk.keys.Numpad8: // (Open last seal, teleport to star and pickup for 30 seconds) - diablopickup = true; - - break; - case sdk.keys.Numpad9: // (Pickup at current location) - normalpickup = true; - - break; - default: - break; - } - }); - - while (true) { - switch (me.area) { - case sdk.areas.ChaosSanctuary: - if (infgo) { - Common.Diablo.infLayout === 1 ? Pather.moveTo(7893, 5306) : Pather.moveTo(7929, 5294); - Pather.makePortal() && say("Infector of Souls TP Up!"); - infgo = false; - } - - if (seisgo) { - Common.Diablo.seisLayout === 1 ? Pather.moveTo(7773, 5191) : Pather.moveTo(7794, 5189); - Pather.makePortal() && say("Lord De Seis TP Up!"); - seisgo = false; - } - - if (vizgo) { - Common.Diablo.vizLayout === 1 ? Pather.moveTo(7681, 5302) : Pather.moveTo(7675, 5305); - Pather.makePortal() && say("Grand Vizier of Chaos TP Up!"); - vizgo = false; - } - - if (infseal) { - Common.Diablo.openSeal(sdk.objects.DiabloSealInfector2); - Common.Diablo.openSeal(sdk.objects.DiabloSealInfector) && say("Infector of Souls spawned!"); - Common.Diablo.infLayout === 1 ? Pather.moveTo(7893, 5306) : Pather.moveTo(7929, 5294); - infseal = false; - } - - if (seisseal) { - Common.Diablo.openSeal(sdk.objects.DiabloSealSeis) && say("Lord De Seis spawned!"); - Common.Diablo.seisLayout === 1 ? Pather.moveTo(7773, 5191) : Pather.moveTo(7794, 5189); - seisseal = false; - } - - if (vizseal) { - Common.Diablo.openSeal(sdk.objects.DiabloSealVizier2) && say("Grand Vizier of Chaos spawned!"); - Common.Diablo.vizLayout === 1 ? Pather.moveTo(7681, 5302) : Pather.moveTo(7675, 5305); - vizseal = false; - } - - if (diablopickup) { - Common.Diablo.openSeal(sdk.objects.DiabloSealVizier); - Pather.moveToPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, 255); - for (let i = 0; i < 300; i += 1) { - Pickit.pickItems(); - delay(100); - } - diablopickup = false; - } - - if (normalpickup) { - Pickit.pickItems(); - normalpickup = false; - } - - break; - default: - if (stargo) { - if (me.inArea(sdk.areas.RiverofFlame)) { - Precast.doPrecast(true); - Pather.moveToPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, 255); - Common.Diablo.initLayout(); - break; - } - stargo = false; - } - - break; - } - - delay(10); - } -} diff --git a/d2bs/kolbot/libs/bots/ClearAnyArea.js b/d2bs/kolbot/libs/bots/ClearAnyArea.js deleted file mode 100644 index d98f317ee..000000000 --- a/d2bs/kolbot/libs/bots/ClearAnyArea.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename ClearAnyArea.js -* @author kolton -* @desc Clears any area -* -*/ - -function ClearAnyArea() { - Town.doChores(); - - for (let i = 0; i < Config.ClearAnyArea.AreaList.length; i += 1) { - try { - Pather.journeyTo(Config.ClearAnyArea.AreaList[i]) && Attack.clearLevel(Config.ClearType); - } catch (e) { - console.error(e); - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Coldcrow.js b/d2bs/kolbot/libs/bots/Coldcrow.js deleted file mode 100644 index 462016930..000000000 --- a/d2bs/kolbot/libs/bots/Coldcrow.js +++ /dev/null @@ -1,19 +0,0 @@ -/** -* @filename Coldcrow.js -* @author njomnjomnjom -* @desc kill Coldcrow -* -*/ - -function Coldcrow() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.ColdPlains); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.CaveLvl1, true, false)) throw new Error("Failed to move to Cave"); - if (!Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Coldcrow, 0, 0, false)) throw new Error("Failed to move to Coldcrow"); - - Attack.kill(getLocaleString(sdk.locale.monsters.Coldcrow)); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Coldworm.js b/d2bs/kolbot/libs/bots/Coldworm.js deleted file mode 100644 index fd03ba66a..000000000 --- a/d2bs/kolbot/libs/bots/Coldworm.js +++ /dev/null @@ -1,33 +0,0 @@ -/** -* @filename Coldworm.js -* @author kolton, edited by 13ack.Stab -* @desc kill Coldworm; optionally kill Beetleburst and clear Maggot Lair -* -*/ - -function Coldworm() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.FarOasis); - Precast.doPrecast(true); - - // Beetleburst, added by 13ack.Stab - if (Config.Coldworm.KillBeetleburst) { - try { - if (!Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Beetleburst)) throw new Error("Failed to move to Beetleburst"); - Attack.kill(getLocaleString(sdk.locale.monsters.Beetleburst)); - } catch (e) { - console.error(e); // not the main part of this script so simply log and move on - } - } - - if (!Pather.moveToExit([sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3], true)) throw new Error("Failed to move to Coldworm"); - - if (Config.Coldworm.ClearMaggotLair) { - Attack.clearLevel(Config.ClearType); - } else { - if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest)) throw new Error("Failed to move to Coldworm"); - Attack.kill(sdk.monsters.ColdwormtheBurrower); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/ControlBot.js b/d2bs/kolbot/libs/bots/ControlBot.js deleted file mode 100644 index abf1d29a6..000000000 --- a/d2bs/kolbot/libs/bots/ControlBot.js +++ /dev/null @@ -1,650 +0,0 @@ -/** -* @filename ControlBot.js -* @author theBGuy -* @credits kolton -* @desc Chat controlled bot for other players. Can open cow portal, give waypoints on command, bo, or enchant -* -*/ - -function ControlBot() { - const startTime = getTickCount(); - - /** - * @type {Object.} - */ - this.cmdNicks = {}; - /** - * @type {Object.} - */ - this.wpNicks = {}; - - let command, nick; - let shitList = []; - let greet = []; - - let controlCommands = ["help", "timeleft", "cows", "wps", "chant", "bo"]; - let commandDesc = { - "help": "Display commands", - "timeleft": "Remaining time left for this game", - "cows": "Open cow level", - "chant": "Enchant. AutoChant is " + (Config.ControlBot.Chant.AutoEnchant ? "ON" : "OFF"), - "wps": "Give waypoints", - "bo": "Bo at waypoint", - }; - - // remove commands we can't/aren't using - for (let i = 0; i < controlCommands.length; i++) { - switch (controlCommands[i]) { - case "cows": - if (!Config.ControlBot.Cows.MakeCows) { - controlCommands.splice(i, 1); - i--; - } - - break; - case "chant": - if (!Config.ControlBot.Chant.Enchant || !me.getSkill(sdk.skills.Enchant, sdk.skills.subindex.SoftPoints)) { - Config.ControlBot.Chant.Enchant = false; - Config.ControlBot.Chant.AutoEnchant = false; - controlCommands.splice(i, 1); - i--; - } - - break; - case "wps": - if (!Config.ControlBot.Wps.GiveWps) { - controlCommands.splice(i, 1); - i--; - } - - break; - case "bo": - if (!Config.ControlBot.Bo || (!me.getSkill(sdk.skills.BattleOrders, sdk.skills.subindex.SoftPoints) && Precast.haveCTA === -1)) { - Config.ControlBot.Bo = false; - controlCommands.splice(i, 1); - i--; - } - - break; - } - } - - this.enchant = function (nick) { - if (!Config.ControlBot.Chant.Enchant) return false; - - if (!Misc.inMyParty(nick)) { - say("Accept party invite, noob."); - - return false; - } - - let unit = Game.getPlayer(nick); - - if (unit && unit.distance > 35) { - say("Get closer."); - - return false; - } - - if (!unit) { - let partyUnit = getParty(nick); - - // wait until party area is readable? - if (partyUnit.inTown) { - say("Wait for me at waypoint."); - Town.goToTown(sdk.areas.actOf(partyUnit.area)); - - unit = Game.getPlayer(nick); - } else { - say("You need to be in one of the towns."); - - return false; - } - } - - if (unit) { - do { - // player is alive - if (!unit.dead) { - if (unit.distance >= 35) { - say("You went too far away."); - - return false; - } - - Packet.enchant(unit); - delay(500); - } - } while (unit.getNext()); - } else { - say("I don't see you"); - } - - unit = Game.getMonster(); - - if (unit) { - do { - // merc or any other owned unit - if (unit.getParent() && unit.getParent().name === nick) { - Packet.enchant(unit); - delay(500); - } - } while (unit.getNext()); - } - - return true; - }; - - this.bo = function (nick) { - if (!Config.ControlBot.Bo) return false; - - if (!Misc.inMyParty(nick)) { - say("Accept party invite, noob."); - - return false; - } - - let partyUnit = getParty(nick); - - // wait until party area is readable? - if (partyUnit.inTown) { - say("Can't bo you in town noob, go to a waypoint"); - - return false; - } else if (Pather.wpAreas.includes(partyUnit.area)) { - Pather.useWaypoint(partyUnit.area); - } else { - say("Can't find you or you're not somewhere with a waypoint"); - - return false; - } - - let unit = Game.getPlayer(nick); - - if (unit && unit.distance > 15) { - say("Get closer."); - let waitTick = getTickCount(); - - while (unit && unit.distance > 15) { - if (getTickCount() - waitTick > 30e3) { - say("You took to long. Going back to town"); - return false; - } - delay(150); - } - } - - if (unit && unit.distance <= 15 && !unit.dead) { - Misc.poll(function () { - Precast.doPrecast(true); - return unit.getState(sdk.states.BattleOrders); - }, 5000, 1000); - Pather.useWaypoint(sdk.areas.RogueEncampment); - } else { - say("I don't see you"); - } - - return true; - }; - - this.autoChant = function () { - if (!Config.ControlBot.Chant.Enchant) return false; - - let chanted = []; - let unit = Game.getPlayer(); - - if (unit) { - do { - if (unit.name !== me.name && !unit.dead && shitList.indexOf(unit.name) === -1 && Misc.inMyParty(unit.name) && !unit.getState(sdk.states.Enchant) && unit.distance <= 40) { - Packet.enchant(unit); - delay(500); - chanted.push(unit.name); - } - } while (unit.getNext()); - } - - unit = Game.getMonster(); - - if (unit) { - do { - if (unit.getParent() && chanted.includes(unit.getParent().name) && !unit.getState(sdk.states.Enchant) && unit.distance <= 40) { - Packet.enchant(unit); - delay(500); - } - } while (unit.getNext()); - } - - return true; - }; - - this.getLeg = function () { - if (me.getItem(sdk.quest.item.WirtsLeg)) { - return me.getItem(sdk.quest.item.WirtsLeg); - } - - let leg, gid, wrongLeg; - - if (!Config.ControlBot.Cows.GetLeg) { - leg = Game.getItem(sdk.items.quest.WirtsLeg); - - if (leg) { - do { - if (leg.name.includes("ÿc1")) { - wrongLeg = true; - } else if (leg.distance <= 15) { - gid = leg.gid; - Pickit.pickItem(leg); - - return me.getItem(-1, -1, gid); - } - } while (leg.getNext()); - } - - say("Bring the leg " + (wrongLeg ? "from this difficulty" : "") + " close to me."); - - return false; - } - - if (!Pather.journeyTo(sdk.areas.Tristram)) { - say("Failed to enter Tristram :("); - Town.goToTown(); - - return false; - } - - Pather.moveTo(25048, 5177); - - let wirt = Game.getObject(sdk.quest.chest.Wirt); - - for (let i = 0; i < 8; i += 1) { - wirt.interact(); - delay(500); - - leg = Game.getItem(sdk.quest.item.WirtsLeg); - - if (leg) { - gid = leg.gid; - - Pickit.pickItem(leg); - Town.goToTown(); - - return me.getItem(-1, -1, gid); - } - } - - Town.goToTown(); - say("Failed to get the leg :("); - - return false; - }; - - this.getTome = function () { - let tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (tpTome.length < 2) { - let npc = Town.initNPC("Shop", "buyTpTome"); - if (!getInteractedNPC()) throw new Error("Failed to find npc"); - - let tome = npc.getItem(sdk.items.TomeofTownPortal); - - if (!!tome && tome.getItemCost(sdk.items.cost.ToBuy) < me.gold && tome.buy()) { - delay(500); - tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); - tpTome.forEach(function (book) { - if (book.isInInventory) { - let scroll = npc.getItem(sdk.items.ScrollofTownPortal); - - while (book.getStat(sdk.stats.Quantity) < 20) { - if (!!scroll && scroll.getItemCost(sdk.items.cost.ToBuy) < me.gold) { - scroll.buy(true); - } else { - break; - } - - delay(20); - } - } - }); - } else { - throw new Error("Failed to buy tome"); - } - } - - return tpTome.last(); - }; - - this.openPortal = function (nick) { - if (!Config.ControlBot.Cows.MakeCows) return false; - try { - if (!Misc.inMyParty(nick)) throw new Error("Accept party invite, noob."); - if (Pather.getPortal(sdk.areas.MooMooFarm)) throw new Error("Cow portal already open."); - // king dead or cain not saved - if (me.cows) throw new Error("Can't open the portal because I killed Cow King."); - if (Config.ControlBot.Cows.GetLeg && !me.tristram && !!Config.Leader && !getParty(Config.Leader)) { - throw new Error("Can't get leg because I don't have Cain quest."); - } - if (!me.diffCompleted) throw new Error("Final quest incomplete."); - } catch (e) { - say(e.message ? e.message : e); - return false; - } - - let leg = this.getLeg(); - if (!leg) return false; - - let tome = this.getTome(); - if (!tome) return false; - - if (!Town.openStash() || !Cubing.emptyCube() || !Storage.Cube.MoveTo(leg) || !Storage.Cube.MoveTo(tome) || !Cubing.openCube()) { - return false; - } - - transmute(); - delay(500); - - for (let i = 0; i < 10; i += 1) { - if (Pather.getPortal(sdk.areas.MooMooFarm)) { - return true; - } - - delay(200); - } - - say("Failed to open cow portal."); - - return false; - }; - - this.getWpNick = function (nick) { - if (this.wpNicks.hasOwnProperty(nick)) { - if (this.wpNicks[nick].requests > 4) { - return "maxrequests"; - } - - if (getTickCount() - this.wpNicks[nick].timer < 60000) { - return "mintime"; - } - - return true; - } - - return false; - }; - - this.addWpNick = function (nick) { - this.wpNicks[nick] = {timer: getTickCount(), requests: 0}; - }; - - this.giveWps = function (nick) { - if (!Config.ControlBot.Wps.GiveWps) return false; - if (!Misc.inMyParty(nick)) { - say("Accept party invite, noob."); - - return false; - } - - switch (this.getWpNick(nick)) { - case "maxrequests": - say(nick + ", you have spent all your waypoint requests for this game."); - - return false; - case "mintime": - say(nick + ", you may request waypoints every 60 seconds."); - - return false; - case false: - this.addWpNick(nick); - - break; - } - - let act = Misc.getPlayerAct(nick); - const wps = { - 1: [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.DarkWood, sdk.areas.BlackMarsh, - sdk.areas.OuterCloister, sdk.areas.JailLvl1, sdk.areas.InnerCloister, sdk.areas.CatacombsLvl2 - ], - 2: [ - sdk.areas.A2SewersLvl2, sdk.areas.DryHills, sdk.areas.HallsoftheDeadLvl2, sdk.areas.FarOasis, - sdk.areas.LostCity, sdk.areas.PalaceCellarLvl1, sdk.areas.ArcaneSanctuary, sdk.areas.CanyonofMagic - ], - 3: [ - sdk.areas.SpiderForest, sdk.areas.GreatMarsh, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, - sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.Travincal, sdk.areas.DuranceofHateLvl2 - ], - 4: [sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame], - 5: [ - sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.CrystalizedPassage, - sdk.areas.GlacialTrail, sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.WorldstoneLvl2 - ] - }; - let wpList = wps[act]; - - for (let i = 0; i < wpList.length; i++) { - if (this.checkHostiles()) { - break; - } - - try { - Pather.useWaypoint(wpList[i], true); - Config.ControlBot.Wps.SecurePortal && Attack.securePosition(me.x, me.y, 20, 1000); - Pather.makePortal(); - say(Pather.getAreaName(me.area) + " TP up"); - - for (let timeout = 0; timeout < 20; timeout++) { - if (Game.getPlayer(nick)) { - break; - } - - delay(1000); - } - - if (timeout >= 20) { - say("Aborting wp giving."); - - break; - } - - delay(5000); - } catch (error) { - continue; - } - } - - Town.doChores(); - Town.goToTown(1); - Town.move("portalspot"); - - this.wpNicks[nick].requests += 1; - this.wpNicks[nick].timer = getTickCount(); - - return true; - }; - - this.checkHostiles = function () { - let rval = false; - let party = getParty(); - - if (party) { - do { - if (party.name !== me.name && getPlayerFlag(me.gid, party.gid, 8)) { - rval = true; - - if (Config.ShitList && shitList.indexOf(party.name) === -1) { - shitList.push(party.name); - } - } - } while (party.getNext()); - } - - return rval; - }; - - this.floodCheck = function (command) { - if (!command || command.length < 2) return false; - let [cmd, nick] = command; - - // ignore overhead messages - if (!nick) return true; - // ignore messages not related to our commands - if (controlCommands.indexOf(cmd.toLowerCase()) === -1) return false; - - if (!this.cmdNicks.hasOwnProperty(nick)) { - this.cmdNicks[nick] = { - firstCmd: getTickCount(), - commands: 0, - ignored: false - }; - } - - if (this.cmdNicks[nick].ignored) { - if (getTickCount() - this.cmdNicks[nick].ignored < 60000) { - return true; // ignore flooder - } - - // unignore flooder - this.cmdNicks[nick].ignored = false; - this.cmdNicks[nick].commands = 0; - } - - this.cmdNicks[nick].commands += 1; - - if (getTickCount() - this.cmdNicks[nick].firstCmd < 10000) { - if (this.cmdNicks[nick].commands > 5) { - this.cmdNicks[nick].ignored = getTickCount(); - - say(nick + ", you are being ignored for 60 seconds because of flooding."); - } - } else { - this.cmdNicks[nick].firstCmd = getTickCount(); - this.cmdNicks[nick].commands = 0; - } - - return false; - }; - - function chatEvent(nick, msg) { - if (shitList.includes(nick)) { - say("No commands for the shitlisted."); - - return; - } - - command = [msg, nick]; - } - - // eslint-disable-next-line no-unused-vars - function gameEvent(mode, param1, param2, name1, name2) { - switch (mode) { - case 0x02: - // idle in town - me.inTown && me.mode === sdk.player.mode.StandingInTown && greet.push(name1); - - break; - } - } - - // START - Config.ShitList && (shitList = ShitList.read()); - - try { - addEventListener("chatmsg", chatEvent); - addEventListener("gameevent", gameEvent); - Town.doChores(); - Town.goToTown(1); - Town.move("portalspot"); - - const spot = { x: me.x, y: me.y }; - - while (true) { - while (greet.length > 0) { - nick = greet.shift(); - - if (shitList.indexOf(nick) === -1) { - say("Welcome, " + nick + "! For a list of commands say 'help'"); - } - } - - spot.distance > 10 && Pather.moveTo(spot.x, spot.y); - - if (command && !this.floodCheck(command)) { - let hostile = this.checkHostiles(); - - switch (command[0].toLowerCase()) { - case "help": - let str = ""; - controlCommands.forEach((cmd) => { - str += (cmd + " (" + commandDesc[cmd] + "), "); - }); - - say("Commands:"); - say(str); - - break; - case "timeleft": - let tick = Time.minutes(Config.ControlBot.GameLength) - getTickCount() + startTime; - let m = Math.floor(tick / 60000); - let s = Math.floor((tick / 1000) % 60); - - say("Time left: " + (m ? m + " minute" + (m > 1 ? "s" : "") + ", " : "") + s + " second" + (s > 1 ? "s." : ".")); - - break; - case "chant": - this.enchant(command[1]); - - break; - case "cows": - if (hostile) { - say("Command disabled because of hostiles."); - - break; - } - - this.openPortal(command[1]); - me.cancel(); - - break; - case "wps": - if (hostile) { - say("Command disabled because of hostiles."); - - break; - } - - this.giveWps(command[1]); - - break; - case "bo": - if (hostile) { - say("Command disabled because of hostiles."); - - break; - } - - this.bo(command[1]); - - break; - } - } - - command = ""; - - me.act > 1 && Town.goToTown(1); - Config.ControlBot.Chant.AutoEnchant && this.autoChant(); - - if (getTickCount() - startTime >= Time.minutes(Config.ControlBot.GameLength)) { - say((Config.ControlBot.EndMessage ? Config.ControlBot.EndMessage : "Bye")); - delay(1000); - - break; - } - - delay(200); - } - } finally { - removeEventListener("chatmsg", chatEvent); - removeEventListener("gameevent", gameEvent); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Corpsefire.js b/d2bs/kolbot/libs/bots/Corpsefire.js deleted file mode 100644 index 3c80b1690..000000000 --- a/d2bs/kolbot/libs/bots/Corpsefire.js +++ /dev/null @@ -1,22 +0,0 @@ -/** -* @filename Corpsefire.js -* @author kolton -* @desc kill Corpsefire and optionally clear Den of Evil -* -*/ - -function Corpsefire() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.ColdPlains); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.DenofEvil], true) - || !Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Corpsefire, 0, 0, false, true)) { - throw new Error("Failed to move to Corpsefire"); - } - - Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Corpsefire)); - Config.Corpsefire.ClearDen && Attack.clearLevel(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Countess.js b/d2bs/kolbot/libs/bots/Countess.js deleted file mode 100644 index 5417d7652..000000000 --- a/d2bs/kolbot/libs/bots/Countess.js +++ /dev/null @@ -1,35 +0,0 @@ -/** -* @filename Countess.js -* @author kolton -* @desc kill The Countess and optionally kill Ghosts along the way -* -*/ - -function Countess() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.BlackMarsh); - Precast.doPrecast(true); - - if (!Pather.moveToExit([ - sdk.areas.ForgottenTower, sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, - sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TowerCellarLvl5 - ], true)) throw new Error("Failed to move to Countess"); - - let poi = Game.getPresetObject(me.area, sdk.objects.SuperChest); - - if (!poi) throw new Error("Failed to move to Countess (preset not found)"); - - switch (poi.roomx * 5 + poi.x) { - case 12565: - Pather.moveTo(12578, 11043); - break; - case 12526: - Pather.moveTo(12548, 11083); - break; - } - - Attack.clear(20, 0, getLocaleString(sdk.locale.monsters.TheCountess)); - Config.OpenChests.Enabled && Misc.openChestsInArea(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Cows.js b/d2bs/kolbot/libs/bots/Cows.js deleted file mode 100644 index 0d5eb2f90..000000000 --- a/d2bs/kolbot/libs/bots/Cows.js +++ /dev/null @@ -1,151 +0,0 @@ -/** -* @filename Cows.js -* @author kolton, theBGuy -* @desc clear the Moo Moo Farm without killing the Cow King -* -*/ - -function Cows() { - this.getLeg = function () { - if (me.wirtsleg) return me.wirtsleg; - - Pather.useWaypoint(sdk.areas.StonyField); - Precast.doPrecast(true); - Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 8, 8); - - if (!Misc.poll(() => { - let p = Pather.getPortal(sdk.areas.Tristram); - return (p && Pather.usePortal(sdk.areas.Tristram, null, p)); - }, Time.minutes(1), 1000)) { - throw new Error("Tristram portal not found"); - } - - Pather.moveTo(25048, 5177); - - let wirt = Game.getObject(sdk.quest.chest.Wirt); - - for (let i = 0; i < 8; i += 1) { - wirt.interact(); - delay(500); - - let leg = Game.getItem(sdk.quest.item.WirtsLeg); - - if (leg) { - let gid = leg.gid; - - Pickit.pickItem(leg); - Town.goToTown(); - - return me.getItem(-1, -1, gid); - } - } - - throw new Error("Failed to get the leg"); - }; - - this.getTome = function () { - let tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (tpTome.length < 2) { - let npc = Town.initNPC("Shop", "buyTpTome"); - - if (!getInteractedNPC()) { - throw new Error("Failed to find npc"); - } - - let tome = npc.getItem(sdk.items.TomeofTownPortal); - - if (!!tome && tome.getItemCost(sdk.items.cost.ToBuy) < me.gold && tome.buy()) { - delay(500); - tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); - tpTome.forEach(function (book) { - if (book.isInInventory) { - let scroll = npc.getItem(sdk.items.ScrollofTownPortal); - while (book.getStat(sdk.stats.Quantity) < 20) { - if (!!scroll && scroll.getItemCost(sdk.items.cost.ToBuy) < me.gold) { - scroll.buy(true); - } else { - break; - } - - delay(20); - } - } - }); - } else { - throw new Error("Failed to buy tome"); - } - } - - return tpTome.last(); - }; - - this.openPortal = function (leg, tome) { - if (!Town.openStash()) throw new Error("Failed to open stash"); - if (!Cubing.emptyCube()) throw new Error("Failed to empty cube"); - if (!Storage.Cube.MoveTo(leg) || !Storage.Cube.MoveTo(tome) || !Cubing.openCube()) { - throw new Error("Failed to cube leg and tome"); - } - - transmute(); - delay(1000); - me.cancelUIFlags(); - - for (let i = 0; i < 10; i += 1) { - if (Pather.getPortal(sdk.areas.MooMooFarm)) { - return true; - } - - delay(200); - } - - throw new Error("Portal not found"); - }; - - - // we can begin now - try { - if (!me.diffCompleted) throw new Error("Final quest incomplete."); - - Town.goToTown(1); - Town.doChores(); - Town.move("stash"); - - // Check to see if portal is already open, if not get the ingredients - if (!Pather.getPortal(sdk.areas.MooMooFarm)) { - if (Config.Cows.DontMakePortal) throw new Error("NOT PORTAL MAKER"); - if (!me.tristram) throw new Error("Cain quest incomplete"); - if (me.cows) throw new Error("Already killed the Cow King."); - - let leg = this.getLeg(); - let tome = this.getTome(); - this.openPortal(leg, tome); - } - } catch (e) { - typeof e === "object" && e.message && e.message !== "NOT PORTAL MAKER" && console.error(e); - - if (Misc.getPlayerCount() > 1) { - Town.goToTown(1); - Town.move("stash"); - console.log("ÿc9(Cows) :: ÿc0Waiting 1 minute to see if anyone else opens the cow portal"); - - if (!Misc.poll(() => Pather.getPortal(sdk.areas.MooMooFarm), Time.minutes(3), 2000)) throw new Error("No cow portal"); - } else { - return false; - } - } - - if (Config.Cows.JustMakePortal) { - if (Pather.getPortal(sdk.areas.MooMooFarm)) { - return true; - } else { - throw new Error("I failed to make cow portal"); - } - } - - Pather.usePortal(sdk.areas.MooMooFarm); - Precast.doPrecast(false); - Config.Cows.KillKing ? Attack.clearLevel() : Common.Cows.clearCowLevel(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Crafting.js b/d2bs/kolbot/libs/bots/Crafting.js deleted file mode 100644 index 12dc78abf..000000000 --- a/d2bs/kolbot/libs/bots/Crafting.js +++ /dev/null @@ -1,468 +0,0 @@ -/** -* @filename Crafting.js -* @author kolton -* @desc Part of CraftingSystem -* -*/ - -let info; -let gameRequest = false; - -function Crafting() { - info = CraftingSystem.getInfo(); - - if (!info || !info.worker) throw new Error("Bad Crafting System config."); - - me.maxgametime = 0; - Town.goToTown(1); - Town.doChores(); - Town.move("stash"); - updateInfo(); - pickItems(); - - addEventListener("copydata", - function (mode, msg) { - let obj, rval; - - if (mode === 0) { - try { - obj = JSON.parse(msg); - } catch (e) { - return false; - } - - if (obj) { - switch (obj.name) { - case "GetGame": - if (info.Collectors.includes(obj.profile)) { - print("GetGame: " + obj.profile); - sendCopyData(null, obj.profile, 4, me.gamename + "/" + me.gamepassword); - - gameRequest = true; - } - - break; - case "GetSetInfo": - if (info.Collectors.includes(obj.profile)) { - print("GetSetInfo: " + obj.profile); - - rval = []; - - for (let i = 0; i < info.Sets.length; i += 1) { - rval.push(info.Sets[i].Enabled ? 1 : 0); - } - - print(rval); - - sendCopyData(null, obj.profile, 4, JSON.stringify({name: "SetInfo", value: rval})); - } - - break; - } - } - } - - return true; - }); - - for (let i = 0; i < Cubing.recipes.length; i += 1) { - Cubing.recipes[i].Level = 0; - } - - while (true) { - for (let i = 0; i < info.Sets.length; i += 1) { - switch (info.Sets[i].Type) { - case "crafting": - let num = 0; - let npcName = getNPCName(info.Sets[i].BaseItems); - - if (npcName) { - num = countItems(info.Sets[i].BaseItems, 4); - - if (num < info.Sets[i].SetAmount) { - shopStuff(npcName, info.Sets[i].BaseItems, info.Sets[i].SetAmount); - } - } - - break; - case "cubing": // Nothing to do currently - break; - case "runewords": // Nothing to do currently - break; - } - } - - me.act !== 1 && Town.goToTown(1) && Town.move("stash"); - - if (gameRequest) { - for (let i = 0; i < 10; i += 1) { - if (Misc.getPlayerCount() > 1) { - while (Misc.getPlayerCount() > 1) { - delay(200); - } - - break; - } else { - break; - } - } - - gameRequest = false; - } - - pickItems(); - Cubing.update(); - Runewords.buildLists(); - Cubing.doCubing(); - Runewords.makeRunewords(); - delay(2000); - } -} - -function getNPCName(idList) { - for (let i = 0; i < idList.length; i += 1) { - switch (idList[i]) { - case sdk.items.LightBelt: - case sdk.items.SharkskinBelt: - return "elzix"; - case sdk.items.Belt: - case sdk.items.MeshBelt: - case sdk.items.LightPlatedBoots: - case sdk.items.BattleBoots: - return "fara"; - } - } - - return false; -} - -function countItems(idList, quality) { - let count = 0; - let item = me.getItem(-1, sdk.items.mode.inStorage); - - if (item) { - do { - if (idList.includes(item.classid) && item.quality === quality) { - count += 1; - } - } while (item.getNext()); - } - - return count; -} - -function updateInfo() { - if (info) { - let items = me.findItems(-1, sdk.items.mode.inStorage); - - for (let i = 0; i < info.Sets.length; i += 1) { - MainSwitch: - switch (info.Sets[i].Type) { - // Always enable crafting because the base can be shopped - // Recipes with bases that can't be shopped don't need to be used with CraftingSystem - case "crafting": - info.Sets[i].Enabled = true; - - break; - // Enable only if we have a viable item to cube - // Currently the base needs to be added manually to the crafter - case "cubing": - !items && (items = []); - - // Enable the recipe if we have an item that matches both bases list and Cubing list - // This is not a perfect check, it might not handle every case - for (let j = 0; j < items.length; j += 1) { - if (info.Sets[i].BaseItems.includes(items[j].classid) // Item is on the bases list - && AutoMule.cubingIngredient(items[j])) { // Item is a valid Cubing ingredient - print("Base found: " + items[j].classid); - - info.Sets[i].Enabled = true; - - break MainSwitch; - } - } - - info.Sets[i].Enabled = false; - - break; - // Enable only if we have a viable runeword base - // Currently the base needs to be added manually to the crafter - case "runewords": - !items && (items = []); - - // Enable the recipe if we have an item that matches both bases list and Cubing list - // This is not a perfect check, it might not handle every case - for (let j = 0; j < items.length; j += 1) { - if (info.Sets[i].BaseItems.includes(items[j].classid) // Item is on the bases list - && runewordIngredient(items[j])) { // Item is a valid Runeword ingredient - print("Base found: " + items[j].classid); - - info.Sets[i].Enabled = true; - - break MainSwitch; - } - } - - info.Sets[i].Enabled = false; - - break; - } - } - - return true; - } - - return false; -} - -function runewordIngredient(item) { - if (Runewords.validGids.includes(item.gid)) return true; - - let baseGids = []; - - for (let i = 0; i < Config.Runewords.length; i += 1) { - let base = (Runewords.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0)) - || Runewords.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0), true)); - - base && baseGids.push(base.gid); - } - - return baseGids.includes(item.gid); -} - -function pickItems() { - let items = []; - let item = Game.getItem(-1, sdk.items.mode.onGround); - - if (item) { - updateInfo(); - - do { - if (checkItem(item) || item.classid === sdk.items.Gold || Pickit.checkItem(item).result > 0) { - items.push(copyUnit(item)); - } - } while (item.getNext()); - } - - while (items.length) { - if (Pickit.canPick(items[0]) && Storage.Inventory.CanFit(items[0])) { - Pickit.pickItem(items[0]); - } - - items.shift(); - delay(1); - } - - Town.stash(); -} - -function checkItem(item) { - for (let i = 0; i < info.Sets.length; i += 1) { - if (info.Sets[i].Enabled) { - switch (info.Sets[i].Type) { - case "crafting": - // Magic item - // Valid crafting base - if (item.magic && info.Sets[i].BaseItems.includes(item.classid)) return true; - // Valid crafting ingredient - if (info.Sets[i].Ingredients.includes(item.classid)) return true; - - break; - case "cubing": - // There is no base check, item has to be put manually on the character - if (info.Sets[i].Ingredients.includes(item.classid)) return true; - - break; - case "runewords": - // There is no base check, item has to be put manually on the character - if (info.Sets[i].Ingredients.includes(item.classid)) return true; - - break; - } - } - } - - return false; -} - -function shopStuff(npcId, classids, amount) { - print("shopStuff: " + npcId + " " + amount); - - let wpArea, town, path, menuId, npc; - let leadTimeout = 30; - let leadRetry = 3; - - this.mover = function (npc, path) { - path = this.processPath(npc, path); - - for (let i = 0; i < path.length; i += 2) { - let j; - - Pather.moveTo(path[i] - 3, path[i + 1] - 3); - moveNPC(npc, path[i], path[i + 1]); // moving npc doesn't work, probably should be removed? - - for (j = 0; j < leadTimeout; j += 1) { - while (npc.mode === sdk.npcs.mode.Walking) { - delay(100); - } - - if (getDistance(npc.x, npc.y, path[i], path[i + 1]) < 4) { - break; - } - - if (j > 0 && j % leadRetry === 0) { - moveNPC(npc, path[i], path[i + 1]); - } - - delay(1000); - } - - if (j === leadTimeout) { - return false; - } - } - - delay(1000); - - return true; - }; - - this.processPath = function (npc, path) { - let cutIndex = 0; - let dist = 100; - - for (let i = 0; i < path.length; i += 2) { - if (getDistance(npc, path[i], path[i + 1]) < dist) { - cutIndex = i; - dist = getDistance(npc, path[i], path[i + 1]); - } - } - - return path.slice(cutIndex); - }; - - this.shopItems = function (classids, amount) { - let npc = getInteractedNPC(); - - if (npc) { - let items = npc.getItemsEx(); - - if (items.length) { - for (let i = 0; i < items.length; i += 1) { - if (Storage.Inventory.CanFit(items[i]) - && Pickit.canPick(items[i]) - && me.gold >= items[i].getItemCost(sdk.items.cost.ToBuy) - && classids.includes(items[i].classid)) { - - //print("Bought " + items[i].name); - items[i].buy(); - - let num = countItems(classids, sdk.items.quality.Magic); - - if (num >= amount) { - return true; - } - } - } - } - } - - return gameRequest; - }; - - Town.doChores(); - - switch (npcId.toLowerCase()) { - case "fara": - if (!Town.goToTown(2) || !Town.move(NPC.Fara)) throw new Error("Failed to get to NPC"); - - wpArea = sdk.areas.A2SewersLvl2; - town = sdk.areas.LutGholein; - path = [5112, 5094, 5092, 5096, 5078, 5098, 5070, 5085]; - menuId = "Repair"; - npc = Game.getNPC(NPC.Fara); - - break; - case "elzix": - if (!Town.goToTown(2) || !Town.move(NPC.Elzix)) throw new Error("Failed to get to NPC"); - - wpArea = sdk.areas.A2SewersLvl2; - town = sdk.areas.LutGholein; - path = [5038, 5099, 5059, 5102, 5068, 5090, 5067, 5086]; - menuId = "Shop"; - npc = Game.getNPC(NPC.Elzix); - - break; - case "drognan": - if (!Town.goToTown(2) || !Town.move(NPC.Drognan)) throw new Error("Failed to get to NPC"); - - wpArea = sdk.areas.A2SewersLvl2; - town = sdk.areas.LutGholein; - path = [5093, 5049, 5088, 5060, 5093, 5079, 5078, 5087, 5070, 5085]; - menuId = "Shop"; - npc = Game.getNPC(NPC.Drognan); - - break; - case "ormus": - if (!Town.goToTown(3) || !Town.move(NPC.Ormus)) throw new Error("Failed to get to NPC"); - - wpArea = sdk.areas.DuranceofHateLvl2; - town = sdk.areas.KurastDocktown; - path = [5147, 5089, 5156, 5075, 5157, 5063, 5160, 5050]; - menuId = "Shop"; - npc = Game.getNPC(NPC.Ormus); - - break; - case "anya": - if (!Town.goToTown(5) || !Town.move(NPC.Anya)) throw new Error("Failed to get to NPC"); - - wpArea = sdk.areas.WorldstoneLvl2; - town = sdk.areas.Harrogath; - path = [5122, 5119, 5129, 5105, 5123, 5087, 5115, 5068]; - menuId = "Shop"; - npc = Game.getNPC(NPC.Anya); - - break; - case "malah": - if (!Town.goToTown(5) || !Town.move(NPC.Malah)) throw new Error("Failed to get to NPC"); - - wpArea = sdk.areas.CrystalizedPassage; - town = sdk.areas.Harrogath; - path = [5077, 5032, 5089, 5025, 5100, 5021, 5106, 5051, 5116, 5071]; - menuId = "Shop"; - npc = Game.getNPC(NPC.Malah); - - break; - default: - throw new Error("Invalid shopbot NPC."); - } - - if (!npc) throw new Error("Failed to find NPC."); - if (!this.mover(npc, path)) throw new Error("Failed to move NPC"); - - Town.move("waypoint"); - - let tickCount = getTickCount(); - - while (true) { - if (me.area === town) { - if (npc.startTrade(menuId)) { - if (this.shopItems(classids, amount)) return true; - } - - me.cancel(); - } - - me.area === town && Pather.useWaypoint(wpArea); - me.area === wpArea && Pather.useWaypoint(town); - - // end script 5 seconds before we need to exit - if (getTickCount() - tickCount > me.maxgametime - 5000) { - break; - } - - delay(5); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/CreepingFeature.js b/d2bs/kolbot/libs/bots/CreepingFeature.js deleted file mode 100644 index 5768416bf..000000000 --- a/d2bs/kolbot/libs/bots/CreepingFeature.js +++ /dev/null @@ -1,18 +0,0 @@ -/** -* @filename CreepingFeature.js -* @author theBGuy -* @desc kill Creeping Feature -* -*/ - -function CreepingFeature() { - Town.doChores(); - Town.goToTown(2); - - Pather.journeyTo(sdk.areas.StonyTombLvl2); - Pather.moveToPreset(sdk.areas.StonyTombLvl2, sdk.unittype.Monster, sdk.monsters.preset.CreepingFeature); - Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.CreepingFeature)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/CrushTele.js b/d2bs/kolbot/libs/bots/CrushTele.js deleted file mode 100644 index ae38c61bf..000000000 --- a/d2bs/kolbot/libs/bots/CrushTele.js +++ /dev/null @@ -1,54 +0,0 @@ -/** -* @filename CrushTele.js -* @author kolton -* @desc Auto tele for classic rush only. Hit the "-" numpad in strategic areas. -* -*/ - -function CrushTele() { - let go = false; - - addEventListener("keyup", - function (key) { - key === sdk.keys.NumpadDash && (go = true); - } - ); - - while (true) { - if (go) { - switch (me.area) { - case sdk.areas.CatacombsLvl2: - Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true); - break; - case sdk.areas.HallsoftheDeadLvl2: - Pather.moveToExit(sdk.areas.HallsoftheDeadLvl3, true); - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest); - break; - case sdk.areas.FarOasis: - Pather.moveToExit([sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3], true); - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest); - break; - case sdk.areas.LostCity: - Pather.moveToExit([sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2], true); - break; - case sdk.areas.CanyonofMagic: - Pather.moveToExit(getRoom().correcttomb, true); - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder); - break; - case sdk.areas.ArcaneSanctuary: - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal, 0, 0, false, true); - break; - case sdk.areas.DuranceofHateLvl2: - Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true); - break; - case sdk.areas.RiverofFlame: - Pather.moveToPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, sdk.objects.DiabloStar); - break; - } - - go = false; - } - - delay(10); - } -} diff --git a/d2bs/kolbot/libs/bots/DeveloperMode.js b/d2bs/kolbot/libs/bots/DeveloperMode.js deleted file mode 100644 index 14ef59b9a..000000000 --- a/d2bs/kolbot/libs/bots/DeveloperMode.js +++ /dev/null @@ -1,290 +0,0 @@ -/** -* @filename Developermode.js -* @author theBGuy -* @desc developer mode made easy - run commands or scripts from chat commands. View packets. See unit info -* -*/ -include("UnitInfo.js"); - -function DeveloperMode() { - let done = false, action = false, command = false, userAddon = false, test = false; - let watchSent = [], watchRecv = [], blockSent = [], blockRecv = []; - let unitInfo; - let runCommand = function (msg) { - if (msg.length <= 1) return; - - let cmd = msg.split(" ")[0].split(".")[1]; - let msgList = msg.split(" "); - - switch (cmd.toLowerCase()) { - case "me": - print("Character Level: " + me.charlvl + " | Area: " + me.area + " | x: " + me.x + ", y: " + me.y); - me.overhead("Character Level: " + me.charlvl + " | Area: " + me.area + " | x: " + me.x + ", y: " + me.y); - - break; - case "useraddon": - userAddon = !userAddon; - me.overhead("userAddon set to " + userAddon); - - break; - case "run": - if (msgList.length < 2) { - print("ÿc1Missing arguments"); - } else { - action = msgList[1]; - } - - break; - case "done": - done = true; - - break; - case "testing": - test = true; - - break; - case "command": - if (msgList.length < 2) { - print("ÿc1Missing arguments"); - } else { - command = msgList.splice(1).join(" "); - } - - break; - case "watch": - if (msgList.length < 3) { - print("ÿc1Missing arguments"); - break; - } - - switch (msgList[1].toLowerCase()) { - case "sent": - if (msgList[2] === "list") { - print("Watching sent packets : ÿc8" + watchSent.join(", ")); - break; - } - - watchSent.push(msgList[2]); - print("Added ÿc80x" + msgList[2] + "ÿc0 (sent) to watch list"); - break; - - case "recv": - if (msgList[2] === "list") { - print("Watching received packets : ÿc8" + watchRecv.join(", ")); - break; - } - - watchRecv.push(msgList[2]); - print("Added ÿc80x" + msgList[2] + "ÿc0 (recv) to watch list"); - break; - - default: - print("ÿc1Invalid argument : " + msgList[1]); - break; - } - - break; - - case "!watch": - if (msgList.length < 3) { - print("ÿc1Missing arguments"); - break; - } - - switch (msgList[1].toLowerCase()) { - case "sent": - if (watchSent.indexOf(msgList[2]) > -1) watchSent.splice(watchSent.indexOf(msgList[2]), 1); - print("Removed packet ÿc80x" + msgList[2] + "ÿc0 (sent) from watch list"); - break; - - case "recv": - if (watchRecv.indexOf(msgList[2]) > -1) watchRecv.splice(watchRecv.indexOf(msgList[2]), 1); - print("Removed packet ÿc80x" + msgList[2] + "ÿc0 (recv) from watch list"); - break; - - default: - print("ÿc1Invalid argument : " + msgList[1]); - break; - } - - break; - - case "block": - if (msgList.length < 3) { - print("ÿc1Missing arguments"); - break; - } - - switch (msgList[1].toLowerCase()) { - case "sent": - if (msgList[2] === "list") { - print("Blocking sent packets : ÿc8" + blockSent.join(", ")); - break; - } - - blockSent.push(msgList[2]); - print("Added ÿc80x" + msgList[2] + "ÿc0 (sent) to block list"); - break; - - case "recv": - if (msgList[2] === "list") { - print("Blocking received packets : ÿc8" + blockRecv.join(", ")); - break; - } - - blockRecv.push(msgList[2]); - print("Added ÿc80x" + msgList[2] + "ÿc0 (recv) to block list"); - break; - - default: - print("ÿc1Invalid argument : " + msgList[1]); - break; - } - - break; - - case "!block": - if (msgList.length < 3) { - print("ÿc1Missing arguments"); - break; - } - - switch (msgList[1].toLowerCase()) { - case "sent": - if (blockSent.indexOf(msgList[2]) > -1) blockSent.splice(blockSent.indexOf(msgList[2]), 1); - print("Removed packet ÿc80x" + msgList[2] + "ÿc0 (sent) from block list"); - break; - - case "recv": - if (blockRecv.indexOf(msgList[2]) > -1) blockRecv.splice(blockRecv.indexOf(msgList[2]), 1); - print("Removed packet ÿc80x" + msgList[2] + "ÿc0 (recv) from block list"); - break; - - default: - print("ÿc1Invalid argument : " + msgList[1]); - break; - } - - break; - } - }; - - // Received packet handler - let packetReceived = function(pBytes) { - let ID = pBytes[0].toString(16); - - // Block received packets from list - if (blockRecv.includes(ID)) return true; - - if (watchRecv.includes(ID)) { - let size = pBytes.length; - let array = [].slice.call(pBytes); - array.shift(); - console.log("ÿc2S ÿc8" + ID + "ÿc0 " + array.join(" ") + " ÿc5(" + size + " Bytes)"); - } - - return false; - }; - - // Sent packet handler - let packetSent = function (pBytes) { - let ID = pBytes[0].toString(16); - - // Block all commands or irc chat from being sent to server - if (ID === "15") { - if (pBytes[3] === 46) { - let str = ""; - - for (let b = 3; b < pBytes.length - 3; b++) { - str += String.fromCharCode(pBytes[b]); - } - - if (pBytes[3] === 46) { - runCommand(str); - return true; - } - } - } - - // Block sent packets from list - if (blockSent.includes(ID)) return true; - - if (watchSent.includes(ID)) { - let size = pBytes.length; - let array = [].slice.call(pBytes); - array.shift(); - console.log("ÿc2C ÿc8" + ID + "ÿc0 " + array.join(" ") + " ÿc5(" + size + " Bytes)"); - } - - return false; - }; - - const copiedConfig = Misc.copy(Config); - - try { - console.log("starting developermode"); - me.overhead("Started developer mode"); - addEventListener("gamepacketsent", packetSent); - addEventListener("gamepacket", packetReceived); - Config.Silence = false; - - while (!done) { - if (action) { - includeIfNotIncluded("bots/" + action + ".js"); - - if (!UnitInfo.cleared) { - UnitInfo.remove(); - userAddon = false; - } - - if (isIncluded("bots/" + action + ".js")) { - try { - Loader.runScript(action); - } catch (e) { - console.error(e); - } - } else { - console.warn("Failed to include: " + action); - } - - me.overhead("Done with action"); - action = false; - } - - if (command) { - if (!UnitInfo.cleared) { - UnitInfo.remove(); - userAddon = false; - } - - try { - eval(command); - } catch (e) { - console.error(e); - } - - me.overhead("Done with action"); - command = false; - } - - if (userAddon) { - !UnitInfo.cleared && !Game.getSelectedUnit() && UnitInfo.remove(); - unitInfo = Game.getSelectedUnit(); - UnitInfo.createInfo(unitInfo); - } - - if (test) { - me.overhead("done"); - test = false; - } - - delay(100); - } - } finally { - removeEventListener("gamepacketsent", packetSent); - removeEventListener("gamepacket", packetReceived); - Config = copiedConfig; - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Diablo.js b/d2bs/kolbot/libs/bots/Diablo.js deleted file mode 100644 index 397115f5e..000000000 --- a/d2bs/kolbot/libs/bots/Diablo.js +++ /dev/null @@ -1,85 +0,0 @@ -/** -* @filename Diablo.js -* @author kolton, theBGuy -* @desc clear Chaos Sanctuary and kill Diablo -* @configurable -* run only Vizier - intended for classic sorc, only kills Vizier -* clear safe spot around seals for leechers - used in conjuction with SealLeecher -* run Fast Diablo - focuses only on popping the seals quickly -* -*/ - -function Diablo() { - Pather._teleport = Pather.teleport; - Common.Diablo.clearRadius = Config.Diablo.ClearRadius; - - // START - Town.doChores(); - !!Config.RandomPrecast ? Precast.doRandomPrecast(true, sdk.areas.RiverofFlame) : Pather.useWaypoint(sdk.areas.RiverofFlame) && Precast.doPrecast(true); - !me.inArea(sdk.areas.RiverofFlame) && Pather.useWaypoint(sdk.areas.RiverofFlame); - - if (!Pather.moveToExit(sdk.areas.ChaosSanctuary, true) && !Pather.moveTo(7790, 5544)) throw new Error("Failed to move to Chaos Sanctuary"); - - Common.Diablo.initLayout(); - - if (Config.Diablo.JustViz) { - Common.Diablo.vizLayout === 1 ? Pather.moveTo(7708, 5269) : Pather.moveTo(7647, 5267); - Config.PublicMode && Pather.makePortal(); - Common.Diablo.vizierSeal(true); - - return true; - } - - try { - if (Config.Diablo.Entrance && !Config.Diablo.Fast) { - Attack.clear(30, 0, false, Common.Diablo.sort); - Pather.moveTo(7790, 5544); - - if (Config.PublicMode && Pather.makePortal()) { - say(Config.Diablo.EntranceTP); - if (Config.Diablo.WalkClear) { - Pather.teleport = false; - } - } - - Pather.moveTo(7790, 5544); - Precast.doPrecast(true); - Attack.clear(30, 0, false, Common.Diablo.sort); - Common.Diablo.followPath(Common.Diablo.entranceToStar); - } else { - Pather.moveTo(7774, 5305); - Attack.clear(15, 0, false, Common.Diablo.sort); - } - - Pather.moveTo(7791, 5293); - - if (Config.PublicMode && Pather.makePortal()) { - say(Config.Diablo.StarTP); - Pather.teleport = !Config.Diablo.WalkClear && Pather._teleport; - } - - Attack.clear(30, 0, false, Common.Diablo.sort); - - try { - Common.Diablo.runSeals(Config.Diablo.SealOrder); - // maybe instead of throwing error if we fail to open seal, add it to an array to re-check before diabloPrep then if that fails throw and error - Config.PublicMode && say(Config.Diablo.DiabloMsg); - console.log("Attempting to find Diablo"); - Common.Diablo.diabloPrep(); - } catch (error) { - console.warn("Diablo wasn't found. Checking seals."); - Common.Diablo.runSeals(Config.Diablo.SealOrder); - Common.Diablo.diabloPrep(); - } - - Attack.kill(sdk.monsters.Diablo); - Pickit.pickItems(); - Config.Diablo.SealLeader && say("done"); - } finally { - if (Pather.teleport !== Pather._teleport) { - Pather.teleport = Pather._teleport; - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/DiabloHelper.js b/d2bs/kolbot/libs/bots/DiabloHelper.js deleted file mode 100644 index 474166dcf..000000000 --- a/d2bs/kolbot/libs/bots/DiabloHelper.js +++ /dev/null @@ -1,160 +0,0 @@ -/** -* @filename DiabloHelper.js -* @author kolton, theBGuy -* @desc help leading player in clearing Chaos Sanctuary and killing Diablo -* -*/ - -function DiabloHelper() { - this.Leader = Config.Leader; - Common.Diablo.waitForGlow = true; - Common.Diablo.clearRadius = Config.DiabloHelper.ClearRadius; - Town.doChores(); - const Worker = require("../modules/Worker"); - - try { - addEventListener("gamepacket", Common.Diablo.diabloLightsEvent); - - if (Config.DiabloHelper.SkipIfBaal) { - let leadTick = getTickCount(); - - Worker.runInBackground.leaderTracker = function () { - if (Common.Diablo.done) return false; - // check every 3 seconds - if (getTickCount() - leadTick < 3000) return true; - leadTick = getTickCount(); - - // check again in another 3 seconds if game wasn't ready - if (!me.gameReady) return true; - if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); - let party = getParty(); - - if (party) { - do { - // Player is in Throne of Destruction or Worldstone Chamber - if ([sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(party.area)) { - if (Loader.scriptName() === "DiabloHelper") { - throw new Error("Party leader is running baal"); - } else { - // kill process - return false; - } - } - } while (party.getNext()); - } - - return true; - }; - } - - Config.DiabloHelper.SafePrecast && Precast.needOutOfTownCast() ? Precast.doRandomPrecast(true, sdk.areas.PandemoniumFortress) : Precast.doPrecast(true); - - if (Config.DiabloHelper.SkipTP) { - !me.inArea(sdk.areas.RiverofFlame) && Pather.useWaypoint(sdk.areas.RiverofFlame); - - if (!Pather.moveTo(7790, 5544)) throw new Error("Failed to move to Chaos Sanctuary"); - !Config.DiabloHelper.Entrance && Pather.moveTo(7774, 5305); - - if (!Misc.poll(() => { - let party = getParty(); - - if (party) { - do { - if ((!DiabloHelper.Leader || party.name === DiabloHelper.Leader) && party.area === sdk.areas.ChaosSanctuary) { - return true; - } - } while (party.getNext()); - } - - Attack.clear(30, 0, false, Common.Diablo.sort); - - return false; - }, Time.minutes(Config.DiabloHelper.Wait), 1000)) throw new Error("Player wait timed out (" + (Config.Leader ? "Leader not" : "No players") + " found in Chaos)"); - } else { - Town.goToTown(4); - Town.move("portalspot"); - !DiabloHelper.Leader && (DiabloHelper.Leader = Misc.autoLeaderDetect({destination: sdk.areas.ChaosSanctuary, quitIf: (area) => [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(area), timeout: Time.minutes(2)})); - - if (!Misc.poll(() => { - if (Pather.getPortal(sdk.areas.ChaosSanctuary, Config.Leader || null) && Pather.usePortal(sdk.areas.ChaosSanctuary, Config.Leader || null)) { - return true; - } - - return false; - }, Time.minutes(Config.DiabloHelper.Wait), 1000)) throw new Error("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); - } - - Common.Diablo.initLayout(); - - let diaTick = getTickCount(); - - Worker.runInBackground.diaSpawned = function () { - if (Common.Diablo.done) return false; - // check every 1/4 second - if (getTickCount() - diaTick < 250) return true; - diaTick = getTickCount(); - - if (Common.Diablo.diabloSpawned) throw new Error("Diablo spawned"); - - return true; - }; - - try { - if (Config.DiabloHelper.Entrance && Common.Diablo.starCoords.distance > Common.Diablo.entranceCoords.distance) { - Attack.clear(35, 0, false, Common.Diablo.sort); - Common.Diablo.followPath(Common.Diablo.entranceToStar); - } else { - Pather.moveTo(7774, 5305); - Attack.clear(35, 0, false, Common.Diablo.sort); - } - - Pather.moveTo(7774, 5305); - Attack.clear(35, 0, false, Common.Diablo.sort); - Common.Diablo.runSeals(Config.DiabloHelper.SealOrder, Config.DiabloHelper.OpenSeals); - Common.Diablo.moveToStar(); - Misc.poll(() => { - if (Common.Diablo.diabloSpawned) return true; - if (Game.getMonster(sdk.monsters.Diablo)) return true; - if ([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(Misc.getPlayerArea(DiabloHelper.Leader))) { - throw new Error("END"); - } - return false; - }, Time.minutes(2), 500); - } catch (e) { - let eMsg = e.message ? e.message : e; - console.log(eMsg); - - if (eMsg === "END") { - return true; - } - } - - try { - !Common.Diablo.diabloSpawned && (Common.Diablo.diaWaitTime += Time.minutes(1)); - console.log("Attempting to find Diablo"); - Common.Diablo.diabloPrep(); - } catch (error) { - console.log("Diablo wasn't found"); - if (Config.DiabloHelper.RecheckSeals) { - try { - console.log("Rechecking seals"); - Common.Diablo.runSeals(Config.DiabloHelper.SealOrder, Config.DiabloHelper.OpenSeals); - Misc.poll(() => Common.Diablo.diabloSpawned, Time.minutes(2), 500); - Common.Diablo.diabloPrep(); - } catch (e2) { - // - } - } - } - - Attack.kill(sdk.monsters.Diablo); - Pickit.pickItems(); - } catch (e) { - console.error(e); - } finally { - Common.Diablo.done = true; - removeEventListener("gamepacket", Common.Diablo.diabloLightsEvent); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Duriel.js b/d2bs/kolbot/libs/bots/Duriel.js deleted file mode 100644 index fade25911..000000000 --- a/d2bs/kolbot/libs/bots/Duriel.js +++ /dev/null @@ -1,55 +0,0 @@ -/** -* @filename Duriel.js -* @author kolton, theBGuy -* @desc kill Duriel -* -*/ - -function Duriel () { - this.killDuriel = function () { - let target = Misc.poll(() => Game.getMonster(sdk.monsters.Duriel), 1000, 200); - if (!target) throw new Error("Duriel not found."); - - Config.MFLeader && Pather.makePortal() && say("kill " + sdk.monsters.Duriel); - - for (let i = 0; i < 300 && target.attackable; i += 1) { - ClassAttack.doAttack(target); - target.distance <= 10 && Pather.moveTo(22638, me.y < target.y ? 15722 : 15693); - } - - return target.dead; - }; - - if (!me.inArea(sdk.areas.CanyonofMagic)) { - Town.doChores(); - Pather.useWaypoint(sdk.areas.CanyonofMagic); - } - - Precast.doPrecast(true); - - if (!Pather.moveToExit(getRoom().correcttomb, true)) throw new Error("Failed to move to Tal Rasha's Tomb"); - if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder, -11, 3)) throw new Error("Failed to move to Orifice"); - - me.hardcore && !me.sorceress && Attack.clear(5); - - let unit = Game.getObject(sdk.objects.PortaltoDurielsLair); - - if (Skill.useTK(unit)) { - Misc.poll(function () { - Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit) && delay(100); - return me.inArea(sdk.areas.DurielsLair); - }, 1000, 200); - } - - if (!me.inArea(sdk.areas.DurielsLair) && (!unit || !Pather.useUnitEx({unit: unit}, sdk.areas.DurielsLair))) { - Attack.clear(10); - Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair); - } - - if (!me.inArea(sdk.areas.DurielsLair)) throw new Error("Failed to move to Duriel"); - - me.sorceress && me.classic ? this.killDuriel() : Attack.kill(sdk.monsters.Duriel); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Eldritch.js b/d2bs/kolbot/libs/bots/Eldritch.js deleted file mode 100644 index c386b3f16..000000000 --- a/d2bs/kolbot/libs/bots/Eldritch.js +++ /dev/null @@ -1,41 +0,0 @@ -/** -* @filename Eldritch.js -* @author kolton -* @desc kill Eldritch the Rectifier, optionally kill Shenk the Overseer, Dac Farren and open chest -* -*/ - -function Eldritch() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.FrigidHighlands); - Precast.doPrecast(true); - let {x, y} = me; - Pather.moveTo(3745, 5084); - Attack.kill(getLocaleString(sdk.locale.monsters.EldritchtheRectifier)); - - try { - // FrigidHighlands returns invalid size with getBaseStat('leveldefs', 111, ['SizeX', 'SizeX(N)', 'SizeX(H)'][me.diff]); - // Could this be causing crashes here? - if (Config.Eldritch.OpenChest && Pather.moveNearPreset(sdk.areas.FrigidHighlands, sdk.unittype.Object, sdk.objects.LargeSparklyChest, 10)) { - Misc.openChest(sdk.objects.FrigidHighlandsChest) && Pickit.pickItems(); - // check distance from current location to shenk and if far tp to town and use wp instead - [x, y].distance > 120 && Town.goToTown() && Pather.useWaypoint(sdk.areas.FrigidHighlands); - } - } catch (e) { - console.warn("(Eldritch) :: Failed to open chest. " + e); - } - - try { - if (Config.Eldritch.KillShenk && Pather.moveToExit(sdk.areas.BloodyFoothills, false) && Pather.moveTo(3876, 5130)) { - Attack.kill(getLocaleString(sdk.locale.monsters.ShenktheOverseer)); - } - } catch (e) { - console.warn("(Eldritch) :: Failed to Kill Shenk. " + e); - } - - if (Config.Eldritch.KillDacFarren && Pather.moveNearPreset(sdk.areas.BloodyFoothills, sdk.unittype.Monster, sdk.monsters.preset.DacFarren, 10) && Pather.moveTo(4478, 5108)) { - Attack.kill(getLocaleString(sdk.locale.monsters.DacFarren)); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Endugu.js b/d2bs/kolbot/libs/bots/Endugu.js deleted file mode 100644 index d6f9bc947..000000000 --- a/d2bs/kolbot/libs/bots/Endugu.js +++ /dev/null @@ -1,21 +0,0 @@ -/** -* @filename Endugu.js -* @author kolton -* @desc kill Witch Doctor Endugu -* -*/ - -function Endugu() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.FlayerJungle); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.FlayerDungeonLvl1, sdk.areas.FlayerDungeonLvl2, sdk.areas.FlayerDungeonLvl3], true) - || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsBrainChest)) { - throw new Error("Failed to move to Endugu"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.WitchDoctorEndugu)); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Eyeback.js b/d2bs/kolbot/libs/bots/Eyeback.js deleted file mode 100644 index f1073529b..000000000 --- a/d2bs/kolbot/libs/bots/Eyeback.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename Eyeback.js -* @author kolton -* @desc kill Eyeback the Unleashed -* -*/ - -function Eyeback() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.ArreatPlateau); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.FrigidHighlands, sdk.unittype.Monster, sdk.monsters.preset.EyebacktheUnleashed)) { - throw new Error("Failed to move to Eyeback the Unleashed"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.EyebacktheUnleashed)); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Fangskin.js b/d2bs/kolbot/libs/bots/Fangskin.js deleted file mode 100644 index 9be8a33a1..000000000 --- a/d2bs/kolbot/libs/bots/Fangskin.js +++ /dev/null @@ -1,24 +0,0 @@ -/** -* @filename Fangskin.js -* @author theBGuy -* @desc kill Fangskin -* -*/ - -function Fangskin() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.LostCity); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2], true)) { - throw new Error("Failed to move to Fangskin"); - } - - // casters can kill fangskin from the altar spot for better safety - Pather.canTeleport() && Skill.getRange(Config.AttackSkill[1] > 10) && Pather.moveTo(15044, 14045); - - Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Fangskin)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Follower.js b/d2bs/kolbot/libs/bots/Follower.js deleted file mode 100644 index dde2aee5d..000000000 --- a/d2bs/kolbot/libs/bots/Follower.js +++ /dev/null @@ -1,650 +0,0 @@ -/** -* @filename Follower.js -* @author kolton, theBGuy -* @desc Controllable bot to follow around leader like an additonal merc -* @Commands -* @Main -* 1 - take leader's tp from town / move to leader's town -* 2 - take leader's tp to town -* 3 - town manager -* c - get corpse -* p - pick items -* s - toggle stop -* s - toggle stop -* @Attack -* a - attack toggle for all -* a - attack toggle for -* aon - attack on for all -* aon - attack on for -* aoff - attack off for all -* aoff - attack off for -* @Teleport *** characters without teleport skill will ignore tele command *** -* tele - toggle teleport for all -* tele - toggle teleport for -* tele on - teleport on for all -* tele on - teleport on for -* tele off - teleport off for all -* tele off - teleport off for -* @Skills *** refer to skills.txt *** -* all skill - change skill for all. refer to skills.txt -* skill - change skill for -* skill - change skill for all characters of certain class *** any part of class name will do *** for example: "sorc skill 36", "zon skill 0", "din skill 106" -* Auras: *** refer to skills.txt *** -* all aura - change aura for all paladins -* aura - change aura for -* @Town -* a2-5 - move to appropriate act (after quest) !NOTE: Disable 'no sound' or game will crash! -* talk - talk to a npc in town -* @Misc -* quiet - stop announcing in chat -* cow - enter red cow portal -* wp - all players activate a nearby wp -* wp - activates a nearby wp -* bo - barbarian precast -* tp - make a TP. Needs a TP tome if not using custom libs. -* move - move in a random direction (use if you're stuck by followers) -* reload - reload script. Use only in case of emergency, or after editing character config. -* quit - exit game -* -*/ - -function Follower() { - let i, stop, leader, leaderUnit, charClass, piece, skill, result, unit, player, coord; - let commanders = [Config.Leader]; - let allowSay = true; - let attack = true; - let openContainers = true; - let action = ""; - - this.announce = function (msg = "") { - if (!allowSay) return; - say(msg); - }; - - // Change areas to where leader is - this.checkExit = function (unit, area) { - if (unit.inTown) return false; - - let target; - let exits = getArea().exits; - - for (let i = 0; i < exits.length; i += 1) { - if (exits[i].target === area) { - return 1; - } - } - - if (unit.inTown) { - target = Game.getObject("waypoint"); - - if (target && getDistance(me, target) < 20) { - return 3; - } - } - - target = Game.getObject("portal"); - - if (target) { - do { - if (target.objtype === area) { - Pather.usePortal(null, null, target); - - return 2; - } - } while (target.getNext()); - } - - // Arcane<->Cellar portal - if ((me.inArea(sdk.areas.ArcaneSanctuary) && area === sdk.areas.PalaceCellarLvl3) - || (me.inArea(sdk.areas.PalaceCellarLvl3) && area === sdk.areas.ArcaneSanctuary)) { - Pather.usePortal(null); - - return 4; - } - - // Tal-Rasha's tomb->Duriel's lair - if (me.area >= sdk.areas.TalRashasTomb1 && me.area <= sdk.areas.TalRashasTomb7 && area === sdk.areas.DurielsLair) { - Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, area); - - return 4; - } - - // Throne->Chamber - if (me.inArea(sdk.areas.ThroneofDestruction) && area === sdk.areas.WorldstoneChamber) { - target = Game.getObject(sdk.objects.WorldstonePortal); - - if (target) { - Pather.usePortal(null, null, target); - - return 4; - } - } - - return false; - }; - - // Talk to a NPC - this.talk = function (name) { - if (!me.inTown) { - this.announce("I'm not in town!"); - - return false; - } - - if (typeof name !== "string") { - this.announce("No NPC name given."); - - return false; - } - - try { - Town.npcInteract(name); - } catch (e) { - this.announce((typeof e === "object" && e.message ? e.message : typeof e === "string" ? e : "Failed to talk to " + name)); - } - - Town.move("portalspot"); - - return false; - }; - - // Change act after completing last act quest - this.changeAct = function (act) { - let preArea = me.area; - - if (me.area >= sdk.areas.townOfAct(act)) { - this.announce("My current act is higher than " + act); - return false; - } - - switch (act) { - case 2: - Town.npcInteract("Warriv", false) && Misc.useMenu(sdk.menu.GoEast); - - break; - case 3: - Town.npcInteract("Jerhyn"); - Town.move("Meshif") && Misc.useMenu(sdk.menu.SailEast); - - break; - case 4: - if (me.inTown) { - Town.npcInteract("Cain"); - Town.move("portalspot"); - Pather.usePortal(sdk.areas.DuranceofHateLvl3, null); - } - - delay(1500); - - let target = Game.getObject(sdk.objects.RedPortalToAct4); - target && Pather.moveTo(target.x - 3, target.y - 1); - - Pather.usePortal(null); - - break; - case 5: - if (Town.npcInteract("Tyrael")) { - try { - Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct5, sdk.areas.Harrogath); - } catch (a5e) { - break; - } - } - - break; - } - - delay(2000); - - while (!me.area) { - delay(500); - } - - if (me.area === preArea) { - me.cancel(); - Town.move("portalspot"); - this.announce("Act change failed."); - - return false; - } - - Town.move("portalspot"); - this.announce("Act change successful."); - act === 2 && this.announce("Don't forget to talk to Drognan after getting the Viper Amulet!"); - - return true; - }; - - this.pickPotions = function (range = 5) { - if (me.dead) return false; - - Town.clearBelt(); - - while (!me.idle) { - delay(40); - } - - let pickList = []; - let item = Game.getItem(); - - if (item) { - do { - if (item.onGroundOrDropping && item.itemType >= sdk.items.type.HealingPotion && item.itemType <= sdk.items.type.RejuvPotion && item.distance <= range) { - pickList.push(copyUnit(item)); - } - } while (item.getNext()); - } - - pickList.sort(Pickit.sortItems); - - while (pickList.length > 0) { - item = pickList.shift(); - - if (item && copyUnit(item).x) { - let status = Pickit.checkItem(item).result; - - if (status && Pickit.canPick(item)) { - Pickit.pickItem(item, status); - } - } - } - - return true; - }; - - this.chatEvent = function (nick, msg) { - if (msg && nick === Config.Leader) { - switch (msg) { - case "tele": - case me.name + " tele": - if (Pather.teleport) { - Pather.teleport = false; - - this.announce("Teleport off."); - } else { - Pather.teleport = true; - - this.announce("Teleport on."); - } - - break; - case "tele off": - case me.name + " tele off": - Pather.teleport = false; - - this.announce("Teleport off."); - - break; - case "tele on": - case me.name + " tele on": - Pather.teleport = true; - - this.announce("Teleport on."); - - break; - case "a": - case me.name + " a": - if (attack) { - attack = false; - - this.announce("Attack off."); - } else { - attack = true; - - this.announce("Attack on."); - } - - break; - case "flash": - Packet.flash(me.gid); - - break; - case "quiet": - allowSay = !allowSay; - - break; - case "aoff": - case me.name + " aoff": - attack = false; - - this.announce("Attack off."); - - break; - case "aon": - case me.name + " aon": - attack = true; - - this.announce("Attack on."); - - break; - case "quit": - case me.name + " quit": - quit(); - - break; - case "s": - case me.name + " s": - if (stop) { - stop = false; - - this.announce("Resuming."); - } else { - stop = true; - - this.announce("Stopping."); - } - - break; - case "r": - me.dead && me.revive(); - - break; - default: - if (me.paladin && msg.includes("aura ")) { - piece = msg.split(" ")[0]; - - if (piece === me.name || piece === "all") { - skill = parseInt(msg.split(" ")[2], 10); - - if (me.getSkill(skill, sdk.skills.subindex.SoftPoints)) { - this.announce("Active aura is: " + skill); - - Config.AttackSkill[2] = skill; - Config.AttackSkill[4] = skill; - - Skill.setSkill(skill, sdk.skills.hand.Right); - } else { - this.announce("I don't have that aura."); - } - } - - break; - } - - if (msg.includes("skill ")) { - piece = msg.split(" ")[0]; - - if (charClass.includes(piece) || piece === me.name || piece === "all") { - skill = parseInt(msg.split(" ")[2], 10); - - if (me.getSkill(skill, sdk.skills.subindex.SoftPoints)) { - this.announce("Attack skill is: " + skill); - - Config.AttackSkill[1] = skill; - Config.AttackSkill[3] = skill; - } else { - this.announce("I don't have that skill."); - } - } - - break; - } - - action = msg; - - break; - } - } - - if (msg && msg.split(" ")[0] === "leader" && commanders.indexOf(nick) > -1) { - piece = msg.split(" ")[1]; - - if (typeof piece === "string") { - if (commanders.indexOf(piece) === -1) { - commanders.push(piece); - } - - this.announce("Switching leader to " + piece); - - Config.Leader = piece; - leader = Misc.findPlayer(Config.Leader); - leaderUnit = Misc.getPlayerUnit(Config.Leader); - } - } - }; - - - // START - addEventListener("chatmsg", this.chatEvent); - openContainers && Config.OpenChests.enabled && Config.OpenChests.Types.push("all"); - - // Override config values that use TP - Config.TownCheck = false; - Config.TownHP = 0; - Config.TownMP = 0; - charClass = sdk.player.class.nameOf(me.classid).toLowerCase(); - leader = Misc.poll(() => Misc.findPlayer(Config.Leader), Time.seconds(20), Time.seconds(1)); - - if (!leader) { - this.announce("Leader not found."); - delay(1000); - quit(); - } else { - this.announce("Leader found."); - } - - while (!Misc.inMyParty(Config.Leader)) { - delay(500); - } - - this.announce("Partied."); - - me.inTown && Town.move("portalspot"); - - // Main Loop - while (Misc.inMyParty(Config.Leader)) { - if (me.mode === sdk.player.mode.Dead) { - while (!me.inTown) { - me.revive(); - delay(1000); - } - - Town.move("portalspot"); - this.announce("I'm alive!"); - } - - while (stop) { - delay(500); - } - - if (!me.inTown) { - if (!leaderUnit || !copyUnit(leaderUnit).x) { - leaderUnit = Misc.getPlayerUnit(Config.Leader); - - if (leaderUnit) { - this.announce("Leader unit found."); - } - } - - if (!leaderUnit) { - player = Game.getPlayer(); - - if (player) { - do { - if (player.name !== me.name) { - Pather.moveToUnit(player); - - break; - } - } while (player.getNext()); - } - } - - if (leaderUnit && getDistance(me.x, me.y, leaderUnit.x, leaderUnit.y) <= 60) { - if (getDistance(me.x, me.y, leaderUnit.x, leaderUnit.y) > 4) { - Pather.moveToUnit(leaderUnit); - } - } - - if (attack) { - Attack.clear(20, false, false, false, true); - this.pickPotions(20); - } - - me.paladin && Config.AttackSkill[2] > 0 && Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); - - if (leader.area !== me.area && !me.inTown) { - while (leader.area === 0) { - delay(100); - } - - result = this.checkExit(leader, leader.area); - - switch (result) { - case 1: - this.announce("Taking exit."); - delay(500); - Pather.moveToExit(leader.area, true); - - break; - case 2: - this.announce("Taking portal."); - - break; - case 3: - this.announce("Taking waypoint."); - delay(500); - Pather.useWaypoint(leader.area, true); - - break; - case 4: - this.announce("Special transit."); - - break; - } - - while (me.area === 0) { - delay(100); - } - - leaderUnit = Misc.getPlayerUnit(Config.Leader); - } - } - - switch (action) { - case "cow": - if (me.inArea(sdk.areas.RogueEncampment)) { - Town.move("portalspot"); - !Pather.usePortal(sdk.areas.MooMooFarm) && this.announce("Failed to use cow portal."); - } - - break; - case "move": - coord = CollMap.getRandCoordinate(me.x, -5, 5, me.y, -5, 5); - Pather.moveTo(coord.x, coord.y); - - break; - case "wp": - case me.name + "wp": - if (me.inTown) { - break; - } - - delay(rand(1, 3) * 500); - - unit = Game.getObject("waypoint"); - - if (unit) { - for (i = 0; i < 3; i += 1) { - if (Pather.getWP(me.area)) { - this.announce("Got wp."); - break; - } - } - - i === 3 && this.announce("Failed to get wp."); - } - - me.cancel(); - - break; - case "c": - !me.inTown && Town.getCorpse(); - - break; - case "p": - this.announce("!Picking items."); - Pickit.pickItems(); - openContainers && Misc.openChests(20); - this.announce("!Done picking."); - - break; - case "1": - if (me.inTown && leader.inTown && Misc.getPlayerAct(Config.Leader) !== me.act) { - this.announce("Going to leader's town."); - Town.goToTown(Misc.getPlayerAct(Config.Leader)); - Town.move("portalspot"); - } else if (me.inTown) { - this.announce("Going outside."); - Town.goToTown(Misc.getPlayerAct(Config.Leader)); - Town.move("portalspot"); - - if (!Pather.usePortal(null, leader.name)) { - break; - } - - while (!Misc.getPlayerUnit(Config.Leader) && !me.dead) { - Attack.clear(10); - delay(200); - } - } - - break; - case "2": - if (!me.inTown) { - delay(150); - this.announce("Going to town."); - Pather.usePortal(null, leader.name); - } - - break; - case "3": - if (me.inTown) { - this.announce("Running town chores"); - Town.doChores(); - Town.move("portalspot"); - this.announce("Ready"); - } - - break; - case "h": - me.barbarian && Skill.cast(sdk.skills.Howl); - - break; - case "bo": - // checks if we have cta or warcries - Precast.needOutOfTownCast() && Precast.doPrecast(true); - - break; - case "a2": - case "a3": - case "a4": - case "a5": - this.changeAct(parseInt(action[1], 10)); - - break; - case me.name + " tp": - unit = Town.getTpTool(); - - if (unit) { - unit.interact(); - - break; - } - - this.announce("No TP scrolls or tomes."); - - break; - } - - if (action.indexOf("talk") > -1) { - this.talk(action.split(" ")[1]); - } - - action = ""; - - delay(100); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Frozenstein.js b/d2bs/kolbot/libs/bots/Frozenstein.js deleted file mode 100644 index 6133e4f5c..000000000 --- a/d2bs/kolbot/libs/bots/Frozenstein.js +++ /dev/null @@ -1,21 +0,0 @@ -/** -* @filename Frozenstein.js -* @author kolton -* @desc kill Frozensteinand optionally clear Frozen River -* -*/ - -function Frozenstein() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.CrystalizedPassage); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.FrozenRiver, true) || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform, -5, -5)) { - throw new Error("Failed to move to Frozenstein"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.Frozenstein)); - Config.Frozenstein.ClearFrozenRiver && Attack.clearLevel(Config.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Gamble.js b/d2bs/kolbot/libs/bots/Gamble.js deleted file mode 100644 index 227fe03ab..000000000 --- a/d2bs/kolbot/libs/bots/Gamble.js +++ /dev/null @@ -1,61 +0,0 @@ -/** -* @filename Gamble.js -* @author kolton, theBGuy (added anti-idle) -* @desc keep gambling while other players supply you with gold -* -*/ - -function Gamble() { - let idleTick = 0; - let info = Gambling.getInfo(); - let needGold = false; - - if (!info) throw new Error("Bad Gambling System config."); - - me.maxgametime = 0; - Town.goToTown(1); - - addEventListener("copydata", - function (mode, msg) { - if (needGold && mode === 0 && info.goldFinders.indexOf(msg) > -1) { - print("Got game request from " + msg); - sendCopyData(null, msg, 4, me.gamename + "/" + me.gamepassword); - } - }); - - while (true) { - Town.needGamble() ? Town.gamble() : (needGold = true) && (idleTick = 0); - Town.move("stash"); - - while (needGold) { - // should there be a player count check before getting into this loop? - // Or maybe gamevent for player join/leave, or itemevent for gold dropping? - while (true) { - Town.needGamble() && (needGold = false); - Town.stash(); - - let gold = Game.getItem(sdk.items.Gold, sdk.items.mode.onGround); - - if (!gold || !Pickit.canPick(gold)) { - break; - } - - Pickit.pickItem(gold); - delay(500); - - } - - if (needGold && getTickCount() - idleTick > 0) { - Packet.questRefresh(); - idleTick += rand(1200, 1500) * 1000; - } - - delay(500); - } - - delay(1000); - } - - // eslint-disable-next-line no-unreachable - return true; -} diff --git a/d2bs/kolbot/libs/bots/GemHunter.js b/d2bs/kolbot/libs/bots/GemHunter.js deleted file mode 100644 index 6a8b391ad..000000000 --- a/d2bs/kolbot/libs/bots/GemHunter.js +++ /dev/null @@ -1,38 +0,0 @@ -/** -* @filename GemHunter.js -* @author icommitdesnet -* @desc hunt gem shrines -* -*/ - -/** - * @todo If this script is going to be run, and we run across a gem shrine in a different one we should: - * - Call check if we have an gems to upgrade in the stash instead of always keep some in invo as that takes up space. - * If we do, go get the gem from the stash before activating shrine. - * - We should also then keep track of where the shrine was, (I don't remember if gem shrines regen, so check this) - */ -function GemHunter () { - Town.doChores(); - Town.getGem(); - if (Town.getGemsInInv().length === 0) { - print("ÿc4GemHunterÿc0: no gems in inventory - aborting."); - return false; - } - - for (let i = 0; i < Config.GemHunter.AreaList.length; i++) { - if (Town.getGemsInInv().length > 0) { - print("ÿc4GemHunterÿc0: Moving to " + Pather.getAreaName(Config.GemHunter.AreaList[i])); - Pather.journeyTo(Config.GemHunter.AreaList[i]); - if (i === 0) Precast.doPrecast(true); - if (Misc.getShrinesInArea(Config.GemHunter.AreaList[i], sdk.shrines.Gem, true)) { - Pickit.pickItems(); - print("ÿc4GemHunterÿc0: found a gem Shrine"); - if ((Town.getGemsInInv().length === 0) && (Town.getGemsInStash().length > 0)) { - print("ÿc4GemHunterÿc0: Getting a new Gem in Town."); - Town.visitTown(); // Go to Town and do chores. Will throw an error if it fails to return from Town. - } - } - } - } - return true; -} diff --git a/d2bs/kolbot/libs/bots/GetKeys.js b/d2bs/kolbot/libs/bots/GetKeys.js deleted file mode 100644 index e93532613..000000000 --- a/d2bs/kolbot/libs/bots/GetKeys.js +++ /dev/null @@ -1,36 +0,0 @@ -/** -* @filename GetKeys.js -* @author kolton -* @desc get them keys -* -*/ - -function GetKeys() { - Town.doChores(); - - if (!me.findItems("pk1") || me.findItems("pk1").length < 3) { - try { - Loader.runScript("Countess"); - } catch (countessError) { - print("ÿc1Countess failed"); - } - } - - if (!me.findItems("pk2") || me.findItems("pk2").length < 3) { - try { - Loader.runScript("Summoner", () => Config.Summoner.FireEye = false); - } catch (summonerError) { - print("ÿc1Summoner failed"); - } - } - - if (!me.findItems("pk3") || me.findItems("pk3").length < 3) { - try { - Loader.runScript("Nihlathak"); - } catch (nihlathakError) { - print("ÿc1Nihlathak failed"); - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/GhostBusters.js b/d2bs/kolbot/libs/bots/GhostBusters.js deleted file mode 100644 index 01139c4a9..000000000 --- a/d2bs/kolbot/libs/bots/GhostBusters.js +++ /dev/null @@ -1,144 +0,0 @@ -/** -* @filename GhostBusters.js -* @author kolton -* @desc who you gonna call? -* -*/ - -function GhostBusters() { - this.clearGhosts = function () { - let room = getRoom(); - if (!room) return false; - - let rooms = []; - - do { - rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]); - } while (room.getNext()); - - while (rooms.length > 0) { - rooms.sort(Sort.points); - room = rooms.shift(); - - let result = Pather.getNearestWalkable(room[0], room[1], 15, 2); - - if (result) { - Pather.moveTo(result[0], result[1], 3); - - let monList = []; - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.isGhost && monster.distance <= 30 && monster.attackable) { - monList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - if (!Attack.clearList(monList)) { - return false; - } - } - } - - return true; - }; - - this.cellar = function () { - Pather.useWaypoint(sdk.areas.BlackMarsh); - Precast.doPrecast(true); - - for (let i = sdk.areas.ForgottenTower; i <= sdk.areas.TowerCellarLvl5; i += 1) { - Pather.moveToExit(i, true) && this.clearGhosts(); - } - - return true; - }; - - this.jail = function () { - // gonna use inner cloister wp and travel backwards - Pather.useWaypoint(sdk.areas.InnerCloister); - Precast.doPrecast(true); - - for (let i = sdk.areas.JailLvl3; i >= sdk.areas.JailLvl1; i -= 1) { - Pather.moveToExit(i, true) && this.clearGhosts(); - } - - return true; - }; - - this.cathedral = function () { - Pather.useWaypoint(sdk.areas.InnerCloister); - Precast.doPrecast(true); - Pather.moveToExit(sdk.areas.Cathedral, true); - this.clearGhosts(); - - return true; - }; - - this.tombs = function () { - Pather.useWaypoint(sdk.areas.CanyonofMagic); - Precast.doPrecast(true); - - for (let i = sdk.areas.TalRashasTomb1; i <= sdk.areas.TalRashasTomb7; i += 1) { - Pather.moveToExit(i, true) && this.clearGhosts(); - Pather.moveToExit(sdk.areas.CanyonofMagic, true); - } - - return true; - }; - - this.flayerDungeon = function () { - let areas = [sdk.areas.FlayerDungeonLvl1, sdk.areas.FlayerDungeonLvl2, sdk.areas.FlayerDungeonLvl3]; - - Pather.useWaypoint(sdk.areas.FlayerJungle); - Precast.doPrecast(true); - - while (areas.length) { - Pather.moveToExit(areas.shift(), true) && this.clearGhosts(); - } - - return true; - }; - - this.crystalinePassage = function () { - Pather.useWaypoint(sdk.areas.CrystalizedPassage); - Precast.doPrecast(true); - this.clearGhosts(); - Pather.moveToExit(sdk.areas.FrozenRiver, true) && this.clearGhosts(); - - return true; - }; - - this.glacialTrail = function () { - Pather.useWaypoint(sdk.areas.GlacialTrail); - Precast.doPrecast(true); - this.clearGhosts(); - Pather.moveToExit(sdk.areas.DrifterCavern, true) && this.clearGhosts(); - - return true; - }; - - this.icyCellar = function () { - Pather.useWaypoint(sdk.areas.AncientsWay); - Precast.doPrecast(true); - Pather.moveToExit(sdk.areas.IcyCellar, true) && this.clearGhosts(); - - return true; - }; - - let sequence = ["cellar", "jail", "cathedral", "tombs", "flayerDungeon", "crystalinePassage", "glacialTrail", "icyCellar"]; - - for (let i = 0; i < sequence.length; i += 1) { - Town.doChores(); - - try { - this[sequence[i]](); - } finally { - Town.goToTown(); - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Hephasto.js b/d2bs/kolbot/libs/bots/Hephasto.js deleted file mode 100644 index 5ba0a69ae..000000000 --- a/d2bs/kolbot/libs/bots/Hephasto.js +++ /dev/null @@ -1,25 +0,0 @@ -/** -* @filename Hephasto.js -* @author kolton -* @desc kill Hephasto the Armorer - optionally clear river -* -*/ - -function Hephasto() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.RiverofFlame); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) throw new Error("Failed to move to Hephasto"); - - try { - Attack.kill(getLocaleString(sdk.locale.monsters.HephastoTheArmorer)); - } catch (e) { - print("Heph not found. Carry on"); - } - - Pickit.pickItems(); - Config.Hephasto.ClearRiver && Attack.clearLevel(Config.Hephasto.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/IPHunter.js b/d2bs/kolbot/libs/bots/IPHunter.js deleted file mode 100644 index 42d66b04c..000000000 --- a/d2bs/kolbot/libs/bots/IPHunter.js +++ /dev/null @@ -1,57 +0,0 @@ -/** -* @filename IPHunter.js -* @author kolton, Mercoory -* @desc search for a "hot" IP and stop if the correct server is found -* @changes 2020.01 - more beeps and movements (anti drop measure) when IP is found; overhead messages with countdown timer; logs to D2Bot console -* -*/ - -function IPHunter() { - let ip = Number(me.gameserverip.split(".")[3]); - - if (Config.IPHunter.IPList.indexOf(ip) > -1) { - D2Bot.printToConsole("IPHunter: IP found! - [" + ip + "] Game is : " + me.gamename + "//" + me.gamepassword, sdk.colors.D2Bot.DarkGold); - print("IP found! - [" + ip + "] Game is : " + me.gamename + "//" + me.gamepassword); - me.overhead(":D IP found! - [" + ip + "]"); - me.maxgametime = 0; - - for (let i = 12; i > 0; i -= 1) { - me.overhead(":D IP found! - [" + ip + "]" + (i - 1) + " beep left"); - beep(); // works if windows sounds are enabled - delay(250); - } - - while (true) { - - /* // remove comment if you want beeps at every movement - for (let i = 12; i != 0; i -= 1) { - me.overhead(":D IP found! - [" + ip + "]" + (i-1) + " beep left"); - beep(); // works if windows sounds are enabled - delay(250); - } - */ - - me.overhead(":D IP found! - [" + ip + "]"); - try { - Town.move("waypoint"); - Town.move("stash"); - } catch (e) { - // ensure it doesnt leave game by failing to walk due to desyncing. - } - - for (let i = (12 * 60); i > 0; i -= 1) { - me.overhead(":D IP found! - [" + ip + "] Next movement in: " + i + " sec."); - delay(1000); - } - } - } - - for (let i = (Config.IPHunter.GameLength * 60); i > 0; i -= 1) { - me.overhead(":( IP : [" + (ip) + "] NG: " + i + " sec"); - delay(1000); - } - - D2Bot.printToConsole("IPHunter: IP was [" + ip + "]", sdk.colors.D2Bot.Gray); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Icehawk.js b/d2bs/kolbot/libs/bots/Icehawk.js deleted file mode 100644 index 1fc9a3234..000000000 --- a/d2bs/kolbot/libs/bots/Icehawk.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename Icehawk.js -* @author kolton -* @desc kill Icehawk Riftwing -* -*/ - -function Icehawk() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.KurastBazaar); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2], false)) { - throw new Error("Failed to move to Icehawk"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.IcehawkRiftwing)); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Izual.js b/d2bs/kolbot/libs/bots/Izual.js deleted file mode 100644 index c45f776f3..000000000 --- a/d2bs/kolbot/libs/bots/Izual.js +++ /dev/null @@ -1,21 +0,0 @@ -/** -* @filename Izual.js -* @author kolton -* @desc kill Izual -* -*/ - -function Izual() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.CityoftheDamned); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.PlainsofDespair, sdk.unittype.Monster, sdk.monsters.Izual)) { - throw new Error("Failed to move to Izual."); - } - - Attack.kill(sdk.monsters.Izual); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/KillDclone.js b/d2bs/kolbot/libs/bots/KillDclone.js deleted file mode 100644 index ff74a8a69..000000000 --- a/d2bs/kolbot/libs/bots/KillDclone.js +++ /dev/null @@ -1,24 +0,0 @@ -/** -* @filename KillDclone.js -* @author kolton -* @desc Go to Palace Cellar level 3 and kill Diablo Clone. -* -*/ - -function KillDclone() { - Pather.useWaypoint(sdk.areas.ArcaneSanctuary); - Precast.doPrecast(true); - - if (!Pather.usePortal(null)) { - throw new Error("Failed to move to Palace Cellar"); - } - - Attack.kill(sdk.monsters.DiabloClone); - Pickit.pickItems(); - - if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) { - scriptBroadcast("muleAnni"); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/KurastTemples.js b/d2bs/kolbot/libs/bots/KurastTemples.js deleted file mode 100644 index a847f53cc..000000000 --- a/d2bs/kolbot/libs/bots/KurastTemples.js +++ /dev/null @@ -1,34 +0,0 @@ -/** -* @filename KurastTemples.js -* @author kolton -* @desc clear Kurast Temples -* -*/ - -function KurastTemples() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.KurastBazaar); - Precast.doPrecast(true); - - let areas = [ - sdk.areas.RuinedTemple, sdk.areas.DisusedFane, sdk.areas.ForgottenReliquary, - sdk.areas.ForgottenTemple, sdk.areas.RuinedFane, sdk.areas.DisusedReliquary - ]; - - areas.forEach((area, i) => { - if (!me.inArea(sdk.areas.KurastBazaar) + Math.floor(i / 2)) { - if (!Pather.moveToExit(sdk.areas.KurastBazaar + Math.floor(i / 2), true)) throw new Error("Failed to change area"); - } - - if (!Pather.moveToExit(area, true)) throw new Error("Failed to move to the temple"); - - i === 3 && Precast.doPrecast(true); - Attack.clearLevel(Config.ClearType); - - if (i < 5 && !Pather.moveToExit(sdk.areas.KurastBazaar + Math.floor(i / 2), true)) { - throw new Error("Failed to move out of the temple"); - } - }); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/MFHelper.js b/d2bs/kolbot/libs/bots/MFHelper.js deleted file mode 100644 index a8555d1d6..000000000 --- a/d2bs/kolbot/libs/bots/MFHelper.js +++ /dev/null @@ -1,189 +0,0 @@ -/** -* @filename MFHelper.js -* @author kolton -* @desc help another player kill bosses or clear areas -* -*/ - -function MFHelper() { - let player, playerAct, split; - let oldCommand = ""; - let command = ""; - - function chatEvent (name, msg) { - if (!player) { - let match = [ - "kill", "clearlevel", "clear", "quit", "cows", "council", "goto", "nextup" - ]; - - if (msg) { - for (let i = 0; i < match.length; i += 1) { - if (msg.match(match[i])) { - player = Misc.findPlayer(name); - - break; - } - } - } - } - - player && name === player.name && (command = msg); - } - - addEventListener("chatmsg", chatEvent); - Town.doChores(); - Town.move("portalspot"); - - if (Config.Leader) { - if (!Misc.poll(() => Misc.inMyParty(Config.Leader), 30e4, 1000)) { - throw new Error("MFHelper: Leader not partied"); - } - - player = Misc.findPlayer(Config.Leader); - } - - if (player) { - if (!Misc.poll(() => player.area, 120 * 60, 100 + me.ping)) { - throw new Error("Failed to wait for player area"); - } - - playerAct = Misc.getPlayerAct(Config.Leader); - - if (playerAct && playerAct !== me.act) { - Town.goToTown(playerAct); - Town.move("portalspot"); - } - } - - // START - while (true) { - if (me.softcore && me.mode === sdk.player.mode.Dead) { - while (!me.inTown) { - me.revive(); - delay(1000); - } - - Town.move("portalspot"); - console.log("revived!"); - } - - if (player) { - if (Config.LifeChicken > 0 && me.hpPercent <= Config.LifeChicken) { - Town.heal(); - Town.move("portalspot"); - } - - // Finish MFHelper script if leader is running Diablo or Baal - if ([sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(player.area)) { - break; - } - - if (command !== oldCommand) { - oldCommand = command; - - if (command.includes("quit")) { - break; - } else if (command.includes("goto")) { - console.log("ÿc4MFHelperÿc0: Goto"); - split = command.substr(6); - - try { - if (!!parseInt(split, 10)) { - split = parseInt(split, 10); - } - - Town.goToTown(split, true); - Town.move("portalspot"); - } catch (townerror) { - console.log(townerror); - } - - delay(500 + me.ping); - } else if (command.includes("nextup")) { - split = command.split("nextup ")[1]; - - if (split && ["Diablo", "Baal"].includes(split)) { - break; - } - - delay(500 + me.ping); - } else if (command.includes("cows")) { - console.log("ÿc4MFHelperÿc0: Clear Cows"); - - if (Misc.poll(() => { - Town.goToTown(1) && Pather.usePortal(sdk.areas.MooMooFarm); - return me.inArea(sdk.areas.MooMooFarm); - }, Time.minutes(1), 500 + me.ping)) { - Precast.doPrecast(false); - Common.Cows.clearCowLevel(); - delay(1000); - - if (!Pather.getPortal(null, player.name) || !Pather.usePortal(null, player.name)) { - Town.goToTown(); - } - } else { - console.warn("Failed to use portal. Currently in area: " + me.area); - } - } else { - // check that we are in the town of the players act so we can take their portal - !me.inArea(sdk.areas.townOf(player.area)) && Town.goToTown(sdk.areas.actOf(player.area)); - Misc.poll(() => Pather.usePortal(player.area, player.name), Time.seconds(15), 500 + me.ping); - - delay(1000); // delay to make sure leader's area is accurate - - if (!me.inTown && me.area === player.area) { - Precast.doPrecast(true); - - if (command.includes("kill")) { - console.log("ÿc4MFHelperÿc0: Kill"); - split = command.split("kill ")[1]; - - try { - if (!!parseInt(split, 10)) { - split = parseInt(split, 10); - } - - Attack.kill(split); - Pickit.pickItems(); - } catch (killerror) { - console.error(killerror); - } - } else if (command.includes("clearlevel")) { - console.log("ÿc4MFHelperÿc0: Clear Level " + getArea().name); - Precast.doPrecast(true); - Attack.clearLevel(Config.ClearType); - } else if (command.indexOf("clear") > -1) { - console.log("ÿc4MFHelperÿc0: Clear"); - split = command.split("clear ")[1]; - - try { - if (!!parseInt(split, 10)) { - split = parseInt(split, 10); - } - - Attack.clear(15, 0, split); - } catch (killerror2) { - console.error(killerror2); - } - } else if (command.includes("council")) { - console.log("ÿc4MFHelperÿc0: Kill Council"); - Attack.clearList(Attack.getMob([sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3], 0, 40)); - } - - delay(100); - - if (!Pather.getPortal(null, player.name) || !Pather.usePortal(null, player.name)) { - Town.goToTown(); - } - } else if (!me.inTown && !me.inArea(player.area)) { - Town.goToTown(sdk.areas.actOf(player.area)); - } - } - } - } - - delay(100); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Mausoleum.js b/d2bs/kolbot/libs/bots/Mausoleum.js deleted file mode 100644 index b8d4c854c..000000000 --- a/d2bs/kolbot/libs/bots/Mausoleum.js +++ /dev/null @@ -1,45 +0,0 @@ -/** -* @filename Mausoleum.js -* @author kolton, theBGuy -* @desc clear Mausoleum - optionally kill Bishibosh and Bloodraven along the way. Also optionally clear crypt -* -*/ - -function Mausoleum() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.ColdPlains); - Precast.doPrecast(true); - - if (Config.Mausoleum.KillBishibosh) { - Pather.moveToPreset(sdk.areas.ColdPlains, sdk.unittype.Monster, sdk.monsters.preset.Bishibosh); - Attack.kill(getLocaleString(sdk.locale.monsters.Bishibosh)); - Pickit.pickItems(); - } - - if (!Pather.moveToExit(sdk.areas.BurialGrounds, true)) throw new Error("Failed to move to Burial Grounds"); - - if (Config.Mausoleum.KillBloodRaven) { - Pather.moveToPreset(sdk.areas.BurialGrounds, sdk.unittype.Monster, sdk.monsters.preset.BloodRaven); - Attack.kill(getLocaleString(sdk.locale.monsters.BloodRaven)); - Pickit.pickItems(); - } - - try { - Pather.moveToExit(sdk.areas.Mausoleum, true) && Attack.clearLevel(Config.ClearType); - } catch (e) { - console.error(e); - } - - if (Config.Mausoleum.ClearCrypt) { - // Crypt exit is... awkward - if (!(Pather.moveToExit(sdk.areas.BurialGrounds, true) - && Pather.moveToPreset(sdk.areas.BurialGrounds, sdk.unittype.Stairs, sdk.exits.preset.Crypt) - && Pather.moveToExit(sdk.areas.Crypt, true))) { - throw new Error("Failed to move to Crypt"); - } - - Attack.clearLevel(Config.ClearType); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Mephisto.js b/d2bs/kolbot/libs/bots/Mephisto.js deleted file mode 100644 index d58ca7375..000000000 --- a/d2bs/kolbot/libs/bots/Mephisto.js +++ /dev/null @@ -1,149 +0,0 @@ -/** -* @filename Mephisto.js -* @author kolton, njomnjomnjom -* @desc kill Mephisto -* -*/ - -function Mephisto() { - this.killMephisto = function () { - let pos = {}; - let attackCount = 0; - let meph = Game.getMonster(sdk.monsters.Mephisto); - if (!meph) throw new Error("Mephisto not found!"); - - Config.MFLeader && Pather.makePortal() && say("kill " + meph.classid); - - while (attackCount < 300 && meph.attackable(meph)) { - if (meph.mode === sdk.monsters.mode.Attacking2) { - let angle = Math.round(Math.atan2(me.y - meph.y, me.x - meph.x) * 180 / Math.PI); - let angles = me.y > meph.y ? [-30, -60, -90] : [30, 60, 90]; - - for (let i = 0; i < angles.length; i += 1) { - pos.dist = 18; - pos.x = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * pos.dist + meph.x); - pos.y = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * pos.dist + meph.y); - - if (Attack.validSpot(pos.x, pos.y)) { - me.overhead("move, bitch!"); - Pather.moveTo(pos.x, pos.y); - - break; - } - } - } - - if (ClassAttack.doAttack(meph) < 2) { - break; - } - - attackCount += 1; - } - - return meph.dead; - }; - - this.moat = function () { - let count = 0; - - delay(350); - Pather.moveTo(17563, 8072); - - let mephisto = Game.getMonster(sdk.monsters.Mephisto); - if (!mephisto) throw new Error("Mephisto not found."); - - delay(350); - Pather.moveTo(17575, 8086) && delay(350); - Pather.moveTo(17584, 8091) && delay(1200); - Pather.moveTo(17600, 8095) && delay(550); - Pather.moveTo(17610, 8094) && delay(2500); - Attack.clear(10); - Pather.moveTo(17610, 8094); - - let distance = getDistance(me, mephisto); - - while (distance > 27) { - count += 1; - - Pather.moveTo(17600, 8095) && delay(150); - Pather.moveTo(17584, 8091) && delay(150); - Pather.moveTo(17575, 8086) && delay(150); - Pather.moveTo(17563, 8072) && delay(350); - Pather.moveTo(17575, 8086) && delay(350); - Pather.moveTo(17584, 8091) && delay(1200); - Pather.moveTo(17600, 8095) && delay(550); - Pather.moveTo(17610, 8094) && delay(2500); - Attack.clear(10); - Pather.moveTo(17610, 8094); - - distance = getDistance(me, mephisto); - - if (count >= 5) { - throw new Error("Failed to lure Mephisto."); - } - } - - return true; - }; - - this.killCouncil = function () { - let coords = [17600, 8125, 17600, 8015, 17643, 8068]; - - for (let i = 0; i < coords.length; i += 2) { - Pather.moveTo(coords[i], coords[i + 1]); - Attack.clearList(Attack.getMob([sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3], 0, 40)); - } - - return true; - }; - - Town.doChores(); - Pather.useWaypoint(sdk.areas.DuranceofHateLvl2); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true)) throw new Error("Failed to move to Durance Level 3"); - - Config.Mephisto.KillCouncil && this.killCouncil(); - - if (Config.Mephisto.TakeRedPortal) { - Pather.moveTo(17590, 8068); - delay(400); // Activate the bridge tile - } else { - Pather.moveTo(17566, 8069); - } - - if (me.sorceress && Config.Mephisto.MoatTrick && Pather.canTeleport()) { - this.moat(); - Skill.usePvpRange = true; - Attack.kill(sdk.monsters.Mephisto); - Skill.usePvpRange = false; - } else { - Attack.kill(sdk.monsters.Mephisto); - } - - Pickit.pickItems(); - - if (Config.OpenChests.Enabled) { - Pather.moveTo(17572, 8011) && Attack.openChests(5); - Pather.moveTo(17572, 8125) && Attack.openChests(5); - Pather.moveTo(17515, 8061) && Attack.openChests(5); - } - - if (Config.Mephisto.TakeRedPortal) { - Pather.moveTo(17590, 8068); - let tick = getTickCount(), time = 0; - - // Wait until bridge is there - while (getCollision(me.area, 17601, 8070, 17590, 8068) !== 0 && (time = getTickCount() - tick) < 2000) { - Pather.moveTo(17590, 8068); // Activate it - delay(3); - } - - // If bridge is there, and we can move to the location - if (time < 2000 && Pather.moveTo(17601, 8070)) { - Pather.usePortal(null); - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Nihlathak.js b/d2bs/kolbot/libs/bots/Nihlathak.js deleted file mode 100644 index 4fccab67a..000000000 --- a/d2bs/kolbot/libs/bots/Nihlathak.js +++ /dev/null @@ -1,34 +0,0 @@ -/** -* @filename Nihlathak.js -* @author kolton, theBGuy -* @desc kill Nihlathak -* -*/ - -function Nihlathak() { - Town.doChores(); - - // UseWaypoint if set to or if we already have it - if (Config.Nihlathak.UseWaypoint || getWaypoint(Pather.wpAreas.indexOf(sdk.areas.HallsofPain))) { - Pather.useWaypoint(sdk.areas.HallsofPain); - } else { - Pather.journeyTo(sdk.areas.NihlathaksTemple) && Pather.moveToExit([sdk.areas.HallsofAnguish, sdk.areas.HallsofPain], true); - } - - Precast.doPrecast(false); - - if (!Pather.moveToExit(sdk.areas.HallsofVaught, true)) throw new Error("Failed to go to Nihlathak"); - - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.NihlathaksPlatform, 0, 0, false, true); - - if (Config.Nihlathak.ViperQuit && Game.getMonster(sdk.monsters.TombViper2)) { - print("Tomb Vipers found."); - - return true; - } - - Attack.kill(sdk.monsters.Nihlathak); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/OrgTorch.js b/d2bs/kolbot/libs/bots/OrgTorch.js deleted file mode 100644 index 43ad3ac3c..000000000 --- a/d2bs/kolbot/libs/bots/OrgTorch.js +++ /dev/null @@ -1,539 +0,0 @@ -/** -* @filename OrgTorch.js -* @author kolton, theBGuy -* @desc Convert keys to organs and organs to torches. It can work with TorchSystem to get keys from other characters -* @notes Search for the word "START" and follow the comments if you want to know what this script does and when. -* -*/ - -/** -* @todo: -* - override Town.buyPots, usually uber killers have only a little invo space so they fail to buy/drink all the pregame pots -* change method to buy/drink one pot at a time -* - add ability to team this, possible roles being: -* - taxi (just tele killer around) -* - helper (goes in tp and actuallys kills mob), maybe config for specifc areas like if we use salvation to kill meph -* but have a helper who comes in with max fanat or conviction -* - bo barb or war cry barb would make killing main boss easier with all the surrounding mobs being stunned -*/ - -function OrgTorch () { - this.currentGameInfo = null; - - const portalMode = { - MiniUbers: 0, - UberTristram: 1 - }; - - const OrgTorchData = { - filePath: "logs/OrgTorch-" + me.profile + ".json", - default: {gamename: me.gamename, doneAreas: []}, - - create: function () { - FileTools.writeText(this.filePath, JSON.stringify(this.default)); - return this.default; - }, - - read: function () { - let obj = {}; - try { - let string = FileTools.readText(this.filePath); - obj = JSON.parse(string); - } catch (e) { - return this.default; - } - - return obj; - }, - - update: function (newData) { - let data = this.read(); - Object.assign(data, newData); - FileTools.writeText(this.filePath, JSON.stringify(data)); - }, - - remove: function () { - return FileTools.remove(this.filePath); - } - }; - - this.getQuestItem = function (item) { - if (item) { - let id = item.classid; - let canFit = Storage.Inventory.CanFit(item); - if (!canFit && Pickit.canMakeRoom()) { - console.log("ÿc7Trying to make room for " + Pickit.itemColor(item) + item.name); - Town.visitTown(); - !copyUnit(item).x && (item = Misc.poll(() => Game.getItem(id))); - } - } - return Pickit.pickItem(item); - }; - - // Identify & mule - this.checkTorch = function () { - if (me.inArea(sdk.areas.UberTristram)) { - Pather.moveTo(25105, 5140); - Pather.usePortal(sdk.areas.Harrogath); - } - - Town.doChores(); - - if (!Config.OrgTorch.MakeTorch) return false; - - let torch = me.checkItem({name: sdk.locale.items.HellfireTorch}); - - if (torch.have && Pickit.checkItem(torch.item).result === 1) { - if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) { - scriptBroadcast("muleTorch"); - scriptBroadcast("quit"); - } - - return true; - } - - return false; - }; - - // Try to lure a monster - wait until it's close enough - // needs to be re-done - // should, lure boss AWAY from the others and to us - // create path to boss, move some -> wait to see if aggroed -> if yes - move back and make sure it follows until its safely away from other bosses - this.lure = function (bossId) { - let unit = Game.getMonster(bossId); - - if (unit) { - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (unit.distance <= 10) { - return true; - } - - delay(50); - } - } - - return false; - }; - - // Check if we have complete sets of organs - this.completeSetCheck = function () { - let horns = me.findItems("dhn"); - let brains = me.findItems("mbr"); - let eyes = me.findItems("bey"); - - if (!horns || !brains || !eyes) { - return false; - } - - // We just need one set to make a torch - if (Config.OrgTorch.MakeTorch) { - return horns.length && brains.length && eyes.length; - } - - return horns.length === brains.length && horns.length === eyes.length && brains.length === eyes.length; - }; - - // Get fade in River of Flames - only works if we are wearing an item with ctc Fade - // todo - equipping an item from storage if we have it - this.getFade = function () { - if (Config.OrgTorch.GetFade && !me.getState(sdk.states.Fade) - && me.haveSome([{name: sdk.locale.items.Treachery, equipped: true}, {name: sdk.locale.items.LastWish, equipped: true}, {name: sdk.locale.items.SpiritWard, equipped: true}])) { - console.log(sdk.colors.Orange + "OrgTorch :: " + sdk.colors.White + "Getting Fade"); - // lets figure out what fade item we have before we leave town - let fadeItem = me.findFirst([ - {name: sdk.locale.items.Treachery, equipped: true}, - {name: sdk.locale.items.LastWish, equipped: true}, - {name: sdk.locale.items.SpiritWard, equipped: true} - ]); - - Pather.useWaypoint(sdk.areas.RiverofFlame); - Precast.doPrecast(true); - // check if item is on switch - let mainSlot; - - Pather.moveTo(7811, 5872); - - if (fadeItem.have && fadeItem.item.isOnSwap && me.weaponswitch !== sdk.player.slot.Secondary) { - mainSlot = me.weaponswitch; - me.switchWeapons(sdk.player.slot.Secondary); - } - - Skill.canUse(sdk.skills.Salvation) && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right); - - while (!me.getState(sdk.states.Fade)) { - delay(100); - } - - mainSlot !== undefined && me.weaponswitch !== mainSlot && me.switchWeapons(mainSlot); - - console.log(sdk.colors.Orange + "OrgTorch :: " + sdk.colors.Green + "Fade Achieved"); - } - - return true; - }; - - // Open a red portal. Mode 0 = mini ubers, mode 1 = Tristram - this.openPortal = function (mode) { - let item1 = mode === portalMode.MiniUbers ? me.findItem("pk1", sdk.items.mode.inStorage) : me.findItem("dhn", sdk.items.mode.inStorage); - let item2 = mode === portalMode.MiniUbers ? me.findItem("pk2", sdk.items.mode.inStorage) : me.findItem("bey", sdk.items.mode.inStorage); - let item3 = mode === portalMode.MiniUbers ? me.findItem("pk3", sdk.items.mode.inStorage) : me.findItem("mbr", sdk.items.mode.inStorage); - - Town.goToTown(5); - Town.doChores(); - - if (Town.openStash() && Cubing.emptyCube()) { - if (!Storage.Cube.MoveTo(item1) - || !Storage.Cube.MoveTo(item2) - || !Storage.Cube.MoveTo(item3) - || !Cubing.openCube()) { - return false; - } - - transmute(); - delay(1000); - - let portal = Game.getObject(sdk.objects.RedPortal); - - if (portal) { - do { - switch (mode) { - case portalMode.MiniUbers: - if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].includes(portal.objtype) - && this.currentGameInfo.doneAreas.indexOf(portal.objtype) === -1) { - return copyUnit(portal); - } - - break; - case portalMode.UberTristram: - if (portal.objtype === sdk.areas.UberTristram) { - return copyUnit(portal); - } - - break; - } - } while (portal.getNext()); - } - } - - return false; - }; - - this.matronsDen = function () { - let dHorns = me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length; - - Precast.doPrecast(true); - Pather.moveToPreset(sdk.areas.MatronsDen, sdk.unittype.Object, sdk.objects.SmallSparklyChest, 2, 2); - Attack.kill(sdk.monsters.Lilith); - Pickit.pickItems(); - this.getQuestItem(Game.getItem(sdk.items.quest.DiablosHorn)); - Town.goToTown(); - - // we sucessfully picked up the horn - return (me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length > dHorns); - }; - - this.forgottenSands = function () { - let bEyes = me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length; - - Precast.doPrecast(true); - - let nodes = [ - {x: 20196, y: 8694}, - {x: 20308, y: 8588}, - {x: 20187, y: 8639}, - {x: 20100, y: 8550}, - {x: 20103, y: 8688}, - {x: 20144, y: 8709}, - {x: 20263, y: 8811}, - {x: 20247, y: 8665}, - ]; - - try { - for (let i = 0; i < nodes.length; i++) { - Pather.moveTo(nodes[i].x, nodes[i].y); - delay(500); - - if (Game.getMonster(sdk.monsters.UberDuriel)) { - break; - } - - let eye = Game.getItem(sdk.items.quest.BaalsEye, sdk.items.mode.onGround); - - if (eye && Pickit.pickItem(eye)) { - throw new Error("Found an picked wanted organ"); - } - } - - Attack.kill(sdk.monsters.UberDuriel); - Pickit.pickItems(); - this.getQuestItem(Game.getItem(sdk.items.quest.BaalsEye)); - Town.goToTown(); - } catch (e) { - // - } - - // we sucessfully picked up the eye - return (me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length > bEyes); - }; - - this.furnance = function () { - let mBrain = me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length; - - Precast.doPrecast(true); - Pather.moveToPreset(sdk.areas.FurnaceofPain, sdk.unittype.Object, sdk.objects.SmallSparklyChest, 2, 2); - Attack.kill(sdk.monsters.UberIzual); - Pickit.pickItems(); - this.getQuestItem(Game.getItem(sdk.items.quest.MephistosBrain)); - Town.goToTown(); - - // we sucessfully picked up the brain - return (me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length > mBrain); - }; - - // re-write this, lure doesn't always work and other classes can do ubers - this.uberTrist = function () { - let skillBackup; - let useSalvation = Config.OrgTorch.UseSalvation && Skill.canUse(sdk.skills.Salvation); - - Pather.moveTo(25068, 5078); - Precast.doPrecast(true); - - let nodes = [ - {x: 25040, y: 5101}, - {x: 25040, y: 5166}, - {x: 25122, y: 5170}, - ]; - - for (let i = 0; i < nodes.length; i++) { - Pather.moveTo(nodes[i].x, nodes[i].y); - } - - useSalvation && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right); - this.lure(sdk.monsters.UberMephisto); - Pather.moveTo(25129, 5198); - useSalvation && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right); - this.lure(sdk.monsters.UberMephisto); - - if (!Game.getMonster(sdk.monsters.UberMephisto)) { - Pather.moveTo(25122, 5170); - } - - if (useSalvation) { - skillBackup = Config.AttackSkill[2]; - Config.AttackSkill[2] = sdk.skills.Salvation; - - Attack.init(); - } - - Attack.kill(sdk.monsters.UberMephisto); - - if (skillBackup && useSalvation) { - Config.AttackSkill[2] = skillBackup; - - Attack.init(); - } - - Pather.moveTo(25162, 5141); - delay(3250); - - if (!Game.getMonster(sdk.monsters.UberDiablo)) { - Pather.moveTo(25122, 5170); - } - - Attack.kill(sdk.monsters.UberDiablo); - - if (!Game.getMonster(sdk.monsters.UberBaal)) { - Pather.moveTo(25122, 5170); - } - - Attack.kill(sdk.monsters.UberBaal); - Pickit.pickItems(); - this.currentGameInfo.doneAreas.push(sdk.areas.UberTristram) && OrgTorchData.update(this.currentGameInfo); - this.checkTorch(); - }; - - // Do mini ubers or Tristram based on area we're already in - this.pandemoniumRun = function (portalId) { - switch (me.area) { - case sdk.areas.MatronsDen: - if (this.matronsDen()) { - this.currentGameInfo.doneAreas.push(portalId) && OrgTorchData.update(this.currentGameInfo); - } - - break; - case sdk.areas.ForgottenSands: - if (this.forgottenSands()) { - this.currentGameInfo.doneAreas.push(portalId) && OrgTorchData.update(this.currentGameInfo); - } - - break; - case sdk.areas.FurnaceofPain: - if (this.furnance()) { - this.currentGameInfo.doneAreas.push(portalId) && OrgTorchData.update(this.currentGameInfo); - } - - break; - case sdk.areas.UberTristram: - this.uberTrist(); - - break; - } - }; - - this.runEvent = function (portal) { - if (portal) { - if (Config.OrgTorch.PreGame.Antidote.At.includes(portal.objtype) && Config.OrgTorch.PreGame.Antidote.Drink > 0) { - Town.buyPots(Config.OrgTorch.PreGame.Antidote.Drink, "Antidote", true, true); - } - if (Config.OrgTorch.PreGame.Thawing.At.includes(portal.objtype) && Config.OrgTorch.PreGame.Thawing.Drink > 0) { - Town.buyPots(Config.OrgTorch.PreGame.Thawing.Drink, "Thawing", true, true); - } - Town.move("stash"); - console.log("taking portal: " + portal.objtype); - Pather.usePortal(null, null, portal); - this.pandemoniumRun(portal.objtype); - } - }; - - this.juvCheck = function () { - let needJuvs = 0; - let col = Town.checkColumns(Storage.BeltSize()); - - for (let i = 0; i < 4; i += 1) { - if (Config.BeltColumn[i] === "rv") { - needJuvs += col[i]; - } - } - - print("Need " + needJuvs + " juvs."); - - return needJuvs; - }; - - // ################# // - /* ##### START ##### */ - // ################# // - - // make sure we are picking the organs - Config.PickitFiles.length === 0 && NTIP.OpenFile("pickit/keyorg.nip", true); - - FileTools.exists(OrgTorchData.filePath) && (this.currentGameInfo = OrgTorchData.read()); - - if (!this.currentGameInfo || this.currentGameInfo.gamename !== me.gamename) { - this.currentGameInfo = OrgTorchData.create(); - } - - let portal; - let tkeys = me.findItems("pk1", sdk.items.mode.inStorage).length || 0; - let hkeys = me.findItems("pk2", sdk.items.mode.inStorage).length || 0; - let dkeys = me.findItems("pk3", sdk.items.mode.inStorage).length || 0; - let brains = me.findItems("mbr", sdk.items.mode.inStorage).length || 0; - let eyes = me.findItems("bey", sdk.items.mode.inStorage).length || 0; - let horns = me.findItems("dhn", sdk.items.mode.inStorage).length || 0; - - // Do town chores and quit if MakeTorch is true and we have a torch. - this.checkTorch(); - - // Wait for other bots to drop off their keys. This works only if TorchSystem.js is configured properly. - Config.OrgTorch.WaitForKeys && TorchSystem.waitForKeys(); - - Town.goToTown(5); - Town.move("stash"); - - let redPortals = getUnits(sdk.unittype.Object, sdk.objects.RedPortal) - .filter(el => [sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain, sdk.areas.UberTristram].includes(el.objtype)); - let miniPortals = 0; - let keySetsReq = 3; - let tristOpen = false; - - if (redPortals.length > 0) { - redPortals.forEach(function (portal) { - if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].includes(portal.objtype)) { - miniPortals++; - keySetsReq--; - } else if (portal.objtype === sdk.areas.UberTristram) { - tristOpen = true; - } - }); - } else { - // possible same game name but different day and data file never got deleted - this.currentGameInfo.doneAreas.length > 0 && (this.currentGameInfo = OrgTorchData.create()); - } - - // End the script if we don't have enough keys nor organs - if ((tkeys < keySetsReq || hkeys < keySetsReq || dkeys < keySetsReq) && (brains < 1 || eyes < 1 || horns < 1) && !tristOpen) { - console.log("Not enough keys or organs."); - OrgTorchData.remove(); - - return true; - } - - Config.UseMerc = false; - - // We have enough keys, do mini ubers - if (tkeys >= keySetsReq && hkeys >= keySetsReq && dkeys >= keySetsReq) { - this.getFade(); - Town.goToTown(5); - console.log("Making organs."); - D2Bot.printToConsole("OrgTorch: Making organs.", sdk.colors.D2Bot.DarkGold); - Town.move("stash"); - - // there are already open portals lets check our info on them - if (miniPortals > 0) { - for (let i = 0; i < miniPortals; i++) { - // mini-portal is up but its not in our done areas, probably chickend during it, lets try again - if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].includes(redPortals[i].objtype) - && !currentGameInfo.doneAreas.includes(redPortals[i].objtype)) { - portal = redPortals[i]; - this.runEvent(portal); - } - } - } - - for (let i = 0; i < keySetsReq; i += 1) { - // Abort if we have a complete set of organs - // If Config.OrgTorch.MakeTorch is false, check after at least one portal is made - if ((Config.OrgTorch.MakeTorch || i > 0) && this.completeSetCheck()) { - break; - } - - portal = this.openPortal(portalMode.MiniUbers); - this.runEvent(portal); - } - } - - // Don't make torches if not configured to OR if the char already has one - if (!Config.OrgTorch.MakeTorch || this.checkTorch()) { - OrgTorchData.remove(); - - return true; - } - - // Count organs - brains = me.findItems("mbr", sdk.items.mode.inStorage).length || 0; - eyes = me.findItems("bey", sdk.items.mode.inStorage).length || 0; - horns = me.findItems("dhn", sdk.items.mode.inStorage).length || 0; - - // We have enough organs, do Tristram - or trist is open we may have chickened and came back so check it - // if trist was already open when we joined should we run that first? - if ((brains && eyes && horns) || tristOpen) { - this.getFade(); - Town.goToTown(5); - Town.move("stash"); - - if (!tristOpen) { - console.log("Making torch"); - D2Bot.printToConsole("OrgTorch: Making torch.", sdk.colors.D2Bot.DarkGold); - portal = this.openPortal(portalMode.UberTristram); - } else { - portal = Pather.getPortal(sdk.areas.UberTristram); - } - - this.runEvent(portal); - OrgTorchData.remove(); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/OuterSteppes.js b/d2bs/kolbot/libs/bots/OuterSteppes.js deleted file mode 100644 index 8e6f6712b..000000000 --- a/d2bs/kolbot/libs/bots/OuterSteppes.js +++ /dev/null @@ -1,18 +0,0 @@ -/** -* @filename OuterSteppes.js -* @author kolton -* @desc clear OuterSteppes -* -*/ - -function OuterSteppes() { - if (!Town.goToTown(4)) throw new Error("Failed to go to act 4"); - Town.doChores(); - // force random precast because currently bugs if we precast as soon as we go from inTown to out of town - Precast.doRandomPrecast(true); - if (!Pather.journeyTo(sdk.areas.OuterSteppes)) throw new Error("Failed to move to Outer Steppes"); - - Attack.clearLevel(Config.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Pindleskin.js b/d2bs/kolbot/libs/bots/Pindleskin.js deleted file mode 100644 index e1f1e3c48..000000000 --- a/d2bs/kolbot/libs/bots/Pindleskin.js +++ /dev/null @@ -1,50 +0,0 @@ -/** -* @filename Pindleskin.js -* @author kolton, theBGuy -* @desc kill Pindleskin and optionally Nihlathak -* -*/ - -function Pindleskin() { - Town.goToTown((Config.Pindleskin.UseWaypoint ? undefined : 5)); - Town.doChores(); - - if (Config.Pindleskin.UseWaypoint) { - Pather.useWaypoint(sdk.areas.HallsofPain); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.HallsofAnguish, sdk.areas.NihlathaksTemple], true)) { - throw new Error("Failed to move to Nihlahak's Temple"); - } - } else { - if (!Pather.journeyTo(sdk.areas.NihlathaksTemple)) throw new Error("Failed to use portal."); - Precast.doPrecast(true); - } - - Pather.moveTo(10058, 13234); - - try { - Attack.kill(getLocaleString(sdk.locale.monsters.Pindleskin)); - } catch (e) { - console.error(e); - } - - if (Config.Pindleskin.KillNihlathak) { - if (!Pather.moveToExit([sdk.areas.HallsofAnguish, sdk.areas.HallsofPain, sdk.areas.HallsofVaught], true)) throw new Error("Failed to move to Halls of Vaught"); - - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.NihlathaksPlatform, 10, 10); - - if (Config.Pindleskin.ViperQuit && Game.getMonster(sdk.monsters.TombViper2)) { - console.log("Tomb Vipers found."); - - return true; - } - - Config.Pindleskin.ClearVipers && Attack.clearList(Attack.getMob(sdk.monsters.TombViper2, 0, 20)); - - Attack.kill(sdk.monsters.Nihlathak); - Pickit.pickItems(); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Pit.js b/d2bs/kolbot/libs/bots/Pit.js deleted file mode 100644 index d5c81ed57..000000000 --- a/d2bs/kolbot/libs/bots/Pit.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename Pit.js -* @author kolton -* @desc clear Pit -* -*/ - -function Pit() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.BlackMarsh); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.TamoeHighland, sdk.areas.PitLvl1], true)) throw new Error("Failed to move to Pit level 1"); - Config.Pit.ClearPit1 && Attack.clearLevel(Config.ClearType); - - if (!Pather.moveToExit(sdk.areas.PitLvl2, true, Config.Pit.ClearPath)) throw new Error("Failed to move to Pit level 2"); - Attack.clearLevel(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Questing.js b/d2bs/kolbot/libs/bots/Questing.js deleted file mode 100644 index 86cac4dd2..000000000 --- a/d2bs/kolbot/libs/bots/Questing.js +++ /dev/null @@ -1,432 +0,0 @@ -/** -* @filename Questing.js -* @author kolton, theBGuy -* @desc Do simple quests, the ones that don't have a lot of pre-reqs for now -* -*/ - -// @notes: can't do duriel or meph because all the extra tasks. this is not meant to be autoplay or self rush - -function Questing () { - const log = (msg = "", errorMsg = false) => { - me.overhead(msg); - console.log("ÿc9(Questing) :: " + (errorMsg ? "ÿc1" : "ÿc0") + msg); - }; - - const getQuestItem = (item) => { - if (item) { - let id = item.classid; - let canFit = Storage.Inventory.CanFit(item); - if (!canFit && Pickit.canMakeRoom()) { - console.log("ÿc7Trying to make room for " + Pickit.itemColor(item) + item.name); - Town.visitTown(); - !copyUnit(item).x && (item = Misc.poll(() => Game.getItem(id))); - } - } - return Pickit.pickItem(item); - }; - - let quests = [ - [sdk.quest.id.DenofEvil, "den"], - [sdk.quest.id.ToolsoftheTrade, "smith"], - [sdk.quest.id.TheSearchForCain, "cain"], - [sdk.quest.id.SistersToTheSlaughter, "andy"], - [sdk.quest.id.RadamentsLair, "radament"], - [sdk.quest.id.LamEsensTome, "lamEssen"], - [sdk.quest.id.TheFallenAngel, "izual"], - [sdk.quest.id.TerrorsEnd, "diablo"], - [sdk.quest.id.SiegeOnHarrogath, "shenk"], - [sdk.quest.id.RescueonMountArreat, "barbs"], - [sdk.quest.id.PrisonofIce, "anya"], - [sdk.quest.id.RiteofPassage, "ancients"], - [sdk.quest.id.EyeofDestruction, "baal"] - ]; - - this.den = function () { - log("starting den"); - - if (!Town.goToTown(1) || !Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.DenofEvil], true)) { - throw new Error(); - } - - Precast.doPrecast(true); - Attack.clearLevel(); - Town.goToTown(); - Town.npcInteract("Akara"); - - return true; - }; - - this.smith = function () { - if (Misc.checkQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete)) return true; - - log("starting smith"); - if (!Loader.runScript("Smith")) throw new Error(); - - let malusChest = Game.getObject(sdk.quest.chest.MalusHolder); - !!malusChest && malusChest.distance > 5 && Pather.moveToUnit(malusChest); - Misc.openChest(malusChest); - let malus = Misc.poll(() => Game.getItem(sdk.quest.item.HoradricMalus), 1000, 100); - getQuestItem(malus); - Town.goToTown(); - Town.npcInteract("Charsi"); - - return !!Misc.checkQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete); - }; - - this.cain = function () { - log("starting cain"); - - Town.doChores(); - Common.Questing.cain(); - - return true; - }; - - this.andy = function () { - log("starting andy"); - - Town.doChores(); - Pather.useWaypoint(sdk.areas.CatacombsLvl2, true); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true) || !Pather.moveTo(22582, 9612)) { - throw new Error("andy failed"); - } - - let coords = [ - {x: 22572, y: 9635}, {x: 22554, y: 9618}, - {x: 22542, y: 9600}, {x: 22572, y: 9582}, - {x: 22554, y: 9566} - ]; - - if (Pather.useTeleport()) { - Pather.moveTo(22571, 9590); - } else { - while (coords.length) { - let andy = Game.getMonster(sdk.monsters.Andariel); - - if (andy && andy.distance < 15) { - break; - } - - Pather.moveToUnit(coords[0]); - Attack.clearClassids(sdk.monsters.DarkShaman1); - coords.shift(); - } - } - - Attack.kill(sdk.monsters.Andariel); - Town.goToTown(); - Town.npcInteract("Warriv", false); - Misc.useMenu(sdk.menu.GoEast); - - return true; - }; - - this.radament = function () { - if (!Pather.accessToAct(2)) return false; - - log("starting radament"); - - if (!Pather.journeyTo(sdk.areas.A2SewersLvl3)) { - throw new Error(); - } - - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.A2SewersLvl3, sdk.unittype.Object, sdk.quest.chest.HoradricScrollChest)) { - throw new Error("radament failed"); - } - - Attack.kill(sdk.monsters.Radament); - - let book = Game.getItem(sdk.quest.item.BookofSkill); - getQuestItem(book); - - Town.goToTown(); - Town.npcInteract("Atma"); - - return true; - }; - - this.lamEssen = function () { - if (!Pather.accessToAct(3)) return false; - - log("starting lam essen"); - - if (!Pather.journeyTo(sdk.areas.RuinedTemple)) { - throw new Error("Lam Essen quest failed"); - } - - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.RuinedTemple, sdk.unittype.Object, sdk.quest.chest.LamEsensTomeHolder)) { - throw new Error("Lam Essen quest failed"); - } - - Misc.openChest(sdk.quest.chest.LamEsensTomeHolder); - let book = Misc.poll(() => Game.getItem(sdk.quest.item.LamEsensTome), 1000, 100); - getQuestItem(book); - Town.goToTown(); - Town.npcInteract("Alkor"); - - return true; - }; - - this.izual = function () { - if (!Pather.accessToAct(4)) return false; - - log("starting izual"); - if (!Loader.runScript("Izual")) throw new Error(); - Town.goToTown(); - Town.npcInteract("Tyrael"); - - return true; - }; - - this.diablo = function () { - if (!Pather.accessToAct(4)) return false; - if (Misc.checkQuest(sdk.quest.id.TerrorsEnd, sdk.quest.states.Completed)) return true; - - log("starting diablo"); - if (!Loader.runScript("Diablo")) throw new Error(); - Town.goToTown(4); - - Game.getObject(sdk.objects.RedPortalToAct5) - ? Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct5, sdk.areas.Harrogath) - : Town.npcInteract("Tyrael", false) && Misc.useMenu(sdk.menu.TravelToHarrogath); - - return true; - }; - - this.shenk = function () { - if (!Pather.accessToAct(5)) return false; - if (Misc.checkQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.ReqComplete)) return true; - - log("starting shenk"); - - if (!Town.goToTown() || !Pather.useWaypoint(sdk.areas.FrigidHighlands, true)) { - throw new Error(); - } - - Precast.doPrecast(true); - Pather.moveTo(3883, 5113); - Attack.kill(getLocaleString(sdk.locale.monsters.ShenktheOverseer)); - Town.goToTown(); - - return true; - }; - - this.barbs = function () { - if (!Pather.accessToAct(5)) return false; - - log("starting barb rescue"); - - Pather.journeyTo(sdk.areas.FrigidHighlands); - Precast.doPrecast(true); - - let barbs = (Game.getPresetObjects(me.area, sdk.quest.chest.BarbCage) || []); - - if (!barbs.length) { - log("Couldn't find the barbs"); - - return false; - } - - let coords = []; - - // Dark-f: x-3 - for (let cage = 0; cage < barbs.length; cage += 1) { - coords.push({ - x: barbs[cage].roomx * 5 + barbs[cage].x - 3, - y: barbs[cage].roomy * 5 + barbs[cage].y - }); - } - - for (let i = 0; i < coords.length; i += 1) { - log((i + 1) + "/" + coords.length); - Pather.moveToUnit(coords[i], 2, 0); - let door = Game.getMonster(sdk.monsters.PrisonDoor); - - if (door) { - Pather.moveToUnit(door, 1, 0); - Attack.kill(door); - } - - delay(1500 + me.ping); - } - - Town.npcInteract("qual_kehk"); - - return !!Misc.checkQuest(sdk.quest.id.RescueonMountArreat, sdk.quest.states.Completed); - }; - - this.anya = function () { - if (!Pather.accessToAct(5)) return false; - if (Misc.checkQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.ReqComplete)) return true; - - log("starting anya"); - - if (!Pather.journeyTo(sdk.areas.CrystalizedPassage)) { - throw new Error(); - } - - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.FrozenRiver, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform)) { - throw new Error("Anya quest failed"); - } - - delay(1000); - - let anya = Game.getObject(sdk.objects.FrozenAnya); - - // talk to anya, then cancel her boring speech - Pather.moveToUnit(anya); - Packet.entityInteract(anya); - delay(300); - me.cancel(); - - // get pot from malah, then return to anya - Town.goToTown(); - Town.npcInteract("Malah"); - if (!Misc.poll(() => { - Pather.usePortal(sdk.areas.FrozenRiver, me.name); - return me.inArea(sdk.areas.FrozenRiver); - }, Time.seconds(30), 1000)) throw new Error("Anya quest failed - Failed to return to frozen river"); - - // unfreeze her, cancel her speech again - anya = Game.getObject(sdk.objects.FrozenAnya); - anya.interact(); - delay(1000); - me.cancel(); - - // get reward - Town.goToTown(); - Town.npcInteract("Malah"); - - let scroll = me.scrollofresistance; - !!scroll && scroll.use(); - - return true; - }; - - // @theBGuy - this.ancients = function () { - Town.doChores(); - log("starting ancients"); - - Pather.useWaypoint(sdk.areas.AncientsWay); - Precast.doPrecast(true); - Pather.moveToExit(sdk.areas.ArreatSummit, true); - - // failed to move to Arreat Summit - if (!me.inArea(sdk.areas.ArreatSummit)) return false; - - // ancients prep - Town.doChores(); - [sdk.items.StaminaPotion, sdk.items.AntidotePotion, sdk.items.ThawingPotion].forEach(p => Town.buyPots(10, p, true)); - - let tempConfig = Misc.copy(Config); // save and update config settings - let townChicken = getScript("tools/townchicken.js"); - townChicken && townChicken.running && townChicken.stop(); - - Config.TownCheck = false; - Config.MercWatch = false; - Config.TownHP = 0; - Config.TownMP = 0; - Config.HPBuffer = 15; - Config.MPBuffer = 15; - Config.LifeChicken = 10; - - log("updated settings"); - - Town.buyPotions(); - // re-enter Arreat Summit - if (!Pather.usePortal(sdk.areas.ArreatSummit, me.name)) { - log("Failed to take portal back to Arreat Summit", true); - Pather.journeyTo(sdk.areas.ArreatSummit); - } - - Precast.doPrecast(true); - - // move to altar - if (!Pather.moveToPreset(sdk.areas.ArreatSummit, sdk.unittype.Object, sdk.quest.chest.AncientsAltar)) { - log("Failed to move to ancients' altar", true); - } - - Common.Ancients.touchAltar(); - Common.Ancients.startAncients(true); - - me.cancel(); - Config = tempConfig; - log("restored settings"); - Precast.doPrecast(true); - - // reload town chicken in case we are doing others scripts after this one finishes - let townChick = getScript("tools/TownChicken.js"); - (Config.TownHP > 0 || Config.TownMP > 0) && (townChick && !townChick.running || !townChick) && load("tools/TownChicken.js"); - - try { - if (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed)) { - Pather.moveToExit([sdk.areas.WorldstoneLvl1, sdk.areas.WorldstoneLvl2], true); - Pather.getWP(sdk.areas.WorldstoneLvl2); - } - } catch (err) { - log("Cleared Ancients. Failed to get WSK Waypoint", true); - } - - return true; - }; - - this.baal = function () { - log("starting baal"); - // just run baal script? I mean why re-invent the wheel here - Loader.runScript("Baal"); - Town.goToTown(5); - - return true; - }; - - let didTask = false; - - me.inTown && Town.doChores(); - - for (let i = 0; i < quests.length; i += 1) { - didTask && me.inTown && Town.doChores(); - let j; - - for (j = 0; j < 3; j += 1) { - if (!Misc.checkQuest(quests[i][0], sdk.quest.states.Completed)) { - try { - if (this[quests[i][1]]()) { - didTask = true; - - break; - } - } catch (e) { - continue; - } - } else { - didTask = false; - - break; - } - } - - j === 3 && D2Bot.printToConsole("Questing :: " + quests[i][1] + " quest failed.", sdk.colors.D2Bot.Red); - } - - if (Config.Questing.StopProfile || Loader.scriptList.length === 1) { - D2Bot.printToConsole("All quests done. Stopping profile.", sdk.colors.D2Bot.Green); - D2Bot.stop(); - } else { - log("ÿc9(Questing) :: ÿc2Complete"); - // reload town chicken in case we are doing others scripts after this one finishes - let townChick = getScript("tools/TownChicken.js"); - (Config.TownHP > 0 || Config.TownMP > 0) && (townChick && !townChick.running || !townChick) && load("tools/TownChicken.js"); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Radament.js b/d2bs/kolbot/libs/bots/Radament.js deleted file mode 100644 index 842c0fd9d..000000000 --- a/d2bs/kolbot/libs/bots/Radament.js +++ /dev/null @@ -1,22 +0,0 @@ -/** -* @filename Radament.js -* @author kolton -* @desc kill Radament -* -*/ - -function Radament() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.A2SewersLvl2); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.A2SewersLvl3, true) || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricScrollChest)) { - throw new Error("Failed to move to Radament"); - } - - Attack.kill(sdk.monsters.Radament); - Pickit.pickItems(); - Attack.openChests(20); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Rakanishu.js b/d2bs/kolbot/libs/bots/Rakanishu.js deleted file mode 100644 index 0f619d524..000000000 --- a/d2bs/kolbot/libs/bots/Rakanishu.js +++ /dev/null @@ -1,24 +0,0 @@ -/** -* @filename Rakanishu.js -* @author kolton -* @desc kill Rakanishu and optionally Griswold -* -*/ - -function Rakanishu() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.StonyField); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 0, 0, false, true)) throw new Error("Failed to move to Rakanishu"); - Attack.kill(getLocaleString(sdk.locale.monsters.Rakanishu)); - - if (Config.Rakanishu.KillGriswold && Pather.getPortal(sdk.areas.Tristram)) { - if (!Pather.usePortal(sdk.areas.Tristram)) throw new Error("Failed to move to Tristram"); - - Pather.moveTo(25149, 5180); - Attack.clear(20, 0xF, sdk.monsters.Griswold); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Rushee.js b/d2bs/kolbot/libs/bots/Rushee.js deleted file mode 100644 index 154749a02..000000000 --- a/d2bs/kolbot/libs/bots/Rushee.js +++ /dev/null @@ -1,1062 +0,0 @@ -/** -* @filename Rushee.js -* @author kolton, theBGuy -* @desc Rushee script that works with Rusher -* -*/ - -let Overrides = require("../modules/Override"); - -new Overrides.Override(Town, Town.goToTown, function(orignal, act, wpmenu) { - try { - orignal(act, wpmenu); - - return true; - } catch (e) { - print(e); - - return Pather.useWaypoint(sdk.areas.townOf(me.area)); - } -}).apply(); - -new Overrides.Override(Pather, Pather.getWP, function(orignal, area, clearPath) { - if (area !== me.area) return false; - - for (let i = 0; i < sdk.waypoints.Ids.length; i++) { - let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); - - if (preset) { - let x = (preset.roomx * 5 + preset.x); - let y = (preset.roomy * 5 + preset.y); - if (!me.inTown && [x, y].distance > 15) return false; - - Skill.haveTK ? this.moveNearUnit(preset, 20, {clearSettings: {clearPath: clearPath}}) : this.moveToUnit(preset, 0, 0, clearPath); - - let wp = Game.getObject("waypoint"); - - if (wp) { - for (let j = 0; j < 10; j++) { - if (!getUIFlag(sdk.uiflags.Waypoint)) { - if (wp.distance > 5 && Skill.useTK(wp) && j < 3) { - wp.distance > 21 && Attack.getIntoPosition(wp, 20, sdk.collision.Ranged); - Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, wp); - } else if (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint)) { - this.moveToUnit(wp) && Misc.click(0, 0, wp); - } - } - - if (Misc.poll(() => me.gameReady && getUIFlag(sdk.uiflags.Waypoint), 1000, 150)) { - delay(500); - me.cancelUIFlags(); - - return true; - } - - // handle getUnit bug - if (!getUIFlag(sdk.uiflags.Waypoint) && me.inTown && wp.name.toLowerCase() === "dummy") { - Town.getDistance("waypoint") > 5 && Town.move("waypoint"); - Misc.click(0, 0, wp); - } - - delay(500); - } - } - } - } - - return false; -}).apply(); - -function Rushee() { - let act, leader, target, done = false; - let actions = []; - - this.log = function (msg = "", sayMsg = false) { - print(msg); - sayMsg && say(msg); - }; - - this.useScrollOfRes = function () { - let scroll = me.scrollofresistance; - if (scroll) { - clickItem(sdk.clicktypes.click.item.Right, scroll); - print("Using scroll of resistance"); - } - }; - - this.revive = function () { - while (me.mode === sdk.player.mode.Death) { - delay(40); - } - - if (me.mode === sdk.player.mode.Dead) { - me.revive(); - - while (!me.inTown) { - delay(40); - } - } - }; - - // todo - map the chest to classid so we only need to pass in one value - this.getQuestItem = function (classid, chestid) { - let tick = getTickCount(); - - if (me.getItem(classid)) { - this.log("Already have: " + classid); - return true; - } - - if (me.inTown) return false; - - let chest = Game.getObject(chestid); - - if (!chest) { - this.log("Couldn't find: " + chestid); - return false; - } - - for (let i = 0; i < 5; i++) { - if (Misc.openChest(chest)) { - break; - } - this.log("Failed to open chest: Attempt[" + (i + 1) + "]"); - let coord = CollMap.getRandCoordinate(chest.x, -4, 4, chest.y, -4, 4); - coord && Pather.moveTo(coord.x, coord.y); - } - - let item = Game.getItem(classid); - - if (!item) { - if (getTickCount() - tick < 500) { - delay(500); - } - - return false; - } - - return Pickit.pickItem(item) && delay(1000); - }; - - this.checkQuestMonster = function (classid) { - let monster = Game.getMonster(classid); - - if (monster) { - while (!monster.dead) { - delay(500); - } - - return true; - } - - return false; - }; - - this.tyraelTalk = function () { - let npc = Game.getNPC(NPC.Tyrael); - - if (!npc) return false; - - for (let i = 0; i < 3; i += 1) { - npc.distance > 3 && Pather.moveToUnit(npc); - npc.interact(); - delay(1000 + me.ping); - me.cancel(); - - if (Pather.getPortal(null)) { - me.cancel(); - - break; - } - } - - return Pather.usePortal(null) || Pather.usePortal(null, Config.Leader); - }; - - this.cubeStaff = function () { - let shaft = me.shaft; - let amulet = me.amulet; - - if (!shaft || !amulet) return false; - - Storage.Cube.MoveTo(amulet); - Storage.Cube.MoveTo(shaft); - Cubing.openCube(); - print("making staff"); - transmute(); - delay(750 + me.ping); - - let staff = me.completestaff; - - if (!staff) return false; - - Storage.Inventory.MoveTo(staff); - me.cancel(); - - return true; - }; - - this.placeStaff = function () { - let tick = getTickCount(); - let orifice = Game.getObject(sdk.quest.chest.HoradricStaffHolder); - if (!orifice) return false; - - Misc.openChest(orifice); - - let staff = me.completestaff; - - if (!staff) { - if (getTickCount() - tick < 500) { - delay(500); - } - - return false; - } - - staff.toCursor(); - submitItem(); - delay(750 + me.ping); - - // unbug cursor - let item = me.findItem(-1, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (item && item.toCursor()) { - Storage.Inventory.MoveTo(item); - } - - return true; - }; - - this.changeAct = function (act) { - let preArea = me.area; - - if (me.mode === sdk.player.mode.Dead) { - me.revive(); - - while (!me.inTown) { - delay(500); - } - } - - if (me.act === act || me.act > act) return true; - - try { - switch (act) { - case 2: - if (!Town.npcInteract("Warriv", false)) return false; - Misc.useMenu(sdk.menu.GoEast); - - break; - case 3: - // Non Quester needs to talk to Townsfolk to enable Harem TP - if (!Config.Rushee.Quester) { - // Talk to Atma - if (!Town.npcInteract("Atma")) { - break; - } - } - - Pather.usePortal(sdk.areas.HaremLvl1, Config.Leader); - Pather.moveToExit(sdk.areas.LutGholein, true); - - if (!Town.npcInteract("Jerhyn")) { - Pather.moveTo(5166, 5206); - - return false; - } - - me.cancel(); - Pather.moveToExit(sdk.areas.HaremLvl1, true); - Pather.usePortal(sdk.areas.LutGholein, Config.Leader); - - if (!Town.npcInteract("Meshif", false)) return false; - Misc.useMenu(sdk.menu.SailEast); - - break; - case 4: - if (me.inTown) { - Town.npcInteract("Cain"); - Pather.usePortal(sdk.areas.DuranceofHateLvl3, Config.Leader); - } else { - delay(1500); - } - - Pather.moveTo(17591, 8070); - Pather.usePortal(null); - - break; - case 5: - Town.npcInteract("Tyrael", false); - delay(me.ping + 1); - - if (Game.getObject(sdk.objects.RedPortalToAct5)) { - me.cancel(); - Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct5, sdk.areas.Harrogath); - } else { - Misc.useMenu(sdk.menu.TravelToHarrogath); - } - - break; - } - - delay(1000 + me.ping * 2); - - while (!me.area) { - delay(500); - } - - if (me.area === preArea) { - me.cancel(); - Town.move("portalspot"); - this.log("Act change failed.", Config.LocalChat.Enabled); - - return false; - } - - this.log("Act change done.", Config.LocalChat.Enabled); - } catch (e) { - return false; - } - - return true; - }; - - this.getQuestInfo = function (id) { - // note: end bosses double printed to match able to go to act flag - let quests = [ - ["cain", sdk.quest.id.TheSearchForCain], ["andariel", sdk.quest.id.SistersToTheSlaughter], ["andariel", sdk.quest.id.AbleToGotoActII], - ["radament", sdk.quest.id.RadamentsLair], ["cube", sdk.quest.id.TheHoradricStaff], ["amulet", sdk.quest.id.TheTaintedSun], - ["summoner", sdk.quest.id.TheArcaneSanctuary], ["duriel", sdk.quest.id.TheSevenTombs], ["duriel", sdk.quest.id.AbleToGotoActIII], - ["lamesen", sdk.quest.id.LamEsensTome], ["travincal", sdk.quest.id.TheBlackenedTemple], ["mephisto", sdk.quest.id.TheGuardian], ["mephisto", sdk.quest.id.AbleToGotoActIV], - ["izual", sdk.quest.id.TheFallenAngel], ["diablo", sdk.quest.id.TerrorsEnd], ["diablo", sdk.quest.id.AbleToGotoActV], - ["shenk", sdk.quest.id.SiegeOnHarrogath], ["anya", sdk.quest.id.PrisonofIce], ["ancients", sdk.quest.id.RiteofPassage], ["baal", sdk.quest.id.EyeofDestruction] - ]; - - let quest = quests.find(element => element[1] === id); - - return (!!quest ? quest[0] : ""); - }; - - this.nonQuesterNPCTalk = false; - - addEventListener("chatmsg", - function (who, msg) { - if (who === Config.Leader) { - actions.push(msg); - } - }); - - // START - Town.goToTown(me.highestAct); - me.inTown && Town.move("portalspot"); - - // if we can't find our leader after 5 minutes, I'm thinking they aren't showing up. Lets not wait around forever - leader = Misc.poll(() => Misc.findPlayer(Config.Leader), Time.minutes(5), 1000); - if (!leader) throw new Error("Failed to find my rusher"); - - Config.Rushee.Quester ? this.log("Leader found", Config.LocalChat.Enabled) : console.log("Leader Found: " + Config.Leader); - - // lets figure out if we either are the bumper or have a bumper so we know if we need to stop at the end of the rush - let bumperLevelReq = [20, 40, 60][me.diff]; - // ensure we are the right level to go to next difficulty if not on classic - let nextGame = (Config.Rushee.Bumper && (me.classic || me.charlvl >= bumperLevelReq)); - if (!nextGame) { - // we aren't the bumper, lets figure out if anyone else is a bumper - // hell is the end of a rush so always end profile after - if (Misc.getPlayerCount() > 2 && !me.hell) { - // there is more than just us and the rusher in game - so check party level - nextGame = Misc.checkPartyLevel(bumperLevelReq, leader.name); - } - } - console.debug("Is this our last run? " + (nextGame ? "No" : "Yes")); - - while (true) { - // todo - clean all this up so there is clear distinction between quester/non-quester and no repeat sequnces - try { - if (actions.length > 0) { - switch (actions[0]) { - case "all in": - switch (leader.area) { - case sdk.areas.A2SewersLvl3: - // Pick Book of Skill, use Book of Skill - Town.move("portalspot"); - Pather.usePortal(sdk.areas.A2SewersLvl3, Config.Leader); - delay(500); - - while (true) { - target = Game.getItem(sdk.quest.item.BookofSkill); - - if (!target) { - break; - } - - Pickit.pickItem(target); - delay(250); - target = me.getItem(sdk.quest.item.BookofSkill); - - if (target) { - print("Using book of skill"); - clickItem(sdk.clicktypes.click.item.Right, target); - - break; - } - } - - Pather.usePortal(sdk.areas.LutGholein, Config.Leader); - actions.shift(); - - break; - } - - actions.shift(); - - break; - case "questinfo": - if (!Config.Rushee.Quester) { - actions.shift(); - - break; - } - - say("highestquest " + this.getQuestInfo(me.highestQuestDone)); - actions.shift(); - - break; - case "wpinfo": - if (!Config.Rushee.Quester) { - actions.shift(); - - break; - } - - // go activate wp if we don't know our wps yet - !getWaypoint(1) && Pather.getWP(me.area); - - let myWps = Pather.nonTownWpAreas.slice(0).filter(function (area) { - if (area === sdk.areas.HallsofPain) return false; - if (me.classic && area >= sdk.areas.Harrogath) return false; - if (getWaypoint(Pather.wpAreas.indexOf(area))) return false; - return true; - }); - - say("mywps " + JSON.stringify(myWps)); - actions.shift(); - - break; - case "wp": - if (!me.inTown && !Town.goToTown()) { - this.log("I can't get to town :(", Config.LocalChat.Enabled); - - break; - } - - act = Misc.getPlayerAct(leader); - - if (me.act !== act) { - Town.goToTown(act); - Town.move("portalspot"); - } - - // make sure we talk to cain to access durance - leader.area === sdk.areas.DuranceofHateLvl2 && (!Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)) && Town.npcInteract("Cain"); - - // we aren't the quester but need to talk to npcs in order to be able to get wps from certain areas - (!Config.Rushee.Quester && !this.nonQuesterNPCTalk) && (this.nonQuesterNPCTalk = true); - - Town.getDistance("portalspot") > 10 && Town.move("portalspot"); - if (Pather.usePortal(null, Config.Leader) && Pather.getWP(me.area) && Pather.usePortal(sdk.areas.townOf(me.area), Config.Leader) && Town.move("portalspot")) { - me.inTown && Config.LocalChat.Enabled && say("gotwp"); - } else { - // check for bugged portal - let p = Game.getObject("portal"); - let preArea = me.area; - if (!!p && Misc.click(0, 0, p) && Misc.poll(() => me.area !== preArea, 1000, 100) - && Pather.getWP(me.area) && (Pather.usePortal(sdk.areas.townOf(me.area), Config.Leader) || Pather.useWaypoint(sdk.areas.townOf(me.area)))) { - me.inTown && Config.LocalChat.Enabled && say("gotwp"); - } else { - this.log("Failed to get wp", Config.LocalChat.Enabled); - !me.inTown && Town.goToTown(); - } - } - - actions.shift(); - - break; - case "1": - while (!leader.area) { - delay(500); - } - - act = Misc.getPlayerAct(leader); - - if (me.act !== act) { - Town.goToTown(act); - Town.move("portalspot"); - } - - // we need to talk to certain npcs in order to be able to grab waypoints as a non-quester - if (this.nonQuesterNPCTalk) { - console.debug("Leader Area: " + Pather.getAreaName(leader.area)); - - switch (leader.area) { - case sdk.areas.ClawViperTempleLvl2: - Misc.poll(() => !!(Misc.checkQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.ReqComplete) || Misc.checkQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.PartyMemberComplete), Time.seconds(20), 1000)); - if (Town.npcInteract("Drognan")) { - actions.shift(); - console.debug("drognan done"); - } - - break; - case sdk.areas.ArcaneSanctuary: - Misc.poll(() => !!(Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.ReqComplete) || Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.PartyMemberComplete), Time.seconds(20), 1000)); - if (Town.npcInteract("Atma")) { - actions.shift(); - console.debug("atma done"); - } - - break; - case sdk.areas.Travincal: - Misc.poll(() => !!(Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, 4) || Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.PartyMemberComplete) || Misc.checkQuest(sdk.quest.id.TheGuardian, 8), Time.seconds(20), 1000)); - if (Town.npcInteract("Cain")) { - actions.shift(); - console.debug("cain done"); - } - - break; - case sdk.areas.ArreatSummit: - Misc.poll(() => (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.ReqComplete) || Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.PartyMemberComplete), Time.seconds(20), 1000)); - if (Town.npcInteract("Malah")) { - actions.shift(); - console.debug("malah done"); - } - - break; - } - - me.inTown && Town.move("portalspot"); - } - - if (!Config.Rushee.Quester) { - actions.shift(); - - break; - } - - switch (leader.area) { - case sdk.areas.StonyField: - if (!Pather.usePortal(sdk.areas.StonyField, Config.Leader)) { - this.log("Failed to us portal to stony field", Config.LocalChat.Enabled); - break; - } - - let stones = [ - Game.getObject(sdk.quest.chest.StoneAlpha), - Game.getObject(sdk.quest.chest.StoneBeta), - Game.getObject(sdk.quest.chest.StoneGamma), - Game.getObject(sdk.quest.chest.StoneDelta), - Game.getObject(sdk.quest.chest.StoneLambda) - ]; - - while (stones.some((stone) => !stone.mode)) { - for (let i = 0; i < stones.length; i++) { - let stone = stones[i]; - - if (Misc.openChest(stone)) { - stones.splice(i, 1); - i--; - } - delay(10); - } - } - - let tick = getTickCount(); - // wait up to two minutes - while (getTickCount() - tick < Time.minutes(2)) { - if (Pather.getPortal(sdk.areas.Tristram)) { - Pather.usePortal(sdk.areas.RogueEncampment, Config.Leader); - - break; - } - } - Town.move("portalspot"); - actions.shift(); - - break; - case sdk.areas.DarkWood: - if (!Pather.usePortal(sdk.areas.DarkWood, Config.Leader)) { - this.log("Failed to use portal to dark wood", Config.LocalChat.Enabled); - break; - } - - this.getQuestItem(sdk.items.quest.ScrollofInifuss, sdk.quest.chest.InifussTree); - delay(500); - Pather.usePortal(sdk.areas.RogueEncampment, Config.Leader); - - if (Town.npcInteract("Akara")) { - this.log("Akara done", Config.LocalChat.Enabled); - } - - Town.move("portalspot"); - actions.shift(); - - break; - case sdk.areas.Tristram: - if (!Pather.usePortal(sdk.areas.Tristram, Config.Leader)) { - this.log("Failed to use portal to Tristram", Config.LocalChat.Enabled); - break; - } - - let gibbet = Game.getObject(sdk.quest.chest.CainsJail); - - if (gibbet && !gibbet.mode) { - Pather.moveTo(gibbet.x, gibbet.y); - if (Misc.poll(() => Misc.openChest(gibbet), 2000, 100)) { - Pather.usePortal(sdk.areas.RogueEncampment, Config.Leader); - Town.npcInteract("Akara") && this.log("Akara done", Config.LocalChat.Enabled); - } - } - Town.move("portalspot"); - actions.shift(); - - break; - case sdk.areas.CatacombsLvl4: - if (!Pather.usePortal(sdk.areas.CatacombsLvl4, Config.Leader)) { - this.log("Failed to use portal to catacombs", Config.LocalChat.Enabled); - break; - } - - target = Pather.getPortal(null, Config.Leader); - target && Pather.walkTo(target.x, target.y); - - actions.shift(); - - break; - case sdk.areas.A2SewersLvl3: - Town.move("portalspot"); - - if (Pather.usePortal(sdk.areas.A2SewersLvl3, Config.Leader)) { - actions.shift(); - } - - break; - case sdk.areas.HallsoftheDeadLvl3: - Pather.usePortal(sdk.areas.HallsoftheDeadLvl3, Config.Leader); - this.getQuestItem(sdk.quest.item.Cube, sdk.quest.chest.HoradricCubeChest); - Pather.usePortal(sdk.areas.LutGholein, Config.Leader); - - actions.shift(); - - break; - case sdk.areas.ClawViperTempleLvl2: - Pather.usePortal(sdk.areas.ClawViperTempleLvl2, Config.Leader); - this.getQuestItem(sdk.quest.item.ViperAmulet, sdk.quest.chest.ViperAmuletChest); - Pather.usePortal(sdk.areas.LutGholein, Config.Leader); - - if (Town.npcInteract("Drognan")) { - actions.shift(); - say("drognan done", Config.LocalChat.Enabled); - } - - Town.move("portalspot"); - - break; - case sdk.areas.MaggotLairLvl3: - Pather.usePortal(sdk.areas.MaggotLairLvl3, Config.Leader); - this.getQuestItem(sdk.quest.item.ShaftoftheHoradricStaff, sdk.quest.chest.ShaftoftheHoradricStaffChest); - delay(500); - Pather.usePortal(sdk.areas.LutGholein, Config.Leader); - this.cubeStaff(); - - actions.shift(); - - break; - case sdk.areas.ArcaneSanctuary: - if (!Pather.usePortal(sdk.areas.ArcaneSanctuary, Config.Leader)) { - break; - } - - actions.shift(); - - break; - case sdk.areas.TalRashasTomb1: - case sdk.areas.TalRashasTomb2: - case sdk.areas.TalRashasTomb3: - case sdk.areas.TalRashasTomb4: - case sdk.areas.TalRashasTomb5: - case sdk.areas.TalRashasTomb6: - case sdk.areas.TalRashasTomb7: - Pather.usePortal(null, Config.Leader); - this.placeStaff(); - Pather.usePortal(sdk.areas.LutGholein, Config.Leader); - actions.shift(); - - break; - case sdk.areas.DurielsLair: - Pather.usePortal(sdk.areas.DurielsLair, Config.Leader); - this.tyraelTalk(); - - actions.shift(); - - break; - case sdk.areas.Travincal: - if (!Pather.usePortal(sdk.areas.Travincal, Config.Leader)) { - me.cancel(); - - break; - } - - actions.shift(); - - break; - case sdk.areas.RuinedTemple: - if (!Pather.usePortal(sdk.areas.RuinedTemple, Config.Leader)) { - me.cancel(); - - break; - } - - this.getQuestItem(sdk.quest.item.LamEsensTome, sdk.quest.chest.LamEsensTomeHolder); - Pather.usePortal(sdk.areas.KurastDocktown, Config.Leader); - Town.npcInteract("Alkor"); - Town.move("portalspot"); - actions.shift(); - - - break; - case sdk.areas.DuranceofHateLvl3: - if (!Pather.usePortal(sdk.areas.DuranceofHateLvl3, Config.Leader)) { - me.cancel(); - - break; - } - - actions.shift(); - - break; - case sdk.areas.OuterSteppes: - case sdk.areas.PlainsofDespair: - if (Pather.usePortal(null, Config.Leader)) { - actions.shift(); - } - - break; - case sdk.areas.ChaosSanctuary: - Pather.usePortal(sdk.areas.ChaosSanctuary, Config.Leader); - Pather.moveTo(7762, 5268); - Packet.flash(me.gid); - delay(500); - Pather.walkTo(7763, 5267, 2); - - while (!Game.getMonster(sdk.monsters.Diablo)) { - delay(500); - } - - Pather.moveTo(7763, 5267); - actions.shift(); - - break; - case sdk.areas.BloodyFoothills: - Pather.usePortal(sdk.areas.BloodyFoothills, Config.Leader); - actions.shift(); - - break; - case sdk.areas.FrozenRiver: - Town.npcInteract("Malah"); - - Pather.usePortal(sdk.areas.FrozenRiver, Config.Leader); - delay(500); - - target = Game.getObject(sdk.objects.FrozenAnya); - - if (target) { - Pather.moveToUnit(target); - Misc.poll(() => { - Packet.entityInteract(target); - delay(100); - return !Game.getObject(sdk.objects.FrozenAnya); - }, 1000, 200); - delay(1000); - me.cancel(); - } - - actions.shift(); - - break; - default: // unsupported area - actions.shift(); - - break; - } - - break; - case "2": // Go back to town and check quest - if (!Config.Rushee.Quester) { - // Non-questers can piggyback off quester out messages - switch (leader.area) { - case sdk.areas.OuterSteppes: - case sdk.areas.PlainsofDespair: - me.act === 4 && Misc.checkQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.ReqComplete) && Town.npcInteract("Tyrael"); - - break; - case sdk.areas.BloodyFoothills: - me.act === 5 && Town.npcInteract("Larzuk"); - - break; - case sdk.areas.FrozenRiver: - if (me.act === 5) { - Town.npcInteract("Malah"); - this.useScrollOfRes(); - } - - break; - } - - actions.shift(); - - break; - } - - this.revive(); - - switch (me.area) { - case sdk.areas.CatacombsLvl4: - // Go to town if not there, break if procedure fails - if (!me.inTown && !Pather.usePortal(sdk.areas.RogueEncampment)) { - break; - } - - if (!Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, 4)) { - D2Bot.printToConsole("Andariel quest failed", sdk.colors.D2Bot.Red); - quit(); - } - - actions.shift(); - - break; - case sdk.areas.A2SewersLvl3: - if (!me.inTown && !Pather.usePortal(sdk.areas.LutGholein, Config.Leader)) { - break; - } - - actions.shift(); - - break; - case sdk.areas.ArcaneSanctuary: - if (!me.inTown && !Pather.usePortal(sdk.areas.LutGholein, Config.Leader)) { - break; - } - - Town.npcInteract("Atma"); - - if (!Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.Completed)) { - D2Bot.printToConsole("Summoner quest failed", sdk.colors.D2Bot.Red); - quit(); - } - - Town.move("portalspot"); - actions.shift(); - - break; - case sdk.areas.Travincal: - if (!me.inTown && !Pather.usePortal(sdk.areas.KurastDocktown, Config.Leader)) { - break; - } - - Town.npcInteract("Cain"); - - if (!Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)) { - D2Bot.printToConsole("Travincal quest failed", sdk.colors.D2Bot.Red); - quit(); - } - - Town.move("portalspot"); - actions.shift(); - - break; - case sdk.areas.DuranceofHateLvl3: - if (!Pather.usePortal(sdk.areas.KurastDocktown, Config.Leader)) { - break; - } - - actions.shift(); - - break; - case sdk.areas.OuterSteppes: - case sdk.areas.PlainsofDespair: - if (!me.inTown && !Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader)) { - break; - } - - if (Misc.checkQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.ReqComplete)) { - Town.npcInteract("Tyrael"); - Town.move("portalspot"); - } - - actions.shift(); - - break; - case sdk.areas.ChaosSanctuary: - me.classic && D2Bot.restart(); - - if (!me.inTown && !Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader)) { - break; - } - - actions.shift(); - - break; - case sdk.areas.BloodyFoothills: - if (!me.inTown && !Pather.usePortal(sdk.areas.Harrogath, Config.Leader)) { - break; - } - - Town.npcInteract("Larzuk"); - Town.move("portalspot"); - actions.shift(); - - break; - case sdk.areas.FrozenRiver: - if (!me.inTown && !Pather.usePortal(sdk.areas.FrozenRiver, Config.Leader)) { - break; - } - - Town.npcInteract("Malah"); - this.useScrollOfRes(); - Town.move("portalspot"); - - actions.shift(); - - break; - default: - Town.move("portalspot"); - actions.shift(); - - break; - } - - break; - case "3": // Bumper - if (!Config.Rushee.Bumper) { - actions.shift(); - - break; - } - - while (!leader.area) { - delay(500); - } - - act = Misc.getPlayerAct(leader); - - if (me.act !== act) { - Town.goToTown(act); - Town.move("portalspot"); - } - - switch (leader.area) { - case sdk.areas.ArreatSummit: - if (!Pather.usePortal(sdk.areas.ArreatSummit, Config.Leader)) { - break; - } - - // Wait until portal is gone - while (Pather.getPortal(sdk.areas.Harrogath, Config.Leader)) { - delay(500); - } - - // Wait until portal is up again - while (!Pather.getPortal(sdk.areas.Harrogath, Config.Leader)) { - delay(500); - } - - if (!Pather.usePortal(sdk.areas.Harrogath, Config.Leader)) { - break; - } - - actions.shift(); - - break; - case sdk.areas.WorldstoneChamber: - if (!Pather.usePortal(sdk.areas.WorldstoneChamber, Config.Leader)) { - break; - } - - actions.shift(); - - break; - } - - break; - case "quit": - done = true; - - break; - case "exit": - case "bye ~": - if (!nextGame) { - D2Bot.printToConsole("Rush Complete"); - D2Bot.stop(); - } else { - D2Bot.restart(); - } - - break; - case "a2": - case "a3": - case "a4": - case "a5": - act = actions[0].toString()[1]; - !!act && (act = (parseInt(act, 10) || me.act + 1)); - - if (!this.changeAct(act)) { - break; - } - - Town.move("portalspot"); - actions.shift(); - - break; - case me.name + " quest": - say("I am quester."); - Config.Rushee.Quester = true; - - actions.shift(); - - break; - case "leader": - console.log(Config.Leader + " is my leader in my config. " + leader.name + " is my leader right now"); - Config.LocalChat.Enabled && say(Config.Leader + " is my leader in my config. " + leader.name + " is my leader right now"); - actions.shift(); - - break; - default: // Invalid command - actions.shift(); - - break; - } - } - } catch (e) { - if (me.mode === sdk.player.mode.Dead) { - me.revive(); - - while (!me.inTown) { - delay(500); - } - } - } - - if (getUIFlag(sdk.uiflags.TradePrompt)) { - me.cancel(); - } - - if (done) { - break; - } - - delay(500); - } - - done && quit(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Rusher.js b/d2bs/kolbot/libs/bots/Rusher.js deleted file mode 100644 index a546abc16..000000000 --- a/d2bs/kolbot/libs/bots/Rusher.js +++ /dev/null @@ -1,228 +0,0 @@ -/** -* @filename Rusher.js -* @author kolton, theBGuy -* @desc Rusher script. -* -* @Commands -* master - assigns player as master and listens to his commands -* release - resets master -* pause - pause the rusher -* resume - resume the rusher -* do sequence - stop current action and start the given sequence. -* supported sequences are: andariel, cube, amulet, staff, summoner, duriel, travincal, mephisto, diablo -* Example: do travincal -* -*/ - -function Rusher() { - load("tools/rushthread.js"); - delay(500); - - let i, command, master, commandSplit0; - let commands = []; - let sequence = [ - "cain", "andariel", "radament", "cube", "amulet", "staff", "summoner", "duriel", "lamesen", - "travincal", "mephisto", "izual", "diablo", "shenk", "anya", "ancients", "baal", "givewps" - ]; - let rushThread = getScript("tools/rushthread.js"); - - this.reloadThread = function () { - rushThread = getScript("tools/rushthread.js"); - rushThread && rushThread.stop(); - - delay(500); - load("tools/rushthread.js"); - - rushThread = getScript("tools/rushthread.js"); - - delay(500); - }; - - this.getPartyAct = function () { - let party = getParty(); - let minArea = 999; - - do { - if (party.name !== me.name) { - while (!party.area) { - me.overhead("Waiting for party area info"); - delay(500); - } - - if (party.area < minArea) { - minArea = party.area; - } - } - } while (party.getNext()); - - return sdk.areas.actOf(minArea); - }; - - this.chatEvent = function (nick, msg) { - if (nick !== me.name) { - if (typeof msg !== "string") return; - switch (msg) { - case "master": - if (!master) { - say(nick + " is my master."); - - master = nick; - } else { - say("I already have a master."); - } - - break; - case "release": - if (nick === master) { - say("I have no master now."); - - master = false; - } else { - say("I'm only accepting commands from my master."); - } - - break; - case "quit": - if (nick === master) { - say("bye ~"); - scriptBroadcast("quit"); - } else { - say("I'm only accepting commands from my master."); - } - - break; - default: - if (msg && msg.match(/^do \w|^clear \d|^pause$|^resume$/gi)) { - if (nick === master) { - commands.push(msg); - } else { - say("I'm only accepting commands from my master."); - } - } else if (msg && msg.includes("highestquest")) { - if (!!master && nick === master || !master) { - command = msg; - } else { - say("I'm only accepting commands from my master."); - } - } - - break; - } - } - }; - - addEventListener("chatmsg", this.chatEvent); - - while (Misc.getPartyCount() < Math.min(8, Config.Rusher.WaitPlayerCount)) { - me.overhead("Waiting for players to join"); - delay(500); - } - - // Skip to a higher act if all party members are there - switch (this.getPartyAct()) { - case 2: - say("Party is in act 2, starting from act 2"); - rushThread.send("skiptoact 2"); - - break; - case 3: - say("Party is in act 3, starting from act 3"); - rushThread.send("skiptoact 3"); - - break; - case 4: - say("Party is in act 4, starting from act 4"); - rushThread.send("skiptoact 4"); - - break; - case 5: - say("Party is in act 5, starting from act 5"); - rushThread.send("skiptoact 5"); - - break; - } - - // get info from master - let tick = getTickCount(); - let askAgain = 1; - say("questinfo"); - while (!command) { - // wait up to 3 minutes - if (getTickCount() - tick > Time.minutes(3)) { - break; - } - - if (getTickCount() - tick > Time.minutes(askAgain)) { - say("questinfo"); - askAgain++; - } - } - - if (command) { - commandSplit0 = command.split(" ")[1]; - !!commandSplit0 && sequence.some(el => el.toLowerCase() === commandSplit0) && rushThread.send(command.toLowerCase()); - } - - delay(200); - rushThread.send("go"); - - while (true) { - if (commands.length > 0) { - command = commands.shift(); - - switch (command) { - case "pause": - if (rushThread.running) { - say("Pausing"); - - rushThread.pause(); - } - - break; - case "resume": - if (!rushThread.running) { - say("Resuming"); - - rushThread.resume(); - } - - break; - default: - if (typeof command === "string") { - commandSplit0 = command.split(" ")[0]; - - if (commandSplit0 === undefined) { - break; - } - - if (commandSplit0.toLowerCase() === "do") { - for (i = 0; i < sequence.length; i += 1) { - if (command.split(" ")[1] && sequence[i].match(command.split(" ")[1], "gi")) { - this.reloadThread(); - rushThread.send(command.split(" ")[1]); - - break; - } - } - - i === sequence.length && say("Invalid sequence"); - } else if (commandSplit0.toLowerCase() === "clear") { - if (!isNaN(parseInt(command.split(" ")[1], 10)) && parseInt(command.split(" ")[1], 10) > 0 && parseInt(command.split(" ")[1], 10) <= 132) { - this.reloadThread(); - rushThread.send(command); - } else { - say("Invalid area"); - } - } - } - - break; - } - } - - delay(100); - } - - // eslint-disable-next-line no-unreachable - return true; -} diff --git a/d2bs/kolbot/libs/bots/SealLeecher.js b/d2bs/kolbot/libs/bots/SealLeecher.js deleted file mode 100644 index 4b462c2ea..000000000 --- a/d2bs/kolbot/libs/bots/SealLeecher.js +++ /dev/null @@ -1,99 +0,0 @@ -/** -* @filename SealLeecher.js -* @author probably kolton, theBGuy -* @desc Leecher script. Works in conjuction with SealLeader script. -* -*/ - -function SealLeecher() { - let commands = []; - - Town.goToTown(4); - Town.doChores(); - Town.move("portalspot"); - - if (!Config.Leader) { - D2Bot.printToConsole("You have to set Config.Leader"); - D2Bot.stop(); - - return false; - } - - let chatEvent = function (nick, msg) { - if (nick === Config.Leader) { - commands.push(msg); - } - }; - - try { - addEventListener("chatmsg", chatEvent); - - // Wait until leader is partied - while (!Misc.inMyParty(Config.Leader)) { - delay(1000); - } - - while (Misc.inMyParty(Config.Leader)) { - if (commands.length > 0) { - let command = commands.shift(); - - switch (command) { - case "in": - if (me.inTown) { - Pather.usePortal(sdk.areas.ChaosSanctuary, Config.Leader); - delay(250); - } - - if (getDistance(me, 7761, 5267) < 10) { - Pather.walkTo(7761, 5267, 2); - } - - break; - case "out": - if (!me.inTown) { - Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader); - } - - break; - case "done": - if (!me.inTown) { - Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader); - } - - return true; // End script - } - } - - if (me.dead) { - while (me.mode === sdk.player.mode.Death) { - delay(40); - } - - me.revive(); - - while (!me.inTown) { - delay(40); - } - } - - if (!me.inTown) { - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.attackable && monster.distance < 20) { - me.overhead("HOT"); - Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader); - } - } while (monster.getNext()); - } - } - - delay(100); - } - } finally { - removeEventListener("chatmsg", chatEvent); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/SharpTooth.js b/d2bs/kolbot/libs/bots/SharpTooth.js deleted file mode 100644 index 928ea1b1f..000000000 --- a/d2bs/kolbot/libs/bots/SharpTooth.js +++ /dev/null @@ -1,21 +0,0 @@ -/** -* @filename Sharptooth.js -* @author loshmi -* @desc kill Thresh Socket -* -*/ - -function SharpTooth() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.FrigidHighlands); - Precast.doPrecast(true); - - // FrigidHighlands returns invalid size with getBaseStat('leveldefs', 111, ['SizeX', 'SizeX(N)', 'SizeX(H)'][me.diff]); - // Could this be causing crashes here? - if (!Pather.moveToPreset(sdk.areas.FrigidHighlands, sdk.unittype.Monster, sdk.monsters.preset.SharpToothSayer)) throw new Error("Failed to move to Sharptooth Slayer"); - - Attack.kill(getLocaleString(sdk.locale.monsters.SharpToothSayer)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/ShopBot.js b/d2bs/kolbot/libs/bots/ShopBot.js deleted file mode 100644 index 9cb1f7d2a..000000000 --- a/d2bs/kolbot/libs/bots/ShopBot.js +++ /dev/null @@ -1,298 +0,0 @@ -/** -* @filename ShopBot.js -* @author kolton, theBGuy -* @desc shop for items continually -* -*/ - -function ShopBot() { - let overlayText = { - title: new Text("kolbot shopbot", 50, 245, 2, 1), - cycles: new Text("Cycles in last minute:", 50, 260, 2, 1), - frequency: new Text("Valid item frequency:", 50, 275, 2, 1), - totalCycles: new Text("Total cycles:", 50, 290, 2, 1), - }; - - let tickCount; - let cycles = 0; - let validItems = 0; - let totalCycles = 0; - - Pather.teleport = false; - this.pickEntries = []; - this.npcs = {}; - - this.buildPickList = function () { - let nipfile, filepath = "pickit/shopbot.nip", - filename = filepath.substring(filepath.lastIndexOf("/") + 1, filepath.length); - - if (!FileTools.exists(filepath)) { - Misc.errorReport("ÿc1NIP file doesn't exist: ÿc0" + filepath); - return false; - } - - try { - nipfile = File.open(filepath, 0); - } catch (fileError) { - Misc.errorReport("ÿc1Failed to load NIP: ÿc0" + filename); - } - - if (!nipfile) return false; - - let lines = nipfile.readAllLines(); - nipfile.close(); - - for (let i = 0; i < lines.length; i += 1) { - let info = { - line: i + 1, - file: filename, - string: lines[i] - }; - - let line = NTIP.ParseLineInt(lines[i], info); - line && this.pickEntries.push(line); - } - - return true; - }; - - this.openMenu = function (npc) { - if (!npc || npc.type !== sdk.unittype.NPC) throw new Error("Unit.openMenu: Must be used on NPCs."); - - let interactedNPC = getInteractedNPC(); - - if (interactedNPC && interactedNPC.name !== npc.name) { - Packet.cancelNPC(interactedNPC); - me.cancel(); - } - - if (getUIFlag(sdk.uiflags.NPCMenu)) return true; - - for (let i = 0; i < 10; i += 1) { - npc.distance > 5 && Pather.walkTo(npc.x, npc.y); - - if (!getUIFlag(sdk.uiflags.NPCMenu)) { - Packet.entityInteract(npc); - sendPacket(1, sdk.packets.send.NPCInit, 4, 1, 4, npc.gid); - } - - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(Math.round((i + 1) * 250 / (i / 3 + 1)), me.ping + 1)) { - if (getUIFlag(sdk.uiflags.NPCMenu)) { - return true; - } - - delay(10); - } - } - - me.cancel(); - - return false; - }; - - this.shopItems = function (npc, menuId) { - let bought; - - if (!Storage.Inventory.CanFit({sizex: 2, sizey: 4}) && AutoMule.getMuleItems().length > 0) { - D2Bot.printToConsole("Mule triggered"); - scriptBroadcast("mule"); - scriptBroadcast("quit"); - return true; - } - - if (!npc) return false; - - for (let i = 0; i < 10; i += 1) { - delay(150); - - i % 2 === 0 && sendPacket(1, sdk.packets.send.EntityAction, 4, 1, 4, npc.gid, 4, 0); - - if (npc.itemcount > 0) { - break; - } - } - - let items = npc.getItemsEx().filter(function (item) { - return (Config.ShopBot.ScanIDs.includes(item.classid) || Config.ShopBot.ScanIDs.length === 0); - }); - - if (!items.length) return false; - - me.overhead(npc.itemcount + " items, " + items.length + " valid"); - - validItems += items.length; - overlayText.frequency.text = "Valid base items / cycle: " + ((validItems / totalCycles).toFixed(2).toString()); - - for (let i = 0; i < items.length; i += 1) { - if (Storage.Inventory.CanFit(items[i]) && Pickit.canPick(items[i]) && - me.gold >= items[i].getItemCost(sdk.items.cost.ToBuy) && - NTIP.CheckItem(items[i], this.pickEntries) - ) { - beep(); - D2Bot.printToConsole("Match found!", sdk.colors.D2Bot.DarkGold); - delay(1000); - - if (npc.startTrade(menuId)) { - Misc.logItem("Shopped", items[i]); - items[i].buy(); - bought = true; - } - - Config.ShopBot.QuitOnMatch && scriptBroadcast("quit"); - } - } - - if (bought) { - me.cancelUIFlags(); - Town.stash(); - } - - return true; - }; - - this.shopAtNPC = function (name) { - let wp, menuId = "Shop"; - - switch (name) { - case NPC.Charsi: - menuId = "Repair"; - // eslint-disable-next-line no-fallthrough - case NPC.Akara: - case NPC.Gheed: - wp = sdk.areas.RogueEncampment; - - break; - case NPC.Fara: - menuId = "Repair"; - // eslint-disable-next-line no-fallthrough - case NPC.Elzix: - case NPC.Drognan: - wp = sdk.areas.LutGholein; - - break; - case NPC.Hratli: - menuId = "Repair"; - // eslint-disable-next-line no-fallthrough - case NPC.Asheara: - case NPC.Ormus: - wp = sdk.areas.KurastDocktown; - - break; - case NPC.Halbu: - menuId = "Repair"; - // eslint-disable-next-line no-fallthrough - case NPC.Jamella: - wp = sdk.areas.PandemoniumFortress; - - break; - case NPC.Larzuk: - menuId = "Repair"; - // eslint-disable-next-line no-fallthrough - case NPC.Malah: - case NPC.Anya: - wp = sdk.areas.Harrogath; - - break; - default: - throw new Error("Invalid NPC"); - } - - if (!Pather.useWaypoint(wp)) return false; - - let npc = this.npcs[name] || Game.getNPC(name); - - if (!npc || npc.distance > 5) { - Town.move(name); - npc = Game.getNPC(name); - } - - if (!npc) return false; - - !this.npcs[name] && (this.npcs[name] = copyUnit(npc)); - Config.ShopBot.CycleDelay && delay(Config.ShopBot.CycleDelay); - this.openMenu(npc) && this.shopItems(npc, menuId); - - return true; - }; - - // START - for (let i = 0; i < Config.ShopBot.ScanIDs.length; i += 1) { - if (isNaN(Config.ShopBot.ScanIDs[i])) { - if (NTIPAliasClassID.hasOwnProperty(Config.ShopBot.ScanIDs[i].replace(/\s+/g, "").toLowerCase())) { - Config.ShopBot.ScanIDs[i] = NTIPAliasClassID[Config.ShopBot.ScanIDs[i].replace(/\s+/g, "").toLowerCase()]; - } else { - Misc.errorReport("ÿc1Invalid ShopBot entry:ÿc0 " + Config.ShopBot.ScanIDs[i]); - Config.ShopBot.ScanIDs.splice(i, 1); - i -= 1; - } - } - } - - typeof Config.ShopBot.ShopNPC === "string" && (Config.ShopBot.ShopNPC = [Config.ShopBot.ShopNPC]); - - for (let i = 0; i < Config.ShopBot.ShopNPC.length; i += 1) { - Config.ShopBot.ShopNPC[i] = Config.ShopBot.ShopNPC[i].toLowerCase(); - } - - if (Config.ShopBot.MinGold && me.gold < Config.ShopBot.MinGold) return true; - - this.buildPickList(); - print("Shopbot: Pickit entries: " + this.pickEntries.length); - Town.doChores(); - - tickCount = getTickCount(); - - while (!Config.ShopBot.Cycles || totalCycles < Config.ShopBot.Cycles) { - if (getTickCount() - tickCount >= 60 * 1000) { - overlayText.cycles.text = "Cycles in last minute: " + cycles.toString(); - overlayText.totalCycles.text = "Total cycles: " + totalCycles.toString(); - cycles = 0; - tickCount = getTickCount(); - } - - for (let i = 0; i < Config.ShopBot.ShopNPC.length; i += 1) { - this.shopAtNPC(Config.ShopBot.ShopNPC[i]); - } - - if (me.inTown) { - let area = getArea(); - let wp = Game.getPresetObject(me.area, [ - sdk.objects.A1Waypoint, sdk.objects.A2Waypoint, sdk.objects.A3Waypoint, sdk.objects.A4Waypoint, sdk.objects.A5Waypoint - ][me.act - 1]); - let wpX = wp.roomx * 5 + wp.x; - let wpY = wp.roomy * 5 + wp.y; - let redPortal = (getUnits(sdk.unittype.Object, sdk.objects.RedPortal).sort((a, b) => a.distance - b.distance)).first(); - let exit = area.exits[0]; - - for (let i = 1; i < area.exits.length; i++) { - if (getDistance(me, exit) > getDistance(me, area.exits[i])) { - exit = area.exits[i]; - } - } - - if ([sdk.areas.RogueEncampment, sdk.areas.Harrogath].includes(me.area) && !!redPortal && redPortal.distance < 20 - && Pather.usePortal(null, null, redPortal)) { - delay(3000); - Pather.usePortal(sdk.areas.townOf(me.area)); - - if (totalCycles === 0) { - delay(10000); - } - - delay(1500); - } else if (getDistance(me, exit) < (getDistance(me, wpX, wpY) + 6)) { - Pather.moveToExit(me.area + 1, true); - Pather.moveToExit(me.area - 1, true); - } else { - Pather.useWaypoint([sdk.areas.CatacombsLvl2, sdk.areas.A2SewersLvl2, sdk.areas.DuranceofHateLvl2, sdk.areas.RiverofFlame, sdk.areas.CrystalizedPassage][me.act - 1]); - } - } - - cycles += 1; - totalCycles += 1; - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Smith.js b/d2bs/kolbot/libs/bots/Smith.js deleted file mode 100644 index 4dc8c4687..000000000 --- a/d2bs/kolbot/libs/bots/Smith.js +++ /dev/null @@ -1,21 +0,0 @@ -/** -* @filename Smith.js -* @author kolton -* @desc kill the Smith -* -*/ - -function Smith() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.OuterCloister); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.Barracks, sdk.unittype.Object, sdk.quest.chest.MalusHolder)) { - throw new Error("Failed to move to the Smith"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.TheSmith)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Snapchip.js b/d2bs/kolbot/libs/bots/Snapchip.js deleted file mode 100644 index f72bea856..000000000 --- a/d2bs/kolbot/libs/bots/Snapchip.js +++ /dev/null @@ -1,21 +0,0 @@ -/** -* @filename Snapchip.js -* @author kolton -* @desc kill Snapchip and optionally clear Icy Cellar -* -*/ - -function Snapchip() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.AncientsWay); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.IcyCellar, true) || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.SmallSparklyChest)) { - throw new Error("Failed to move to Snapchip Shatter"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.SnapchipShatter)); - Config.Snapchip.ClearIcyCellar && Attack.clearLevel(Config.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Stormtree.js b/d2bs/kolbot/libs/bots/Stormtree.js deleted file mode 100644 index 2f191bf7d..000000000 --- a/d2bs/kolbot/libs/bots/Stormtree.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename Stormtree.js -* @author kolton -* @desc kill Stormtree -* -*/ - -function Stormtree() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.LowerKurast); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.FlayerJungle, true)) { - throw new Error("Failed to move to Stormtree"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.Stormtree)); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Summoner.js b/d2bs/kolbot/libs/bots/Summoner.js deleted file mode 100644 index 1e899b28b..000000000 --- a/d2bs/kolbot/libs/bots/Summoner.js +++ /dev/null @@ -1,42 +0,0 @@ -/** -* @filename Summoner.js -* @author kolton -* @desc kill the Summoner -* -*/ - -function Summoner () { - Town.doChores(); - Pather.useWaypoint(sdk.areas.ArcaneSanctuary); - Precast.doPrecast(true); - - if (Config.Summoner.FireEye) { - try { - if (!Pather.usePortal(null)) throw new Error("Failed to move to Fire Eye"); - Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.FireEye)); - } catch (e) { - console.error(e); - } - } - - if (me.inArea(sdk.areas.PalaceCellarLvl3) && !Pather.usePortal(null)) throw new Error("Failed to move to Summoner"); - if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal, -3, -3)) throw new Error("Failed to move to Summoner"); - - Attack.clear(15, 0, sdk.monsters.TheSummoner); - - if (Loader.scriptName(1) === "Duriel") { - let journal = Game.getObject(sdk.quest.chest.Journal); - if (!journal) return true; - - Pather.moveToUnit(journal); - journal.interact(); - delay(500); - me.cancel(); - - if (!Pather.usePortal(sdk.areas.CanyonofMagic)) return true; - - Loader.skipTown.push("Duriel"); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Synch.js b/d2bs/kolbot/libs/bots/Synch.js deleted file mode 100644 index b1fb25a8f..000000000 --- a/d2bs/kolbot/libs/bots/Synch.js +++ /dev/null @@ -1,58 +0,0 @@ -/** -* @filename Synch.js -* @author kolton -* @desc sync script? It's unused but works with Synch2.js -* -*/ - -let Synched = false; -let uRdyMsg = "I'm rdy, u?"; -let rdyMsg = "rdy"; - -function messageHandler(nick, msg) { - if (nick !== me.name) { - if (msg === uRdyMsg) { - say(rdyMsg); - Synched = true; - } else if (msg === rdyMsg) { - Synched = true; - } else if (msg === "Yo, I'm rdy, u?") { - say("No"); - quit(); - } - } -} - -function Synch() { - let i, party, j; - - addEventListener("chatmsg", messageHandler); - - delay(1000); - say(uRdyMsg); - - for (i = 0; i < 720 && !Synched; i += 1) { - delay(1000); - - for (j = 0; j < Config.Synch.WaitFor.length; j += 1) { - party = getParty(Config.Synch.WaitFor[j]); - if (!party) { - D2Bot.printToConsole("WaitFor not in game: " + - Config.Synch.WaitFor[j] + " so quitting."); - - removeEventListener("chatmsg", messageHandler); - quit(); - return false; - } - } - } - - if (!Synched) { - D2Bot.printToConsole("Failed to sync."); - quit(); - } - - removeEventListener("chatmsg", messageHandler); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Synch2.js b/d2bs/kolbot/libs/bots/Synch2.js deleted file mode 100644 index d2a34db01..000000000 --- a/d2bs/kolbot/libs/bots/Synch2.js +++ /dev/null @@ -1,62 +0,0 @@ -/** -* @filename Synch2.js -* @author kolton -* @desc sync script? It's unused but works with Synch.js -* -*/ - -let Synched2 = false; -let uRdyMsg2 = "Yo, I'm rdy, u?"; -let rdyMsg2 = "Let's go"; - -function messageHandler2(nick, msg) { - if (nick !== me.name) { - if (msg === uRdyMsg2) { - say(rdyMsg2); - Synched2 = true; - } else if (msg === rdyMsg2) { - Synched2 = true; - } else if (msg === "I'm rdy, u?") { - say("No"); - quit(); - } - } -} - -function Synch2() { - let i, party, j; - - addEventListener("chatmsg", messageHandler2); - - delay(1000); - say(uRdyMsg2); - - delay(1000); - - for (i = 0; i < 720 && !Synched2; i += 1) { - for (j = 0; j < Config.Synch.WaitFor.length; j += 1) { - party = getParty(Config.Synch.WaitFor[j]); - if (!party) { - D2Bot.printToConsole("WaitFor not in game: " + - Config.Synch.WaitFor[j] + " so quitting."); - - removeEventListener("chatmsg", messageHandler2); - quit(); - return false; - } - } - - delay(1000); - } - - if (!Synched) { - D2Bot.printToConsole("Failed to sync."); - quit(); - } - - delay(1000); - - removeEventListener("chatmsg", messageHandler2); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Test.js b/d2bs/kolbot/libs/bots/Test.js deleted file mode 100644 index 864048f3f..000000000 --- a/d2bs/kolbot/libs/bots/Test.js +++ /dev/null @@ -1,38 +0,0 @@ -/** -* @filename Test.js -* @author kolton -* @desc Unsure? Just testing addEventListener it looks like -* -*/ - -function Test() { - print("ÿc8TESTING"); - - let c; - - function KeyDown(key) { - key === sdk.keys.Insert && (c = true); - } - - addEventListener("keydown", KeyDown); - - while (true) { - if (c) { - try { - doTest(); - } catch (qq) { - print("failed"); - print(qq + " " + qq.fileName + " " + qq.lineNumber); - } - - c = false; - } - - delay(10); - } -} - -function doTest() { - print("test"); - print("done"); -} diff --git a/d2bs/kolbot/libs/bots/ThreshSocket.js b/d2bs/kolbot/libs/bots/ThreshSocket.js deleted file mode 100644 index 0b90cc7b7..000000000 --- a/d2bs/kolbot/libs/bots/ThreshSocket.js +++ /dev/null @@ -1,21 +0,0 @@ -/** -* @filename ThreshSocket.js -* @author kolton -* @desc kill Thresh Socket -* -*/ - -function ThreshSocket() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.ArreatPlateau); - Precast.doPrecast(true); - - // ArreatPlateau returns invalid size with getBaseStat('leveldefs', 112, ['SizeX', 'SizeX(N)', 'SizeX(H)'][me.diff]); - // Could this be causing crashes here? Would it be better to go from crystal to Arreat instead? - if (!Pather.moveToExit(sdk.areas.CrystalizedPassage, false)) throw new Error("Failed to move to Thresh Socket"); - - Attack.kill(getLocaleString(sdk.locale.monsters.ThreshSocket)); - Pickit.pickItems(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Tombs.js b/d2bs/kolbot/libs/bots/Tombs.js deleted file mode 100644 index e0b2e017c..000000000 --- a/d2bs/kolbot/libs/bots/Tombs.js +++ /dev/null @@ -1,31 +0,0 @@ -/** -* @filename Tombs.js -* @author kolton, theBGuy -* @desc clear Tal Rasha's Tombs, optionally kill duriel as well -* -*/ - -function Tombs() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.CanyonofMagic); - Precast.doPrecast(true); - - for (let i = sdk.areas.TalRashasTomb1; i <= sdk.areas.TalRashasTomb7; i += 1) { - try { - if (!Pather.journeyTo(i, true)) throw new Error("Failed to move to tomb"); - - Attack.clearLevel(Config.ClearType); - - if (Config.Tombs.KillDuriel && me.area === getRoom().correcttomb) { - Pather.journeyTo(sdk.areas.DurielsLair) && Attack.kill(sdk.monsters.Duriel); - Pather.journeyTo(sdk.areas.CanyonofMagic); - } - - if (!Pather.moveToExit(sdk.areas.CanyonofMagic, true)) throw new Error("Failed to move to Canyon"); - } catch (e) { - console.error(e); - } - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Travincal.js b/d2bs/kolbot/libs/bots/Travincal.js deleted file mode 100644 index f101d8729..000000000 --- a/d2bs/kolbot/libs/bots/Travincal.js +++ /dev/null @@ -1,75 +0,0 @@ -/** -* @filename Travincal.js -* @author kolton -* @desc kill Council members in Travincal -* -*/ - -function Travincal() { - this.buildList = function (checkColl) { - let monsterList = []; - let monster = Game.getMonster(); - - if (monster) { - do { - if ([sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3].includes(monster.classid) - && monster.attackable && (!checkColl || !checkCollision(me, monster, sdk.collision.BlockWall))) { - monsterList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - return monsterList; - }; - - Town.doChores(); - Pather.useWaypoint(sdk.areas.Travincal); - Precast.doPrecast(true); - - let orgX = me.x; - let orgY = me.y; - - if (Config.Travincal.PortalLeech) { - Pather.moveTo(orgX + 85, orgY - 139); - Attack.securePosition(orgX + 70, orgY - 139, 25, 2000); - Attack.securePosition(orgX + 100, orgY - 139, 25, 2000); - Attack.securePosition(orgX + 85, orgY - 139, 25, 5000); - Pather.moveTo(orgX + 85, orgY - 139); - Pather.makePortal(); - delay(1000); - Precast.doPrecast(true); - } - - if (Skill.canUse(sdk.skills.LeapAttack) && !Pather.canTeleport()) { - let coords = [60, -53, 64, -72, 78, -72, 74, -88]; - - for (let i = 0; i < coords.length; i += 2) { - if (i % 4 === 0) { - Pather.moveTo(orgX + coords[i], orgY + coords[i + 1]); - } else { - Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, orgX + coords[i], orgY + coords[i + 1]); - Attack.clearList(this.buildList(1)); - } - } - - Attack.clearList(this.buildList(0)); - } else { - Pather.moveTo(orgX + 101, orgY - 56); - - // Stack Merc - if (me.barbarian && !Pather.canTeleport() && me.expansion) { - Pather.moveToExit([sdk.areas.DuranceofHateLvl1, sdk.areas.Travincal], true); - } - - if (Config.MFLeader) { - Pather.makePortal(); - say("council " + me.area); - } - - Attack.clearList(this.buildList(0)); - } - - Config.MFLeader && Config.PublicMode && say("travdone"); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/TravincalLeech.js b/d2bs/kolbot/libs/bots/TravincalLeech.js deleted file mode 100644 index f790801f4..000000000 --- a/d2bs/kolbot/libs/bots/TravincalLeech.js +++ /dev/null @@ -1,82 +0,0 @@ -/** -* @filename TravincalLeech.js -* @author ToS/XxXGoD/YGM/azero, theBGuy -* @desc Travincal Leech -* -*/ - -/** -* @todo: -* - add help option -* - keep within 40 of leader for just leeching -* - long range help for helper? -* - add dodge if position is too hot (hydras can kill a low level quickly) -*/ - -function TravincalLeech () { - let leader; - let done = false; - - const chatEvent = function (nick, msg) { - if (nick === leader && msg.toLowerCase() === "travdone") { - done = true; - } - }; - - Town.goToTown(3); - Town.doChores(); - Town.move("portalspot"); - - if (Config.Leader) { - leader = Config.Leader; - if (!Misc.poll(() => Misc.inMyParty(leader), Time.minutes(2), 1000)) throw new Error("TristramLeech: Leader not partied"); - } - - !leader && (leader = Misc.autoLeaderDetect({ - destination: sdk.areas.Travincal, - quitIf: (area) => Common.Leecher.nextScriptAreas.includes(area), - timeout: Time.minutes(5) - })); - - if (leader) { - try { - const Worker = require("../modules/Worker"); - addEventListener("chatmsg", chatEvent); - - Common.Leecher.killLeaderTracker = false; - Common.Leecher.leader = leader; - Common.Leecher.currentScript = Loader.scriptName(); - Worker.runInBackground.leaderTracker = Common.Leecher.leaderTracker; - - while (Misc.inMyParty(Common.Leecher.leader)) { - if (done) return true; - - if (me.inTown && Pather.getPortal(sdk.areas.Travincal, Common.Leecher.leader)) { - Pather.usePortal(sdk.areas.Travincal, Common.Leecher.leader); - Town.getCorpse(); - } - - if (me.mode === sdk.player.mode.Dead) { - me.revive(); - - while (!me.inTown) { - delay(100); - } - - Town.move("portalspot"); - } - - delay(100); - } - } catch (e) { - console.error(e); - } finally { - removeEventListener("chatmsg", chatEvent); - Common.Leecher.killLeaderTracker = true; - } - } else { - console.warn("No leader found"); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Treehead.js b/d2bs/kolbot/libs/bots/Treehead.js deleted file mode 100644 index d98bdcf2d..000000000 --- a/d2bs/kolbot/libs/bots/Treehead.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename Treehead.js -* @author kolton -* @desc kill Treehead WoodFist -* -*/ - -function Treehead() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.DarkWood); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) { - throw new Error("Failed to move to Treehead"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.TreeheadWoodFist)); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Tristram.js b/d2bs/kolbot/libs/bots/Tristram.js deleted file mode 100644 index 1bc3c9382..000000000 --- a/d2bs/kolbot/libs/bots/Tristram.js +++ /dev/null @@ -1,59 +0,0 @@ -/** -* @filename Tristram.js -* @author kolton, cuss, theBGuy -* @desc clear Tristram -* -*/ - -function Tristram () { - Pather._teleport = Pather.teleport; - - // complete quest if its not complete - if (!me.getQuest(sdk.quest.id.TheSearchForCain, 4) && !me.getQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed)) { - Common.Questing.cain(); - } - - MainLoop: - while (true) { - switch (true) { - case me.inTown: - Town.doChores(); - Pather.useWaypoint(sdk.areas.StonyField); - Precast.doPrecast(true); - - break; - case me.inArea(sdk.areas.StonyField): - if (!Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 0, 0, false, true)) { - throw new Error("Failed to move to Rakanishu"); - } - - Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Rakanishu)); - - while (!Pather.usePortal(sdk.areas.Tristram)) { - Attack.securePosition(me.x, me.y, 10, 1000); - } - - break; - case me.inArea(sdk.areas.Tristram): - let redPortal = Game.getObject(sdk.objects.RedPortal); - !!redPortal && Pather.moveTo(redPortal.x, redPortal.y + 6); - - if (Config.Tristram.PortalLeech) { - Pather.makePortal(); - delay(1000); - Pather.teleport = !Config.Tristram.WalkClear && Pather._teleport; - } - - Config.Tristram.PortalLeech ? Attack.clearLevel(0) : Attack.clearLevel(Config.ClearType); - - break MainLoop; - default: - break MainLoop; - } - } - - Config.MFLeader && Config.PublicMode && say("tristdone"); - Pather.teleport = Pather._teleport; - - return true; -} diff --git a/d2bs/kolbot/libs/bots/TristramLeech.js b/d2bs/kolbot/libs/bots/TristramLeech.js deleted file mode 100644 index 27075b79a..000000000 --- a/d2bs/kolbot/libs/bots/TristramLeech.js +++ /dev/null @@ -1,115 +0,0 @@ -/** -* @filename TristramLeech.js -* @author ToS/XxXGoD/YGM, theBGuy -* @desc Tristram Leech (Helper) -* -*/ - -function TristramLeech () { - let done = false; - let whereisleader, leader; - - const chatEvent = function (nick, msg) { - if (nick === leader && msg.toLowerCase() === "tristdone") { - done = true; - } - }; - - Town.doChores(); - Town.goToTown(1); - Town.move("portalspot"); - - if (Config.Leader) { - leader = (Config.Leader || Config.TristramLeech.Leader); - if (!Misc.poll(() => Misc.inMyParty(leader), Time.seconds(30), 1000)) throw new Error("TristramLeech: Leader not partied"); - } - - !leader && (leader = Misc.autoLeaderDetect({ - destination: sdk.areas.Tristram, - quitIf: (area) => Common.Leecher.nextScriptAreas.includes(area), - timeout: Time.minutes(5) - })); - - if (leader) { - try { - const Worker = require("../modules/Worker"); - addEventListener("chatmsg", chatEvent); - - Common.Leecher.leader = leader; - Common.Leecher.currentScript = Loader.scriptName(); - Common.Leecher.killLeaderTracker = false; - Worker.runInBackground.leaderTracker = Common.Leecher.leaderTracker; - - if (!Misc.poll(() => { - if (done) return true; - if (Pather.getPortal(sdk.areas.Tristram, Config.Leader || null) && Pather.usePortal(sdk.areas.Tristram, Config.Leader || null)) { - return true; - } - - return false; - }, Time.minutes(5), 1000)) throw new Error("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); - - Precast.doPrecast(true); - delay(3000); - - whereisleader = Misc.poll(() => { - let lead = getParty(leader); - - if (lead.area === sdk.areas.Tristram) { - return lead; - } - - return false; - }, Time.minutes(3), 1000); - - while (true) { - if (done) return true; - - whereisleader = getParty(leader); - let leaderUnit = Misc.getPlayerUnit(leader); - - if (whereisleader.area !== sdk.areas.Tristram && !Misc.poll(() => { - let lead = getParty(leader); - - if (lead.area === sdk.areas.Tristram) { - return true; - } - - return false; - }, Time.minutes(3), 1000)) { - console.log("Leader wasn't in tristram for longer than 3 minutes, End script"); - - break; - } - - if (whereisleader.area === me.area) { - try { - if (copyUnit(leaderUnit).x) { - Config.TristramLeech.Helper && leaderUnit.distance > 4 && Pather.moveToUnit(leaderUnit) && Attack.clear(10); - !Config.TristramLeech.Helper && leaderUnit.distance > 20 && Pather.moveNearUnit(leaderUnit, 15); - } else { - Config.TristramLeech.Helper && Pather.moveTo(copyUnit(leaderUnit).x, copyUnit(leaderUnit).y) && Attack.clear(10); - !Config.TristramLeech.Helper && Pather.moveNear(copyUnit(leaderUnit).x, copyUnit(leaderUnit).y, 15); - } - } catch (err) { - if (whereisleader.area === me.area) { - Config.TristramLeech.Helper && Pather.moveTo(whereisleader.x, whereisleader.y) && Attack.clear(10); - !Config.TristramLeech.Helper && Pather.moveNear(whereisleader.x, whereisleader.y, 15); - } - } - } - - delay(100); - } - } catch (e) { - console.error(e); - } finally { - removeEventListener("chatmsg", chatEvent); - Common.Leecher.killLeaderTracker = true; - } - } - - if (!me.inTown && Town.goToTown()) throw new Error("Failed to get back to town"); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/UndergroundPassage.js b/d2bs/kolbot/libs/bots/UndergroundPassage.js deleted file mode 100644 index a8bec7035..000000000 --- a/d2bs/kolbot/libs/bots/UndergroundPassage.js +++ /dev/null @@ -1,20 +0,0 @@ -/** -* @filename UndergroundPassage.js -* @author loshmi -* @desc Move and clear Underground passage level 2 -* -*/ - -function UndergroundPassage() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.StonyField); - Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.UndergroundPassageLvl1, sdk.areas.UndergroundPassageLvl2], true)) { - throw new Error("Failed to move to Underground passage level 2"); - } - - Attack.clearLevel(); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/UserAddon.js b/d2bs/kolbot/libs/bots/UserAddon.js deleted file mode 100644 index 43a835fac..000000000 --- a/d2bs/kolbot/libs/bots/UserAddon.js +++ /dev/null @@ -1,97 +0,0 @@ -/** -* @filename UserAddon.js -* @author kolton -* @desc Allows you to see more information about items, NPCs and players by placing the cursor over them. -* Shows item level, items in sockets, classid, code and magic item prefix/suffix numbers. -* Shows monster's classid, HP percent and resistances. -* Shows other players' gear. -* -*/ - -include("UnitInfo.js"); - -function UserAddon () { - let i, title, dummy, command = ""; - const className = sdk.player.class.nameOf(me.classid); - const flags = [ - sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow, sdk.uiflags.ChatBox, - sdk.uiflags.Quest, sdk.uiflags.Msgs, sdk.uiflags.Stash, sdk.uiflags.Shop, sdk.uiflags.EscMenu, sdk.uiflags.Cube - ]; - - const keyEvent = function (key) { - switch (key) { - case sdk.keys.Spacebar: - FileTools.copy("libs/config/" + className + ".js", "libs/config/" + className + "." + me.name + ".js"); - D2Bot.printToConsole("libs/config/" + className + "." + me.name + ".js has been created."); - D2Bot.printToConsole("Please configure your bot and start it again."); - D2Bot.stop(); - - break; - } - }; - - const onChatInput = (speaker, msg) => { - if (msg.length && msg[0] === ".") { - command = str.split(" ")[0].split(".")[1]; - - return true; - } - - return false; - }; - - try { - // Make sure the item event is loaded - why though? - !Config.FastPick && addEventListener("itemaction", Pickit.itemEvent); - addEventListener("chatinputblocker", onChatInput); - - if (!FileTools.exists("libs/config/" + className + "." + me.name + ".js")) { - showConsole(); - print("ÿc4UserAddonÿc0: Press HOME and then press SPACE if you want to create character config."); - addEventListener("keyup", keyEvent); - } - - while (true) { - for (i = 0; i < flags.length; i += 1) { - if (getUIFlag(flags[i])) { - if (title) { - title.remove(); - dummy.remove(); - - title = false; - dummy = false; - } - - break; - } - } - - if (i === flags.length && !title) { - title = new Text(":: kolbot user addon ::", 400, 525, 4, 0, 2); - dummy = new Text("`", 1, 1); // Prevents crash - } - - !UnitInfo.cleared && !Game.getSelectedUnit() && UnitInfo.remove(); - - if (command && command.toLowerCase() === "done") { - print("ÿc4UserAddon ÿc1ended"); - - return true; - } else { - print(command); - command = ""; - } - - Pickit.fastPick(); - - let unit = Game.getSelectedUnit(); - !!unit && UnitInfo.createInfo(unit); - - delay(20); - } - } finally { - removeEventListener("keyup", keyEvent); - removeEventListener("itemaction", Pickit.itemEvent); - removeEventListener("chatinputblocker", onChatInput); - } -} diff --git a/d2bs/kolbot/libs/bots/WPGetter.js b/d2bs/kolbot/libs/bots/WPGetter.js deleted file mode 100644 index 1bfc0e89e..000000000 --- a/d2bs/kolbot/libs/bots/WPGetter.js +++ /dev/null @@ -1,27 +0,0 @@ -/** -* @filename WPGetter.js -* @author kolton -* @desc Get wps we don't have -* -*/ - -function WPGetter() { - Town.doChores(); - Town.goToTown(); - Pather.getWP(me.area); - - let highestAct = me.highestAct; - let lastWP = sdk.areas.townOfAct((highestAct === 5 ? highestAct : highestAct + 1)); - lastWP === sdk.areas.Harrogath && (lastWP = me.baal ? sdk.areas.WorldstoneLvl2 : sdk.areas.AncientsWay); - let wpsToGet = Pather.nonTownWpAreas.filter((wp) => wp < lastWP && wp !== sdk.areas.HallsofPain && !getWaypoint(Pather.wpAreas.indexOf(wp))); - - console.debug(wpsToGet); - - for (let i = 0; i < wpsToGet.length; i += 1) { - Pather.getWP(wpsToGet[i]); - delay(500); - Town.checkScrolls(sdk.items.TomeofTownPortal) < 10 && Town.doChores(); - } - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Wakka.js b/d2bs/kolbot/libs/bots/Wakka.js deleted file mode 100644 index 6200e952f..000000000 --- a/d2bs/kolbot/libs/bots/Wakka.js +++ /dev/null @@ -1,420 +0,0 @@ -/** -* @filename Wakka.js -* @author kolton, theBGuy -* @desc walking Chaos Sanctuary leecher -* -*/ - -function Wakka () { - const timeout = Config.Wakka.Wait; - const minDist = 50; - const maxDist = 80; - const internals = { - safeTP: false, - coordsInit: false, - vizCoords: [], - seisCoords: [], - infCoords: [], - vizClear: false, - seisClear: false, - infClear: false, - }; - - let portal, tick; - let leaderUnit = null; - let leaderPartyUnit = null; - let leader = ""; - - this.checkMonsters = function (range = 15, dodge = false) { - let monList = []; - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.y < 5565 && monster.attackable && monster.distance <= range) { - if (!dodge) return true; - monList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - if (!monList.length) return false; - - monList.sort(Sort.units); - - if (monList[0].distance < 25 && !checkCollision(me, monList[0], sdk.collision.Ranged)) { - Attack.deploy(monList[0], 25, 5, 15); - } - - return true; - }; - - this.getCoords = function () { - if (!internals.coordsInit) { - Common.Diablo.initLayout(); - internals.vizCoords = Common.Diablo.vizLayout === 1 ? [7707, 5274] : [7708, 5298]; - internals.seisCoords = Common.Diablo.seisLayout === 1 ? [7812, 5223] : [7809, 5193]; - internals.infCoords = Common.Diablo.infLayout === 1 ? [7868, 5294] : [7882, 5306]; - internals.coordsInit = true; - } - }; - - this.checkBoss = function (name) { - let glow = Game.getObject(sdk.objects.SealGlow); - - if (glow) { - for (let i = 0; i < 10; i += 1) { - let boss = Game.getMonster(name); - - if (boss) { - while (!boss.dead) { - delay(500); - } - - return true; - } - - delay(500); - } - - return true; - } - - return false; - }; - - this.getCorpse = function () { - me.mode === sdk.player.mode.Dead && me.revive(); - - let rval = false; - let corpse = Game.getPlayer(me.name, sdk.player.mode.Dead); - - if (corpse) { - do { - if (corpse.distance <= 15) { - Pather.moveToUnit(corpse); - corpse.interact(); - delay(500); - - rval = true; - } - } while (corpse.getNext()); - } - - return rval; - }; - - this.followPath = function (dest) { - let path = getPath(me.area, me.x, me.y, dest[0], dest[1], 0, 10); - if (!path) throw new Error("Failed go get path"); - - while (path.length > 0) { - if (me.mode === sdk.player.mode.Dead || me.inTown) return false; - (!leaderUnit || !copyUnit(leaderUnit).x) && (leaderUnit = Game.getPlayer(leader)); - - if (leaderUnit) { - // monsters nearby - don't move - if (this.checkMonsters(45, true) && leaderUnit.distance <= maxDist) { - path = getPath(me.area, me.x, me.y, dest[0], dest[1], 0, 15); - delay(200); - - continue; - } - - // leader within minDist range - don't move - if (leaderUnit.distance <= minDist) { - delay(200); - - continue; - } - - // make sure distance to next node isn't too hot - if ([path[0].x, path[0].y].mobCount({range: 15}) !== 0) { - console.log("Mobs at next node"); - // mobs, stay where we are - delay(200); - - continue; - } - } else { - // leaderUnit out of getUnit range but leader is still within reasonable distance - check party unit's coords! - leaderPartyUnit = getParty(leader); - - if (leaderPartyUnit) { - // leader went to town - don't move - if (leaderPartyUnit.area !== me.area) { - delay(200); - - continue; - } - - // if there's monsters between the leecher and leader, wait until monsters are dead or leader is out of maxDist range - if (this.checkMonsters(45, true) && getDistance(me, leaderPartyUnit.x, leaderPartyUnit.y) <= maxDist) { - path = getPath(me.area, me.x, me.y, dest[0], dest[1], 0, 15); - - delay(200); - - continue; - } - } - } - - Pather.moveTo(path[0].x, path[0].y) && path.shift(); - // no mobs around us, so it's safe to pick - !me.checkForMobs({range: 10, coll: (sdk.collision.BlockWall | sdk.collision.Objects | sdk.collision.ClosedDoor)}) && Pickit.pickItems(5); - this.getCorpse(); - } - - return true; - }; - - this.getLeaderUnitArea = function () { - (!leaderUnit || !copyUnit(leaderUnit).x) && (leaderUnit = Game.getPlayer(leader)); - return !!leaderUnit ? leaderUnit.area : getParty(leader).area; - }; - - this.log = function (msg = "") { - me.overhead(msg); - console.log(msg); - }; - - // START - Town.goToTown(4); - Town.move("portalspot"); - - if (Config.Leader) { - leader = Config.Leader; - if (!Misc.poll(() => Misc.inMyParty(leader), 30e3, 1000)) throw new Error("Wakka: Leader not partied"); - } - - !leader && (leader = Misc.autoLeaderDetect({ - destination: sdk.areas.ChaosSanctuary, - quitIf: (area) => [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(area), - timeout: timeout * 60e3 - })); - Town.doChores(); - - if (leader) { - addEventListener("gamepacket", Common.Diablo.diabloLightsEvent); - const Worker = require("../modules/Worker"); - - try { - if (Config.Wakka.SkipIfBaal) { - let leadTick = getTickCount(); - let killLeaderTracker = false; - - Worker.runInBackground.leaderTracker = function () { - if (Common.Diablo.done || killLeaderTracker) return false; - // check every 3 seconds - if (getTickCount() - leadTick < 3000) return true; - leadTick = getTickCount(); - - // check again in another 3 seconds if game wasn't ready - if (!me.gameReady) return true; - if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); - - // Player is in Throne of Destruction or Worldstone Chamber - if ([sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(this.getLeaderUnitArea())) { - if (Loader.scriptName() === "Wakka") { - killLeaderTracker = true; - throw new Error("Party leader is running baal"); - } else { - // kill process - return false; - } - } - - return true; - }; - } - - let levelTick = getTickCount(); - let killLevelTracker = false; - - Worker.runInBackground.levelTracker = function () { - if (Common.Diablo.done || killLevelTracker) return false; - // check every 3 seconds - if (getTickCount() - levelTick < 3000) return true; - levelTick = getTickCount(); - - // check again in another 3 seconds if game wasn't ready - if (!me.gameReady) return true; - - if (me.charlvl >= Config.Wakka.StopAtLevel) { - Config.Wakka.StopProfile && D2Bot.stop(); - - if (Loader.scriptName() === "Wakka") { - killLevelTracker = true; - throw new Error("Reached wanted level"); - } else { - // kill process - return false; - } - } - - return true; - }; - - while (Misc.inMyParty(leader)) { - try { - switch (me.area) { - case sdk.areas.PandemoniumFortress: - portal = Pather.getPortal(sdk.areas.ChaosSanctuary, null); - - if (portal) { - !internals.safeTP && delay(5000); - Pather.usePortal(sdk.areas.ChaosSanctuary, null); - Precast.doPrecast(true); - } - - break; - case sdk.areas.ChaosSanctuary: - try { - let diaTick = getTickCount(); - - Worker.runInBackground.diaSpawned = function () { - if (Common.Diablo.done || (internals.vizClear && internals.seisClear && internals.infClear)) return false; - // check every 1/4 second - if (getTickCount() - diaTick < 250) return true; - diaTick = getTickCount(); - - if (Common.Diablo.diabloSpawned) { - internals.vizClear = true; - internals.seisClear = true; - internals.infClear = true; - throw new Error("Diablo spawned"); - } - - return true; - }; - - if (!internals.safeTP) { - if (this.checkMonsters(25, false)) { - this.log("hot tp"); - Pather.usePortal(sdk.areas.PandemoniumFortress, null); - this.getCorpse(); - - break; - } else { - this.getCoords(); - internals.safeTP = true; - } - } - - if (!internals.vizClear) { - if (!this.followPath(internals.vizCoords)) { - console.debug("Failed to move to viz"); - break; - } - - if (this.checkBoss(getLocaleString(sdk.locale.monsters.GrandVizierofChaos))) { - this.log("vizier dead"); - internals.vizClear = true; - Precast.doPrecast(true); - tick = getTickCount(); - - while (getTickCount() - tick >= 5000) { - delay(100); - } - } - - break; - } - - if (internals.vizClear && !internals.seisClear) { - if (!this.followPath(internals.seisCoords)) { - console.debug("Failed to move to seis"); - break; - } - - if (this.checkBoss(getLocaleString(sdk.locale.monsters.LordDeSeis))) { - this.log("seis dead"); - internals.seisClear = true; - Precast.doPrecast(true); - tick = getTickCount(); - - while (getTickCount() - tick >= 7000) { - delay(100); - } - } - - break; - } - - if (internals.vizClear && internals.seisClear && !internals.infClear) { - if (!this.followPath(internals.infCoords)) { - console.debug("Failed to move to infector"); - break; - } - - if (this.checkBoss(getLocaleString(sdk.locale.monsters.InfectorofSouls))) { - this.log("infector dead"); - internals.infClear = true; - Precast.doPrecast(true); - tick = getTickCount(); - - while (getTickCount() - tick >= 2000) { - delay(100); - } - } - - break; - } - - Pather.moveTo(7767, 5263); - Misc.poll(() => { - if (Common.Diablo.diabloSpawned) return true; - if (Game.getMonster(sdk.monsters.Diablo)) return true; - return false; - }, Time.minutes(2), 500); - } catch (e) { - console.log((e.message ? e.message : e)); - } - - if (internals.vizClear && internals.seisClear && internals.infClear) { - Pather.moveTo(7767, 5263); - - let diablo = Misc.poll(() => Game.getMonster(sdk.monsters.Diablo), Time.minutes(3), 500); - - if (diablo) { - while (!diablo.dead) { - delay(100); - } - this.log("Diablo is dead"); - - if (!Town.canTpToTown() || !Town.goToTown()) { - Pather.usePortal(sdk.areas.PandemoniumFortress); - } - - return true; - } else { - this.log("Couldn't find diablo"); - } - } - - break; - } - - me.dead && me.revive(); - - delay(200); - } catch (e) { - console.error(e); - - return true; - } - } - } catch (e) { - // - } finally { - Common.Diablo.done; - removeEventListener("gamepacket", Common.Diablo.diabloLightsEvent); - } - } else { - throw new Error("No leader found"); - } - - this.log("Wakka complete"); - - return true; -} diff --git a/d2bs/kolbot/libs/bots/Worldstone.js b/d2bs/kolbot/libs/bots/Worldstone.js deleted file mode 100644 index 1d9fe0921..000000000 --- a/d2bs/kolbot/libs/bots/Worldstone.js +++ /dev/null @@ -1,39 +0,0 @@ -/** -* @filename Worldstone.js -* @author kolton -* @desc Clear Worldstone levels -* -*/ - -function Worldstone() { - Town.doChores(); - Pather.useWaypoint(sdk.areas.WorldstoneLvl2); - Precast.doPrecast(true); - /** - * Calc distances so we know whether to tp to town or not after clearing WSK1 - * - WP -> WSK3 - * - WSK1 -> WSK3 - * @todo Take into account walking vs tele and adjust distance check accordingly - */ - - /** @type {Exit[]} */ - let exits = getArea().exits; - let WS1 = exits.find(t => t.target === sdk.areas.WorldstoneLvl1); - let WS3 = exits.find(t => t.target === sdk.areas.WorldstoneLvl3); - let wpToWS3 = WS3.distance; - let ws1ToWS3 = getDistance(WS1, WS3); - - Attack.clearLevel(Config.ClearType); - Pather.moveToExit(sdk.areas.WorldstoneLvl1, true) && Attack.clearLevel(Config.ClearType); - if (wpToWS3 < ws1ToWS3 + Pather.getDistanceToExit(me.area, sdk.areas.WorldstoneLvl2)) { - console.log("Going to town to start from WSK2 waypoint."); - Town.goToTown(); - Pather.useWaypoint(sdk.areas.WorldstoneLvl2); - } else { - Pather.moveToExit(sdk.areas.WorldstoneLvl2, true); - } - - Pather.moveToExit(sdk.areas.WorldstoneLvl3, true) && Attack.clearLevel(Config.ClearType); - - return true; -} diff --git a/d2bs/kolbot/libs/common/Attack.js b/d2bs/kolbot/libs/common/Attack.js deleted file mode 100644 index 453e323ce..000000000 --- a/d2bs/kolbot/libs/common/Attack.js +++ /dev/null @@ -1,1604 +0,0 @@ -/** -* @filename Attack.js -* @author kolton, theBGuy -* @desc handle player attacks -* -*/ - -const Attack = { - infinity: false, - auradin: false, - monsterObjects: [ - sdk.monsters.Turret1, sdk.monsters.Turret2, sdk.monsters.Turret3, sdk.monsters.MummyGenerator, - sdk.monsters.GargoyleTrap, sdk.monsters.LightningSpire, sdk.monsters.FireTower, - sdk.monsters.BarricadeDoor1, sdk.monsters.BarricadeDoor2, sdk.monsters.BarricadeWall1, sdk.monsters.BarricadeWall2, - sdk.monsters.CatapultS, sdk.monsters.CatapultE, sdk.monsters.CatapultSiege, sdk.monsters.CatapultW, - sdk.monsters.BarricadeTower, sdk.monsters.PrisonDoor, sdk.monsters.DiablosBoneCage, sdk.monsters.Hut, - ], - Result: { - FAILED: 0, - SUCCESS: 1, - CANTATTACK: 2, // need to fix the ambiguity between this result and Failed - NEEDMANA: 3 - }, - - // Initialize attacks - init: function () { - if (Config.Wereform) { - include("common/Attacks/wereform.js"); - } else if (Config.CustomClassAttack && FileTools.exists("libs/common/Attacks/" + Config.CustomClassAttack + ".js")) { - console.log("Loading custom attack file"); - include("common/Attacks/" + Config.CustomClassAttack + ".js"); - } else { - include("common/Attacks/" + sdk.player.class.nameOf(me.classid) + ".js"); - } - - if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { - showConsole(); - console.warn("ÿc1Bad attack config. Don't expect your bot to attack."); - } - - this.getPrimarySlot(); - Skill.init(); - - if (me.expansion) { - Precast.checkCTA(); - this.checkInfinity(); - this.checkAuradin(); - } - }, - - // check if slot has items - checkSlot: function (slot = me.weaponswitch) { - let item = me.getItem(-1, sdk.items.mode.Equipped); - - if (item) { - do { - if (me.weaponswitch !== slot) { - if (item.bodylocation === sdk.body.RightArmSecondary || item.bodylocation === sdk.body.LeftArmSecondary) { - return true; - } - } else { - if (item.isOnMain) { - return true; - } - } - } while (item.getNext()); - } - - return false; - }, - - getPrimarySlot: function () { - // determine primary slot if not set - if (Config.PrimarySlot === -1) { - if (me.classic) { - Config.PrimarySlot = sdk.player.slot.Main; - } else { - // Always start on main-hand - me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main); - // have cta - if ((Precast.haveCTA > -1) || Precast.checkCTA()) { - // have item on non-cta slot - set non-cta slot as primary - if (this.checkSlot(Precast.haveCTA ^ 1)) { - Config.PrimarySlot = Precast.haveCTA ^ 1; - } else { - // other slot is empty - set cta as primary slot - Config.PrimarySlot = Precast.haveCTA; - } - } else if (!this.checkSlot(sdk.player.slot.Main) && this.checkSlot(sdk.player.slot.Secondary)) { - // only slot II has items - Config.PrimarySlot = sdk.player.slot.Secondary; - } else { - // both slots have items, both are empty, or only slot I has items - Config.PrimarySlot = sdk.player.slot.Main; - } - } - } - - return Config.PrimarySlot; - }, - - getCustomAttack: function (unit) { - // Check if unit got invalidated - if (!unit || !unit.name || !copyUnit(unit).x) return false; - - for (let i in Config.CustomAttack) { - if (Config.CustomAttack.hasOwnProperty(i)) { - // if it contains numbers but is a string, convert to an int - if (i.match(/\d+/g)) { - i = parseInt(i, 10); - } - - switch (typeof i) { - case "string": - if (unit.name.toLowerCase() === i.toLowerCase()) { - return Config.CustomAttack[i]; - } - - break; - case "number": - if (unit.classid === i) { - return Config.CustomAttack[i]; - } - } - } - } - - return false; - }, - - // Get items with charges - isn't used anywhere - getCharges: function () { - !Skill.charges && (Skill.charges = []); - - let item = me.getItem(-1, sdk.items.mode.Equipped); - - if (item) { - do { - let stats = item.getStat(-2); - - if (stats.hasOwnProperty(sdk.stats.ChargedSkill)) { - if (stats[sdk.stats.ChargedSkill] instanceof Array) { - for (let i = 0; i < stats[sdk.stats.ChargedSkill].length; i += 1) { - if (stats[sdk.stats.ChargedSkill][i] !== undefined) { - Skill.charges.push({ - unit: copyUnit(item), - gid: item.gid, - skill: stats[sdk.stats.ChargedSkill][i].skill, - level: stats[sdk.stats.ChargedSkill][i].level, - charges: stats[sdk.stats.ChargedSkill][i].charges, - maxcharges: stats[sdk.stats.ChargedSkill][i].maxcharges - }); - } - } - } else { - Skill.charges.push({ - unit: copyUnit(item), - gid: item.gid, - skill: stats[sdk.stats.ChargedSkill].skill, - level: stats[sdk.stats.ChargedSkill].level, - charges: stats[sdk.stats.ChargedSkill].charges, - maxcharges: stats[sdk.stats.ChargedSkill].maxcharges - }); - } - } - } while (item.getNext()); - } - - return true; - }, - - // Check if player or his merc are using Infinity, and adjust resistance checks based on that - checkInfinity: function () { - if (me.classic) return false; - - let merc; - // check if we have a merc and they aren't dead - Config.UseMerc && !me.mercrevivecost && (merc = Misc.poll(() => me.getMerc(), 1000, 100)); - - // Check merc infinity - !!merc && (this.infinity = merc.checkItem({name: sdk.locale.items.Infinity}).have); - - // Check player infinity - only check if merc doesn't have - !this.infinity && (this.infinity = me.checkItem({name: sdk.locale.items.Infinity, equipped: true}).have); - - return this.infinity; - }, - - checkAuradin: function () { - // Check player Dragon, Dream, HoJ, or Ice - this.auradin = me.haveSome([ - {name: sdk.locale.items.Dragon, equipped: true}, {name: sdk.locale.items.Dream, equipped: true}, - {name: sdk.locale.items.HandofJustice, equipped: true}, {name: sdk.locale.items.Ice, equipped: true}, - ]); - - return this.auradin; - }, - - // just check if we can telestomp - canTeleStomp: function (unit) { - if (!unit || !unit.attackable) return false; - return Config.TeleStomp && Config.UseMerc && Pather.canTeleport() && Attack.checkResist(unit, "physical") && !!me.getMerc() && Attack.validSpot(unit.x, unit.y); - }, - - // Kill a monster based on its classId, can pass a unit as well - kill: function (classId) { - if (!classId || Config.AttackSkill[1] < 0) return false; - let target = (typeof classId === "object" ? classId : Misc.poll(() => Game.getMonster(classId), 2000, 100)); - - if (!target) { - console.warn("Attack.kill: Target not found"); - return Attack.clear(10); - } - - const findTarget = function (gid, loc) { - let path = getPath(me.area, me.x, me.y, loc.x, loc.y, 1, 5); - if (!path) return false; - - if (path.some(function (node) { - Pather.walkTo(node.x, node.y); - return Game.getMonster(-1, -1, gid); - })) { - return Game.getMonster(-1, -1, gid); - } else { - return false; - } - }; - - const who = (!!target.name ? target.name : classId); - const gid = target.gid; - - let retry = 0; - let errorInfo = ""; - let attackCount = 0; - - let lastLoc = {x: me.x, y: me.y}; - let tick = getTickCount(); - console.log("ÿc7Kill ÿc0:: " + who); - Config.MFLeader && Pather.makePortal() && say("kill " + classId); - - while (attackCount < Config.MaxAttackCount && target.attackable && !this.skipCheck(target)) { - Misc.townCheck(); - - // Check if unit got invalidated, happens if necro raises a skeleton from the boss's corpse. - if (!target || !copyUnit(target).x) { - target = Game.getMonster(-1, -1, gid); - !target && (target = findTarget(gid, lastLoc)); - - if (!target) { - console.warn("ÿc1Failed to kill " + who + " (couldn't relocate unit)"); - break; - } - } - - // todo - dodge boss missiles - Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9); - Config.MFSwitchPercent && target.hpPercent < Config.MFSwitchPercent && me.switchWeapons(this.getPrimarySlot() ^ 1); - - if (attackCount > 0 && attackCount % 15 === 0 && Skill.getRange(Config.AttackSkill[1]) < 4) { - Packet.flash(me.gid); - } - - let result = ClassAttack.doAttack(target, attackCount % 15 === 0); - - if (result === this.Result.FAILED) { - if (retry++ > 3) { - errorInfo = " (doAttack failed)"; - - break; - } - - Packet.flash(me.gid); - } else if (result === this.Result.CANTATTACK) { - errorInfo = " (No valid attack skills)"; - - break; - } else if (result === this.Result.NEEDMANA) { - continue; - } else { - retry = 0; - } - - lastLoc = {x: me.x, y: me.y}; - attackCount++; - } - - attackCount === Config.MaxAttackCount && (errorInfo = " (attackCount exceeded: " + attackCount + ")"); - Config.MFSwitchPercent && me.switchWeapons(this.getPrimarySlot()); - ClassAttack.afterAttack(); - Pickit.pickItems(); - - if (!!target && target.attackable) { - console.warn("ÿc1Failed to kill ÿc0" + who + errorInfo); - } else { - console.log("ÿc7Killed ÿc0:: " + who + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick)); - } - - return (!target || !copyUnit(target).x || target.dead || !target.attackable); - }, - - hurt: function (classId, percent) { - if (!classId || !percent) return false; - let target = (typeof classId === "object" ? classid : Misc.poll(() => Game.getMonster(classId), 2000, 100)); - - if (!target) { - console.warn("Attack.hurt: Target not found"); - return false; - } - - let retry = 0, attackCount = 0; - let tick = getTickCount(); - const who = (!!target.name ? target.name : classId); - - while (attackCount < Config.MaxAttackCount && target.attackable && !Attack.skipCheck(target)) { - let result = ClassAttack.doAttack(target, attackCount % 15 === 0); - - if (result === this.Result.FAILED) { - if (retry++ > 3) { - break; - } - - Packet.flash(me.gid); - } else if (result === this.Result.CANTATTACK) { - break; - } else if (result === this.Result.NEEDMANA) { - continue; - } else { - retry = 0; - } - - if (!copyUnit(target).x) { - return true; - } - - attackCount += 1; - - if (target.hpPercent <= percent) { - console.log("ÿc7Hurt ÿc0:: " + who + "ÿc7HpPercent: ÿc0" + target.hpPercent + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick)); - break; - } - } - - return true; - }, - - getScarinessLevel: function (unit) { - // todo - define summonertype prototype - let scariness = 0; - const ids = [ - sdk.monsters.FallenShaman, sdk.monsters.CarverShaman, sdk.monsters.CarverShaman2, sdk.monsters.DevilkinShaman, sdk.monsters.DevilkinShaman2, - sdk.monsters.DarkShaman1, sdk.monsters.DarkShaman2, sdk.monsters.WarpedShaman, sdk.monsters.HollowOne, sdk.monsters.Guardian1, - sdk.monsters.Guardian2, sdk.monsters.Unraveler1, sdk.monsters.Unraveler2, sdk.monsters.Ancient1, sdk.monsters.Ancient2, sdk.monsters.Ancient3, - sdk.monsters.BaalSubjectMummy, sdk.monsters.RatManShaman, sdk.monsters.FetishShaman, sdk.monsters.FlayerShaman1, sdk.monsters.FlayerShaman2, sdk.monsters.SoulKillerShaman1, - sdk.monsters.SoulKillerShaman2, sdk.monsters.StygianDollShaman1, sdk.monsters.StygianDollShaman2, sdk.monsters.FleshSpawner1, sdk.monsters.FleshSpawner2, - sdk.monsters.StygianHag, sdk.monsters.Grotesque1, sdk.monsters.Grotesque2 - ]; - - // Only handling monsters for now - if (!unit || unit.type !== sdk.unittype.Monster) return undefined; - // Minion - (unit.isMinion) && (scariness += 1); - // Champion - (unit.isChampion) && (scariness += 2); - // Boss - (unit.isUnique) && (scariness += 4); - // Summoner or the like - ids.includes(unit.classid) && (scariness += 8); - - return scariness; - }, - - // Clear monsters in a section based on range and spectype or clear monsters around a boss monster - // probably going to change to passing an object - clear: function (range, spectype, bossId, sortfunc, pickit = true) { - while (!me.gameReady) { - delay(40); - } - - if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) return false; - - range === undefined && (range = 25); - spectype === undefined && (spectype = 0); - bossId === undefined && (bossId = false); - sortfunc === undefined && (sortfunc = false); - !sortfunc && (sortfunc = this.sortMonsters); - - if (typeof (range) !== "number") throw new Error("Attack.clear: range must be a number."); - - let i, boss, orgx, orgy, start, skillCheck; - let gidAttack = []; - let tick = getTickCount(); - let [killedBoss, logged] = [false, false]; - let [retry, attackCount] = [0, 0]; - - if (bossId) { - boss = Misc.poll(function () { - switch (true) { - case typeof bossId === "object": - return bossId; - case ((typeof bossId === "number" && bossId > 999)): - return Game.getMonster(-1, -1, bossId); - default: - return Game.getMonster(bossId); - } - }, 2000, 100); - - if (!boss) { - console.warn("Attack.clear: " + bossId + " not found"); - return Attack.clear(10); - } - - ({orgx, orgy} = {orgx: boss.x, orgy: boss.y}); - Config.MFLeader && !!bossId && Pather.makePortal() && say("clear " + bossId); - } else { - ({orgx, orgy} = {orgx: me.x, orgy: me.y}); - } - - let monsterList = []; - let target = Game.getMonster(); - - if (target) { - do { - if ((!spectype || (target.spectype & spectype)) && target.attackable && !this.skipCheck(target)) { - // Speed optimization - don't go through monster list until there's at least one within clear range - if (!start && getDistance(target, orgx, orgy) <= range && (Pather.canTeleport() || !checkCollision(me, target, sdk.collision.WallOrRanged))) { - start = true; - } - - monsterList.push(copyUnit(target)); - } - } while (target.getNext()); - } - - while (start && monsterList.length > 0 && attackCount < Config.MaxAttackCount) { - if (me.dead) return false; - - boss && (({orgx, orgy} = {orgx: boss.x, orgy: boss.y})); - monsterList.sort(sortfunc); - target = copyUnit(monsterList[0]); - - if (target.x !== undefined && (getDistance(target, orgx, orgy) <= range || (this.getScarinessLevel(target) > 7 && target.distance <= range)) - && target.attackable) { - Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9); - Misc.townCheck(true); - tick = getTickCount(); - - if (!logged && boss && boss.gid === target.gid) { - logged = true; - console.log("ÿc7Clear ÿc0:: " + (!!target.name ? target.name : bossId)); - } - //me.overhead("attacking " + target.name + " spectype " + target.spectype + " id " + target.classid); - - let result = ClassAttack.doAttack(target, attackCount % 15 === 0); - - if (result) { - retry = 0; - - if (result === this.Result.CANTATTACK) { - monsterList.shift(); - - continue; - } else if (result === this.Result.NEEDMANA) { - continue; - } - - for (i = 0; i < gidAttack.length; i += 1) { - if (gidAttack[i].gid === target.gid) { - break; - } - } - - if (i === gidAttack.length) { - gidAttack.push({gid: target.gid, attacks: 0, name: target.name}); - } - - gidAttack[i].attacks += 1; - attackCount += 1; - let isSpecial = target.isSpecial; - let secAttack = me.barbarian ? (isSpecial ? 2 : 4) : 5; - - if (Config.AttackSkill[secAttack] > -1 && (!Attack.checkResist(target, Config.AttackSkill[isSpecial ? 1 : 3]) - || (me.paladin && Config.AttackSkill[isSpecial ? 1 : 3] === sdk.skills.BlessedHammer && !ClassAttack.getHammerPosition(target)))) { - skillCheck = Config.AttackSkill[secAttack]; - } else { - skillCheck = Config.AttackSkill[isSpecial ? 1 : 3]; - } - - // Desync/bad position handler - switch (skillCheck) { - case sdk.skills.BlessedHammer: - // Tele in random direction with Blessed Hammer - if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 4 : 2) === 0) { - let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 5); - Pather.moveTo(coord.x, coord.y); - } - - break; - default: - // Flash with melee skills - if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 15 : 5) === 0 && Skill.getRange(skillCheck) < 4) { - Packet.flash(me.gid); - } - - break; - } - - // Skip non-unique monsters after 15 attacks, except in Throne of Destruction - if (!me.inArea(sdk.areas.ThroneofDestruction) && !isSpecial && gidAttack[i].attacks > 15) { - print("ÿc1Skipping " + target.name + " " + target.gid + " " + gidAttack[i].attacks); - monsterList.shift(); - } - - if (target.dead || Config.FastPick) { - if (boss && boss.gid === target.gid && target.dead) { - killedBoss = true; - console.log("ÿc7Cleared ÿc0:: " + (!!target.name ? target.name : bossId) + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick)); - } - Pickit.fastPick(); - } - } else { - if (retry++ > 3) { - monsterList.shift(); - retry = 0; - } - - Packet.flash(me.gid); - } - } else { - monsterList.shift(); - } - } - - if (attackCount > 0) { - ClassAttack.afterAttack(pickit); - this.openChests(range, orgx, orgy); - pickit && Pickit.pickItems(); - } else { - Precast.doPrecast(false); // we didn't attack anything but check if we need to precast. TODO: better method of keeping track of precast skills - } - - if (boss && !killedBoss) { - // check if boss corpse is around - if (boss.dead) { - console.log("ÿc7Cleared ÿc0:: " + (!!boss.name ? boss.name : bossId) + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick)); - } else { - console.log("ÿc7Clear ÿc0:: ÿc1Failed to clear ÿc0:: " + (!!boss.name ? boss.name : bossId)); - } - } - - return true; - }, - - clearClassids: function (...ids) { - let monster = Game.getMonster(); - - if (monster) { - let list = []; - - do { - if (ids.includes(monster.classid) && monster.attackable) { - list.push(copyUnit(monster)); - } - } while (monster.getNext()); - - Attack.clearList(list); - } - - return true; - }, - - // Filter monsters based on classId, spectype and range - getMob: function (classid, spectype, range, center) { - let monsterList = []; - let monster = Game.getMonster(); - - range === undefined && (range = 25); - !center && (center = me); - - switch (typeof classid) { - case "number": - case "string": - monster = Game.getMonster(classid); - - if (monster) { - do { - if (getDistance(center.x, center.y, monster.x, monster.y) <= range - && (!spectype || (monster.spectype & spectype)) && monster.attackable) { - monsterList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - break; - case "object": - monster = Game.getMonster(); - - if (monster) { - do { - if (classid.includes(monster.classid) && getDistance(center.x, center.y, monster.x, monster.y) <= range - && (!spectype || (monster.spectype & spectype)) && monster.attackable) { - monsterList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - break; - } - - return monsterList; - }, - - // Clear an already formed array of monstas - clearList: function (mainArg, sortFunc, refresh) { - let i, target, monsterList; - let retry = 0; - let gidAttack = []; - let attackCount = 0; - - switch (typeof mainArg) { - case "function": - monsterList = mainArg.call(); - - break; - case "object": - monsterList = mainArg.slice(0); - - break; - case "boolean": // false from Attack.getMob() - return false; - default: - throw new Error("clearList: Invalid argument"); - } - - !sortFunc && (sortFunc = this.sortMonsters); - - while (monsterList.length > 0 && attackCount < Config.MaxAttackCount) { - if (refresh && attackCount > 0 && attackCount % refresh === 0) { - monsterList = mainArg.call(); - } - - if (me.dead) return false; - - monsterList.sort(sortFunc); - target = copyUnit(monsterList[0]); - - if (target.x !== undefined && target.attackable) { - Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9); - Misc.townCheck(true); - //me.overhead("attacking " + target.name + " spectype " + target.spectype + " id " + target.classid); - - let result = ClassAttack.doAttack(target, attackCount % 15 === 0); - - if (result) { - retry = 0; - - if (result === this.Result.CANTATTACK) { - monsterList.shift(); - - continue; - } else if (result === this.Result.NEEDMANA) { - continue; - } - - for (i = 0; i < gidAttack.length; i += 1) { - if (gidAttack[i].gid === target.gid) { - break; - } - } - - if (i === gidAttack.length) { - gidAttack.push({gid: target.gid, attacks: 0}); - } - - gidAttack[i].attacks += 1; - let isSpecial = target.isSpecial; - - // Desync/bad position handler - switch (Config.AttackSkill[isSpecial ? 1 : 3]) { - case sdk.skills.BlessedHammer: - // Tele in random direction with Blessed Hammer - if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 5 : 15) === 0) { - let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 4); - Pather.moveTo(coord.x, coord.y); - } - - break; - default: - // Flash with melee skills - if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 5 : 15) === 0 && Skill.getRange(Config.AttackSkill[isSpecial ? 1 : 3]) < 4) { - Packet.flash(me.gid); - } - - break; - } - - // Skip non-unique monsters after 15 attacks, except in Throne of Destruction - if (!me.inArea(sdk.areas.ThroneofDestruction) && !isSpecial && gidAttack[i].attacks > 15) { - console.log("ÿc1Skipping " + target.name + " " + target.gid + " " + gidAttack[i].attacks); - monsterList.shift(); - } - - attackCount += 1; - - if (target.dead || Config.FastPick) { - Pickit.fastPick(); - } - } else { - if (retry++ > 3) { - monsterList.shift(); - retry = 0; - } - - Packet.flash(me.gid); - } - } else { - monsterList.shift(); - } - } - - if (attackCount > 0) { - ClassAttack.afterAttack(true); - this.openChests(Config.OpenChests.Range); - Pickit.pickItems(); - } else { - Precast.doPrecast(false); // we didn't attack anything but check if we need to precast. TODO: better method of keeping track of precast skills - } - - return true; - }, - - securePosition: function (x, y, range = 15, timer = 3000, skipBlocked = true, special = false) { - let tick; - - (typeof x !== "number" || typeof y !== "number") && ({x, y} = me); - skipBlocked === true && (skipBlocked = sdk.collision.Ranged); - - while (true) { - [x, y].distance > 5 && Pather.moveTo(x, y); - - let monster = Game.getMonster(); - let monList = []; - - if (monster) { - do { - if (getDistance(monster, x, y) <= range && monster.attackable && this.canAttack(monster) - && (!skipBlocked || !checkCollision(me, monster, skipBlocked)) - && (Pather.canTeleport() || !checkCollision(me, monster, sdk.collision.BlockWall))) { - monList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - if (!monList.length) { - !tick && (tick = getTickCount()); - - // only return if it's been safe long enough - if (getTickCount() - tick >= timer) { - return; - } - } else { - this.clearList(monList); - - // reset the timer when there's monsters in range - tick && (tick = false); - } - - if (special) { - if (me.paladin && Skill.canUse(sdk.skills.Redemption) && Skill.setSkill(sdk.skills.Redemption, sdk.skills.hand.Right)) { - delay(1000); - } - } - - delay(100); - } - }, - - // Draw lines around a room on minimap - markRoom: function (room, color) { - let arr = []; - - arr.push(new Line(room.x * 5, room.y * 5, room.x * 5, room.y * 5 + room.ysize, color, true)); - arr.push(new Line(room.x * 5, room.y * 5, room.x * 5 + room.xsize, room.y * 5, color, true)); - arr.push(new Line(room.x * 5 + room.xsize, room.y * 5, room.x * 5 + room.xsize, room.y * 5 + room.ysize, color, true)); - arr.push(new Line(room.x * 5, room.y * 5 + room.ysize, room.x * 5 + room.xsize, room.y * 5 + room.ysize, color, true)); - }, - - countUniques: function () { - !this.uniques && (this.uniques = 0); - !this.ignoredGids && (this.ignoredGids = []); - - let monster = Game.getMonster(); - - if (monster) { - do { - if ((monster.isSuperUnique) && this.ignoredGids.indexOf(monster.gid) === -1) { - this.uniques += 1; - this.ignoredGids.push(monster.gid); - } - } while (monster.getNext()); - } - }, - - storeStatistics: function (area) { - !FileTools.exists("statistics.json") && Misc.fileAction("statistics.json", 1, "{}"); - - let obj = JSON.parse(Misc.fileAction("statistics.json", 0)); - - if (obj) { - if (obj[area] === undefined) { - obj[area] = { - runs: 0, - averageUniques: 0 - }; - } - - obj[area].averageUniques = ((obj[area].averageUniques * obj[area].runs + this.uniques) / (obj[area].runs + 1)).toFixed(4); - obj[area].runs += 1; - - Misc.fileAction("statistics.json", 1, JSON.stringify(obj)); - } - - this.uniques = 0; - this.ignoredGids = []; - }, - - // Clear an entire area based on monster spectype - clearLevel: function (spectype = 0) { - function RoomSort(a, b) { - return getDistance(myRoom[0], myRoom[1], a[0], a[1]) - getDistance(myRoom[0], myRoom[1], b[0], b[1]); - } - - let room = getRoom(); - if (!room) return false; - - console.time("clearLevel"); - console.info(true, Pather.getAreaName(me.area)); - - let myRoom, previousArea; - let rooms = []; - const currentArea = getArea().id; - const breakClearLevelCheck = !!(Loader.scriptName() === "MFHelper" && Config.MFHelper.BreakClearLevel && Config.Leader !== ""); - - do { - rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]); - } while (room.getNext()); - - if (Config.MFLeader && rooms.length > 0) { - Pather.makePortal(); - // tombs exception - if (me.area >= sdk.areas.TalRashasTomb1 && me.area <= sdk.areas.TalRashasTomb7) { - say("clearlevel " + me.area); - } else { - say("clearlevel " + Pather.getAreaName(currentArea)); - } - } - - while (rooms.length > 0) { - // get the first room + initialize myRoom var - !myRoom && (room = getRoom(me.x, me.y)); - - if (breakClearLevelCheck) { - let leader = Misc.findPlayer(Config.Leader); - - if (leader && leader.area !== me.area && !leader.inTown) { - me.overhead("break the clearing in " + getArea().name); - - return true; - } - } - - if (room) { - // use previous room to calculate distance - if (room instanceof Array) { - myRoom = [room[0], room[1]]; - } else { - // create a new room to calculate distance (first room, done only once) - myRoom = [room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]; - } - } - - rooms.sort(RoomSort); - room = rooms.shift(); - - let result = Pather.getNearestWalkable(room[0], room[1], 18, 3); - - if (result) { - Pather.moveTo(result[0], result[1], 3, spectype); - previousArea = result; - - if (!this.clear(40, spectype)) { - break; - } - } else if (currentArea !== getArea().id) { - // Make sure bot does not get stuck in different area. - Pather.moveTo(previousArea[0], previousArea[1], 3, spectype); - } - } - - //this.storeStatistics(Pather.getAreaName(me.area)); - console.info(false, Pather.getAreaName(currentArea), "clearLevel"); - - return true; - }, - - // Sort monsters based on distance, spectype and classId (summoners are attacked first) - // Think this needs a collison check included for non tele chars, might prevent choosing closer mob that is actually behind a wall vs the one we pass trying to get behind the wall - sortMonsters: function (unitA, unitB) { - // No special sorting for were-form - if (Config.Wereform) return getDistance(me, unitA) - getDistance(me, unitB); - - // sort main bosses first - // Andy - if (me.inArea(sdk.areas.CatacombsLvl4)) { - if (unitA.distance < 5 && unitA.classid === sdk.monsters.Andariel && !checkCollision(me, unitA, sdk.collision.Ranged)) return -1; - } - - // Meph - if (me.inArea(sdk.areas.DuranceofHateLvl3)) { - if (unitA.distance < 5 && unitA.classid === sdk.monsters.Mephisto && !checkCollision(me, unitA, sdk.collision.Ranged)) return -1; - } - - // Baal - if (me.inArea(sdk.areas.WorldstoneChamber)) { - if (unitA.classid === sdk.monsters.Baal) return -1; - } - - // Barb optimization - if (me.barbarian) { - if (!Attack.checkResist(unitA, Attack.getSkillElement(Config.AttackSkill[(unitA.isSpecial) ? 1 : 3]))) { - return 1; - } - - if (!Attack.checkResist(unitB, Attack.getSkillElement(Config.AttackSkill[(unitB.isSpecial) ? 1 : 3]))) { - return -1; - } - } - - // Put monsters under Attract curse at the end of the list - They are helping us - if (unitA.getState(sdk.states.Attract)) return 1; - if (unitB.getState(sdk.states.Attract)) return -1; - - const ids = [ - sdk.monsters.OblivionKnight1, sdk.monsters.OblivionKnight2, sdk.monsters.OblivionKnight3, sdk.monsters.FallenShaman, sdk.monsters.CarverShaman, sdk.monsters.CarverShaman2, - sdk.monsters.DevilkinShaman, sdk.monsters.DevilkinShaman2, sdk.monsters.DarkShaman1, sdk.monsters.DarkShaman2, sdk.monsters.WarpedShaman, sdk.monsters.HollowOne, sdk.monsters.Guardian1, - sdk.monsters.Guardian2, sdk.monsters.Unraveler1, sdk.monsters.Unraveler2, sdk.monsters.Ancient1, sdk.monsters.BaalSubjectMummy, sdk.monsters.BloodRaven, sdk.monsters.RatManShaman, - sdk.monsters.FetishShaman, sdk.monsters.FlayerShaman1, sdk.monsters.FlayerShaman2, sdk.monsters.SoulKillerShaman1, sdk.monsters.SoulKillerShaman2, sdk.monsters.StygianDollShaman1, - sdk.monsters.StygianDollShaman2, sdk.monsters.FleshSpawner1, sdk.monsters.FleshSpawner2, sdk.monsters.StygianHag, sdk.monsters.Grotesque1, sdk.monsters.Ancient2, sdk.monsters.Ancient3, - sdk.monsters.Grotesque2 - ]; - - if (!me.inArea(sdk.areas.ClawViperTempleLvl2) && ids.includes(unitA.classid) && ids.includes(unitB.classid)) { - // Kill "scary" uniques first (like Bishibosh) - if ((unitA.isUnique) && (unitB.isUnique)) return getDistance(me, unitA) - getDistance(me, unitB); - if (unitA.isUnique) return -1; - if (unitB.isUnique) return 1; - - return getDistance(me, unitA) - getDistance(me, unitB); - } - - if (ids.includes(unitA.classid)) return -1; - if (ids.includes(unitB.classid)) return 1; - - if (Config.BossPriority) { - if ((unitA.isSuperUnique) && (unitB.isSuperUnique)) return getDistance(me, unitA) - getDistance(me, unitB); - - if (unitA.isSuperUnique) return -1; - if (unitB.isSuperUnique) return 1; - } - - return getDistance(me, unitA) - getDistance(me, unitB); - }, - - // Check if a set of coords is valid/accessable - // re-work this for more info - // casting skills can go over non-floors - excluding bliz/meteor - not sure if any others - // physical skills can't, need to exclude monster objects though - // splash skills can go through some objects, however some objects are cast blockers - validSpot: function (x, y, skill = -1, unitid = 0) { - // Just in case - if (!me.area || !x || !y) return false; - // for now this just returns true and we leave getting into position to the actual class attack files - if (Skill.missileSkills.includes(skill) - || ([sdk.skills.Blizzard, sdk.skills.Meteor].includes(skill) && unitid > 0 && !getBaseStat("monstats", unitid, "flying"))) { - return true; - } - - let result; - let nonFloorAreas = [sdk.areas.ArcaneSanctuary, sdk.areas.RiverofFlame, sdk.areas.ChaosSanctuary, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit]; - - // Treat thrown errors as invalid spot - try { - result = getCollision(me.area, x, y); - } catch (e) { - return false; - } - - if (result === undefined) return false; - - switch (true) { - case Skill.needFloor.includes(skill) && nonFloorAreas.includes(me.area): - let isFloor = !!(result & (0 | sdk.collision.IsOnFloor)); - // this spot is not on the floor (lava (river/chaos, space (arcane), ect)) - if (!isFloor) { - return false; - } - - return !(result & sdk.collision.BlockWall); // outside lava area in abaddon returns coll 1 - case Attack.monsterObjects.includes(unitid) && (!!(result & sdk.collision.MonsterIsOnFloor) || !!(result & sdk.collision.MonsterObject)): - // kinda dumb - monster objects have a collision that causes them to not be attacked - // this should fix that - return true; - default: - // Avoid non-walkable spots, objects - this preserves the orignal function and also physical attack skills will get here - if ((result & sdk.collision.BlockWall) || (result & sdk.collision.Objects)) return false; - - break; - } - - return true; - }, - - // Open chests when clearing - openChests: function (range, x, y) { - if (!Config.OpenChests.Enabled) return false; - (typeof x !== "number" || typeof y !== "number") && ({x, y} = me); - range === undefined && (range = 10); - - let list = []; - let ids = ["chest", "chest3", "weaponrack", "armorstand"]; - let unit = Game.getObject(); - - if (unit) { - do { - if (unit.name && getDistance(unit, x, y) <= range && ids.includes(unit.name.toLowerCase())) { - list.push(copyUnit(unit)); - } - } while (unit.getNext()); - } - - while (list.length) { - list.sort(Sort.units); - - if (Misc.openChest(list.shift())) { - Pickit.pickItems(); - } - } - - return true; - }, - - buildMonsterList: function () { - let monList = []; - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.attackable) { - monList.push(copyUnit(monster)); - } - } while (monster.getNext()); - } - - return monList; - }, - - findSafeSpot: function (unit, distance, spread, range) { - if (arguments.length < 4) throw new Error("deploy: Not enough arguments supplied"); - - let index; - let monList = []; - let count = 999; - - monList = this.buildMonsterList(); - monList.sort(Sort.units); - if (this.getMonsterCount(me.x, me.y, 15, monList) === 0) return true; - - CollMap.getNearbyRooms(unit.x, unit.y); - let grid = this.buildGrid(unit.x - distance, unit.x + distance, unit.y - distance, unit.y + distance, spread); - - if (!grid.length) return false; - grid.sort((a, b) => getDistance(b.x, b.y, unit.x, unit.y) - getDistance(a.x, a.y, unit.x, unit.y)); - - for (let i = 0; i < grid.length; i += 1) { - if (!(CollMap.getColl(grid[i].x, grid[i].y, true) & sdk.collision.BlockWall) && !CollMap.checkColl(unit, {x: grid[i].x, y: grid[i].y}, sdk.collision.Ranged)) { - let currCount = this.getMonsterCount(grid[i].x, grid[i].y, range, monList); - - if (currCount < count) { - index = i; - count = currCount; - } - - if (currCount === 0) { - break; - } - } - } - - if (typeof index === "number") { - return { - x: grid[index].x, - y: grid[index].y, - }; - } - - return false; - }, - - deploy: function (unit, distance, spread, range) { - if (arguments.length < 4) throw new Error("deploy: Not enough arguments supplied"); - - let safeLoc = this.findSafeSpot(unit, distance, spread, range); - - return (typeof safeLoc === "object" ? Pather.moveToUnit(safeLoc, 0) : false); - }, - - getMonsterCount: function (x, y, range, list) { - let count = 0; - let ignored = [sdk.monsters.Diablo]; // why is diablo ignored? - - for (let i = 0; i < list.length; i += 1) { - if (ignored.indexOf(list[i].classid) === -1 && list[i].attackable && getDistance(x, y, list[i].x, list[i].y) <= range) { - count += 1; - } - } - - // missile check? - let fire = Game.getObject("fire"); - - if (fire) { - do { - if (getDistance(x, y, fire.x, fire.y) <= 4) { - count += 100; - } - } while (fire.getNext()); - } - - return count; - }, - - buildGrid: function (xmin, xmax, ymin, ymax, spread) { - if (xmin >= xmax || ymin >= ymax || spread < 1) { - throw new Error("buildGrid: Bad parameters"); - } - - let grid = []; - - for (let i = xmin; i <= xmax; i += spread) { - for (let j = ymin; j <= ymax; j += spread) { - let coll = CollMap.getColl(i, j, true); - - if (typeof coll === "number") { - grid.push({x: i, y: j, coll: coll}); - } - } - } - - return grid; - }, - - /** - * @param unit - * @desc checks if we should skip a monster - * @returns Boolean - */ - skipCheck: function (unit) { - if (me.inArea(sdk.areas.ThroneofDestruction)) return false; - if (unit.isSpecial && Config.SkipException && Config.SkipException.includes(unit.name)) { - console.log("ÿc1Skip Exception: " + unit.name); - return false; - } - - if (Config.SkipId.includes(unit.classid)) return true; - - let tempArray = []; - - // EnchantLoop: // Skip enchanted monsters - for (let i = 0; i < Config.SkipEnchant.length; i += 1) { - tempArray = Config.SkipEnchant[i].toLowerCase().split(" and "); - - for (let j = 0; j < tempArray.length; j += 1) { - switch (tempArray[j]) { - case "extra strong": - tempArray[j] = sdk.enchant.ExtraStrong; - - break; - case "extra fast": - tempArray[j] = sdk.enchant.ExtraFast; - - break; - case "cursed": - tempArray[j] = sdk.enchant.Cursed; - - break; - case "magic resistant": - tempArray[j] = sdk.enchant.MagicResistant; - - break; - case "fire enchanted": - tempArray[j] = sdk.enchant.FireEnchanted; - - break; - case "lightning enchanted": - tempArray[j] = sdk.enchant.LightningEnchanted; - - break; - case "cold enchanted": - tempArray[j] = sdk.enchant.ColdEnchanted; - - break; - case "mana burn": - tempArray[j] = sdk.enchant.ManaBurn; - - break; - case "teleportation": - tempArray[j] = sdk.enchant.Teleportation; - - break; - case "spectral hit": - tempArray[j] = sdk.enchant.SpectralHit; - - break; - case "stone skin": - tempArray[j] = sdk.enchant.StoneSkin; - - break; - case "multiple shots": - tempArray[j] = sdk.enchant.MultipleShots; - - break; - } - } - - if (tempArray.every(enchant => unit.getEnchant(enchant))) { - return true; - } - } - - // ImmuneLoop: // Skip immune monsters - for (let i = 0; i < Config.SkipImmune.length; i += 1) { - tempArray = Config.SkipImmune[i].toLowerCase().split(" and "); - - // Infinity calculations are built-in - if (tempArray.every(immnue => !Attack.checkResist(unit, immnue))) { - return true; - } - } - - // AuraLoop: // Skip monsters with auras - for (let i = 0; i < Config.SkipAura.length; i += 1) { - let aura = Config.SkipAura[i].toLowerCase(); - - switch (true) { - case aura === "might" && unit.getState(sdk.states.Might): - case aura === "blessed aim" && unit.getState(sdk.states.BlessedAim): - case aura === "fanaticism" && unit.getState(sdk.states.Fanaticism): - case aura === "conviction" && unit.getState(sdk.states.Conviction): - case aura === "holy fire" && unit.getState(sdk.states.HolyFire): - case aura === "holy freeze" && unit.getState(sdk.states.HolyFreeze): - case aura === "holy shock" && unit.getState(sdk.states.HolyShock): - return true; - default: - break; - } - } - - return false; - }, - - // Get element by skill number - getSkillElement: function (skillId) { - this.elements = ["physical", "fire", "lightning", "magic", "cold", "poison", "none"]; - - switch (skillId) { - case sdk.skills.HolyFire: - return "fire"; - case sdk.skills.HolyFreeze: - return "cold"; - case sdk.skills.HolyShock: - return "lightning"; - case sdk.skills.CorpseExplosion: - case sdk.skills.Stun: - case sdk.skills.Concentrate: - case sdk.skills.Frenzy: - case sdk.skills.MindBlast: - case sdk.skills.Summoner: - return "physical"; - case sdk.skills.HolyBolt: - // no need to use this.elements array because it returns before going over the array - return "holybolt"; - } - - let eType = getBaseStat("skills", skillId, "etype"); - - return typeof (eType) === "number" ? this.elements[eType] : false; - }, - - // Get a monster's resistance to specified element - getResist: function (unit, type) { - // some scripts pass empty units in throne room - if (!unit || !unit.getStat) return 100; - if (unit.isPlayer) return 0; - - switch (type) { - case "physical": - return unit.getStat(sdk.stats.DamageResist); - case "fire": - return unit.getStat(sdk.stats.FireResist); - case "lightning": - return unit.getStat(sdk.stats.LightningResist); - case "magic": - return unit.getStat(sdk.stats.MagicResist); - case "cold": - return unit.getStat(sdk.stats.ColdResist); - case "poison": - return unit.getStat(sdk.stats.PoisonResist); - case "none": - return 0; - case "holybolt": // check if a monster is undead - if (getBaseStat("monstats", unit.classid, "lUndead") || getBaseStat("monstats", unit.classid, "hUndead")) { - return 0; - } - - return 100; - } - - return 100; - }, - - getLowerResistPercent: function () { - const calc = (level) => Math.floor(Math.min(25 + (45 * ((110 * level) / (level + 6)) / 100), 70)); - if (Skill.canUse(sdk.skills.LowerResist)) { - return calc(me.getSkill(sdk.skills.LowerResist, sdk.skills.subindex.SoftPoints)); - } - return 0; - }, - - getConvictionPercent: function () { - const calc = (level) => Math.floor(Math.min(25 + (5 * level), 150)); - if (me.expansion && this.checkInfinity()) { - return calc(12); - } - if (Skill.canUse(sdk.skills.Conviction)) { - return calc(me.getSkill(sdk.skills.Conviction, sdk.skills.subindex.SoftPoints)); - } - return 0; - }, - - // Check if a monster is immune to specified attack type - checkResist: function (unit, val, maxres = 100) { - if (!unit || !unit.type || unit.isPlayer) return true; - - const damageType = typeof val === "number" ? this.getSkillElement(val) : val; - const addLowerRes = !!(Skill.canUse(sdk.skills.LowerResist) && unit.curseable); - - // Static handler - if (val === sdk.skills.StaticField && this.getResist(unit, damageType) < 100) { - return unit.hpPercent > Config.CastStatic; - } - - // TODO: sometimes unit is out of range of conviction so need to check that - // baal in throne room doesn't have getState - if (this.infinity && ["fire", "lightning", "cold"].includes(damageType) && unit.getState) { - if (!unit.getState(sdk.states.Conviction)) { - if (addLowerRes && !unit.getState(sdk.states.LowerResist)) { - let lowerResPercent = this.getLowerResistPercent(); - return (this.getResist(unit, damageType) - (Math.floor((lowerResPercent + 85) / 5))) < 100; - } - return this.getResist(unit, damageType) < 117; - } - - return this.getResist(unit, damageType) < maxres; - } - - if (this.auradin && ["physical", "fire", "cold", "lightning"].includes(damageType) && me.getState(sdk.states.Conviction) && unit.getState) { - let valid = false; - - // our main dps is not physical despite using zeal - if (damageType === "physical") return true; - - if (!unit.getState(sdk.states.Conviction)) { - return (this.getResist(unit, damageType) - (this.getConvictionPercent() / 5) < 100); - } - - // check unit's fire resistance - if (me.getState(sdk.states.HolyFire)) { - valid = this.getResist(unit, "fire") < maxres; - } - - // check unit's light resistance but only if the above check failed - if (me.getState(sdk.states.HolyShock) && !valid) { - valid = this.getResist(unit, "lightning") < maxres; - } - - // check unit's cold resistance but only if the above checks failed - we might be using an Ice Bow - if (me.getState(sdk.states.HolyFreeze) && !valid) { - valid = this.getResist(unit, "cold") < maxres; - } - - // TODO: maybe if still invalid at this point check physical resistance? Although if we are an auradin our physcial dps is low - - return valid; - } - - if (addLowerRes && ["fire", "lightning", "cold", "poison"].includes(damageType) && unit.getState) { - let lowerResPercent = this.getLowerResistPercent(); - if (!unit.getState(sdk.states.LowerResist)) { - return (this.getResist(unit, damageType) - (Math.floor(lowerResPercent / 5)) < 100); - } - } - - return this.getResist(unit, damageType) < maxres; - }, - - // Check if we have valid skills to attack a monster - canAttack: function (unit) { - if (unit.isMonster) { - // Unique/Champion - if (unit.isSpecial) { - if (Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[1])) || Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[2]))) { - return true; - } - } else { - if (Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[3])) || Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[4]))) { - return true; - } - } - - if (Config.AttackSkill.length === 7) { - return Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[5])) || Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[6])); - } - } - - return false; - }, - - // Detect use of bows/crossbows - usingBow: function () { - let item = me.getItem(-1, sdk.items.mode.Equipped); - - if (item) { - do { - if (item.isOnMain) { - switch (item.itemType) { - case sdk.items.type.Bow: - case sdk.items.type.AmazonBow: - return "bow"; - case sdk.items.type.Crossbow: - return "crossbow"; - } - } - } while (item.getNext()); - } - - return false; - }, - - // Find an optimal attack position and move or walk to it - getIntoPosition: function (unit, distance, coll, walk) { - if (!unit || !unit.x || !unit.y) return false; - - walk === true && (walk = 1); - - if (distance < 4 && (!unit.hasOwnProperty("mode") || !unit.dead)) { - //me.overhead("Short range"); - - if (walk) { - if (unit.distance > 8 || checkCollision(me, unit, coll)) { - Pather.walkTo(unit.x, unit.y, 3); - } - } else { - Pather.moveTo(unit.x, unit.y, 0); - } - - return !CollMap.checkColl(me, unit, coll); - } - - let coords = []; - let fullDistance = distance; - let name = unit.hasOwnProperty("name") ? unit.name : ""; - let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); - let angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 135, -135, 180]; - - //let t = getTickCount(); - - for (let n = 0; n < 3; n += 1) { - n > 0 && (distance -= Math.floor(fullDistance / 3 - 1)); - - for (let i = 0; i < angles.length; i += 1) { - let cx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * distance + unit.x); - let cy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * distance + unit.y); - - if (Pather.checkSpot(cx, cy, sdk.collision.BlockWall, false)) { - coords.push({x: cx, y: cy}); - } - } - - //print("ÿc9potential spots: ÿc2" + coords.length); - - if (coords.length > 0) { - coords.sort(Sort.units); - - for (let i = 0; i < coords.length; i += 1) { - // Valid position found - if (!CollMap.checkColl({x: coords[i].x, y: coords[i].y}, unit, coll, 1)) { - //print("ÿc9optimal pos build time: ÿc2" + (getTickCount() - t) + " ÿc9distance from target: ÿc2" + getDistance(cx, cy, unit.x, unit.y)); - - switch (walk) { - case 1: - Pather.walkTo(coords[i].x, coords[i].y, 2); - - break; - case 2: - if (coords[i].distance < 6 && !CollMap.checkColl(me, coords[i], sdk.collision.WallOrRanged)) { - Pather.walkTo(coords[i].x, coords[i].y, 2); - } else { - Pather.moveTo(coords[i].x, coords[i].y, 1); - } - - break; - default: - Pather.moveTo(coords[i].x, coords[i].y, 1); - - break; - } - - return true; - } - } - } - } - - !!name && print("ÿc4Attackÿc0: No valid positions for: " + name); - - return false; - }, - - getNearestMonster: function (givenSettings = {}) { - const settings = Object.assign({}, { - skipBlocked: true, - skipImmune: true, - skipGid: -1, - }, givenSettings); - - let gid; - let monster = Game.getMonster(); - let range = 30; - - if (monster) { - do { - if (monster.attackable && !monster.getParent()) { - let distance = getDistance(me, monster); - - if (distance < range - && (settings.skipGid === -1 || monster.gid !== settings.skipGid) - && (!settings.skipBlocked || !checkCollision(me, monster, sdk.collision.WallOrRanged)) - && (!settings.skipImmune || Attack.canAttack(monster))) { - range = distance; - gid = monster.gid; - } - } - } while (monster.getNext()); - } - - return !!gid ? Game.getMonster(-1, -1, gid) : false; - }, - - checkCorpse: function (unit) { - if (!unit || (unit.mode !== sdk.monsters.mode.Death && unit.mode !== sdk.monsters.mode.Dead)) return false; - if (unit.classid <= sdk.monsters.BurningDeadArcher2 && !getBaseStat("monstats2", unit.classid, "corpseSel")) return false; - return ([ - sdk.states.FrozenSolid, sdk.states.Revive, sdk.states.Redeemed, - sdk.states.CorpseNoDraw, sdk.states.Shatter, sdk.states.RestInPeace, sdk.states.CorpseNoSelect - ].every(state => !unit.getState(state))); - }, - - checkNearCorpses: function (unit, range = 15) { - let corpses = getUnits(sdk.unittype.Monster).filter(function (corpse) { - return getDistance(corpse, unit) <= range && Attack.checkCorpse(corpse); - }); - return corpses.length > 0 ? corpses : []; - }, - - whirlwind: function (unit) { - if (!unit.attackable) return true; - - let angles = [180, 175, -175, 170, -170, 165, -165, 150, -150, 135, -135, 45, -45, 90, -90]; - - unit.isSpecial && angles.unshift(120); - - me.runwalk = me.gametype; - let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); - - // get a better spot - for (let i = 0; i < angles.length; i += 1) { - let coords = [Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * 4 + unit.x), Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * 4 + unit.y)]; - - if (!CollMap.checkColl(me, {x: coords[0], y: coords[1]}, sdk.collision.BlockWall, 1)) { - return Skill.cast(sdk.skills.Whirlwind, sdk.skills.hand.Right, coords[0], coords[1]); - } - } - - return (Attack.validSpot(unit.x, unit.y) && Skill.cast(sdk.skills.Whirlwind, Skill.getHand(sdk.skills.Whirlwind), me.x, me.y)); - } -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Amazon.js b/d2bs/kolbot/libs/common/Attacks/Amazon.js deleted file mode 100644 index 325567d12..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Amazon.js +++ /dev/null @@ -1,243 +0,0 @@ -/** -* @filename Amazon.js -* @author kolton, theBGuy -* @desc Amazon attack sequence -* -*/ - -const ClassAttack = { - bowCheck: false, - lightFuryTick: 0, - - decideSkill: function (unit) { - let skills = {timed: -1, untimed: -1}; - if (!unit) return skills; - - let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - let classid = unit.classid; - - // Get timed skill - let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - skills.timed = checkSkill; - } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { - skills.timed = Config.AttackSkill[5]; - } - - // Get untimed skill - checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill)) { - skills.untimed = checkSkill; - } else if (Config.AttackSkill[6] > -1 && Attack.checkResist(unit, Config.AttackSkill[6]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { - skills.untimed = Config.AttackSkill[6]; - } - - // Low mana timed skill - if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(skills.timed) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) { - skills.timed = Config.LowManaSkill[0]; - } - - // Low mana untimed skill - if (Config.LowManaSkill[1] > -1 && Skill.getManaCost(skills.untimed) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[1])) { - skills.untimed = Config.LowManaSkill[1]; - } - - return skills; - }, - - doAttack: function (unit, preattack) { - if (!unit) return Attack.Result.SUCCESS; - let gid = unit.gid; - let needRepair = Town.needRepair(); - - if ((Config.MercWatch && Town.needMerc()) || needRepair.length > 0) { - print("towncheck"); - - if (Town.visitTown(!!needRepair.length)) { - // lost reference to the mob we were attacking - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - return Attack.Result.SUCCESS; - } - } - } - - if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - if (Skill.canUse(sdk.skills.InnerSight)) { - if (!unit.getState(sdk.states.InnerSight) && unit.distance > 3 && unit.distance < 13 && !checkCollision(me, unit, sdk.collision.Ranged)) { - Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit); - } - } - - if (Skill.canUse(sdk.skills.SlowMissiles)) { - if (!unit.getState(sdk.states.SlowMissiles)) { - if ((unit.distance > 3 || unit.getEnchant(sdk.enchant.LightningEnchanted)) && unit.distance < 13 && !checkCollision(me, unit, sdk.collision.Ranged)) { - // Act Bosses and mini-bosses are immune to Slow Missles and pointless to use on lister or Cows, Use Inner-Sight instead - if ([sdk.monsters.HellBovine].includes(unit.classid) || unit.isBoss) { - // Check if already in this state - if (!unit.getState(sdk.states.InnerSight) && Config.UseInnerSight && Skill.canUse(sdk.skills.InnerSight)) { - Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit); - } - } else { - Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit); - } - } - } - } - - let mercRevive = 0; - let skills = this.decideSkill(unit); - let result = this.doCast(unit, skills.timed, skills.untimed); - - if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { - let merc = me.getMerc(); - - while (unit.attackable) { - if (Misc.townCheck()) { - if (!unit || !copyUnit(unit).x) { - unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); - } - } - - if (!unit) return Attack.Result.SUCCESS; - - if (Town.needMerc()) { - if (Config.MercWatch && mercRevive++ < 1) { - Town.visitTown(); - } else { - return Attack.Result.CANTATTACK; - } - - (merc === undefined || !merc) && (merc = me.getMerc()); - } - - if (!!merc && getDistance(merc, unit) > 5) { - Pather.moveToUnit(unit); - - let spot = Attack.findSafeSpot(unit, 10, 5, 9); - !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); - } - - let closeMob = Attack.getNearestMonster({skipGid: gid}); - - if (!!closeMob) { - let findSkill = this.decideSkill(closeMob); - (this.doCast(closeMob, findSkill.timed, findSkill.untimed) === Attack.Result.SUCCESS) || (Skill.canUse(sdk.skills.Decoy) && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, unit)); - } - } - - return Attack.Result.SUCCESS; - } - - return result; - }, - - afterAttack: function () { - Precast.doPrecast(false); - - let needRepair = (Town.needRepair() || []); - - // Repair check, mainly to restock arrows - needRepair.length > 0 && Town.visitTown(true); - - this.lightFuryTick = 0; - }, - - // Returns: 0 - fail, 1 - success, 2 - no valid attack skills - doCast: function (unit, timedSkill = -1, untimedSkill = -1) { - // No valid skills can be found - if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; - - // Arrow/bolt check - if (this.bowCheck) { - switch (true) { - case this.bowCheck === "bow" && !me.getItem("aqv", sdk.items.mode.Equipped): - case this.bowCheck === "crossbow" && !me.getItem("cqv", sdk.items.mode.Equipped): - console.log("Bow check"); - Town.visitTown(); - - break; - } - } - - let walk; - - if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { - switch (timedSkill) { - case sdk.skills.LightningFury: - if (!this.lightFuryTick || getTickCount() - this.lightFuryTick > Config.LightningFuryDelay * 1000) { - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - if (!unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit)) { - this.lightFuryTick = getTickCount(); - } - - return Attack.Result.SUCCESS; - } - - break; - default: - if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, unit.classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(timedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); - - return Attack.Result.SUCCESS; - } - } - - if (untimedSkill > -1) { - if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, unit.classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(untimedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); - - return Attack.Result.SUCCESS; - } - - Misc.poll(() => !me.skillDelay, 1000, 40); - - // Wait for Lightning Fury timeout - while (timedSkill === sdk.skills.LightningFury && this.lightFuryTick && getTickCount() - this.lightFuryTick < Config.LightningFuryDelay * 1000) { - delay(40); - } - - return Attack.Result.SUCCESS; - } -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Assassin.js b/d2bs/kolbot/libs/common/Attacks/Assassin.js deleted file mode 100644 index 2d216a339..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Assassin.js +++ /dev/null @@ -1,256 +0,0 @@ -/** -* @filename Assassin.js -* @author kolton, theBGuy -* @desc Assassin attack sequence -* -*/ - -const ClassAttack = { - lastTrapPos: {}, - trapRange: 20, - - doAttack: function (unit, preattack) { - if (!unit) return Attack.Result.SUCCESS; - let gid = unit.gid; - - if (Config.MercWatch && Town.needMerc()) { - print("mercwatch"); - - if (Town.visitTown()) { - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - return Attack.Result.SUCCESS; // lost reference to the mob we were attacking - } - } - } - - if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - let mercRevive = 0; - let timedSkill = -1; - let untimedSkill = -1; - let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - let classid = unit.classid; - - // Cloak of Shadows (Aggressive) - can't be cast again until previous one runs out and next to useless if cast in precast sequence (won't blind anyone) - if (Config.AggressiveCloak && Skill.canUse(sdk.skills.CloakofShadows) && !me.skillDelay && !me.getState(sdk.states.CloakofShadows)) { - if (unit.distance < 20) { - Skill.cast(sdk.skills.CloakofShadows, sdk.skills.hand.Right); - } else if (!Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - let checkTraps = this.checkTraps(unit); - - if (checkTraps) { - if (unit.distance > this.trapRange || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, this.trapRange, sdk.collision.Ranged) - || (checkCollision(me, unit, sdk.collision.BlockWall) && (getCollision(me.area, unit.x, unit.y) & sdk.collision.BlockWall))) { - return Attack.Result.FAILED; - } - } - - this.placeTraps(unit, checkTraps); - } - - // Cloak of Shadows (Defensive; default) - can't be cast again until previous one runs out and next to useless if cast in precast sequence (won't blind anyone) - if (!Config.AggressiveCloak && Skill.canUse(sdk.skills.CloakofShadows) && unit.distance < 20 && !me.skillDelay && !me.getState(sdk.states.CloakofShadows)) { - Skill.cast(sdk.skills.CloakofShadows, sdk.skills.hand.Right); - } - - // Get timed skill - let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - timedSkill = checkSkill; - } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { - timedSkill = Config.AttackSkill[5]; - } - - // Get untimed skill - checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - untimedSkill = checkSkill; - } else if (Config.AttackSkill[6] > -1 && Attack.checkResist(unit, Config.AttackSkill[6]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { - untimedSkill = Config.AttackSkill[6]; - } - - // Low mana timed skill - if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(timedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) { - timedSkill = Config.LowManaSkill[0]; - } - - // Low mana untimed skill - if (Config.LowManaSkill[1] > -1 && Skill.getManaCost(untimedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[1])) { - untimedSkill = Config.LowManaSkill[1]; - } - - let result = this.doCast(unit, timedSkill, untimedSkill); - - if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { - let merc = me.getMerc(); - - while (unit.attackable) { - if (Misc.townCheck()) { - if (!unit || !copyUnit(unit).x) { - unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); - } - } - - if (!unit) return Attack.Result.SUCCESS; - - if (Town.needMerc()) { - if (Config.MercWatch && mercRevive++ < 1) { - Town.visitTown(); - } else { - return Attack.Result.CANTATTACK; - } - - (merc === undefined || !merc) && (merc = me.getMerc()); - } - - if (!!merc && getDistance(merc, unit) > 5) { - Pather.moveToUnit(unit); - - let spot = Attack.findSafeSpot(unit, 10, 5, 9); - !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); - } - - let closeMob = Attack.getNearestMonster({skipGid: gid}); - !!closeMob && this.doCast(closeMob, timedSkill, untimedSkill); - } - - return Attack.Result.SUCCESS; - } - - return result; - }, - - afterAttack: function () { - Precast.doPrecast(false); - }, - - // Returns: 0 - fail, 1 - success, 2 - no valid attack skills - doCast: function (unit, timedSkill = -1, untimedSkill = -1) { - // No valid skills can be found - if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; - // unit became invalidated - if (!unit || !unit.attackable) return Attack.Result.SUCCESS; - - let walk; - let classid = unit.classid; - - if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { - switch (timedSkill) { - case sdk.skills.Whirlwind: - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.BlockWall)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.BlockWall)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Attack.whirlwind(unit); - - return Attack.Result.SUCCESS; - default: - if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(timedSkill) < 4 && getDistance(me, unit) < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); - - return Attack.Result.SUCCESS; - } - } - - if (untimedSkill > -1) { - if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(untimedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); - - return Attack.Result.SUCCESS; - } - - Misc.poll(() => !me.skillDelay, 1000, 40); - - return Attack.Result.SUCCESS; - }, - - checkTraps: function (unit) { - if (!Config.UseTraps || !unit) return false; - - // getDistance crashes when using an object with x, y props, that's why it's unit.x, unit.y and not unit - // is this still a thing ^^? todo: test it - if (me.getMinionCount(sdk.summons.type.AssassinTrap) === 0 || !this.lastTrapPos.hasOwnProperty("x") - || getDistance(unit.x, unit.y, this.lastTrapPos.x, this.lastTrapPos.y) > 15) { - return 5; - } - - return 5 - me.getMinionCount(sdk.summons.type.AssassinTrap); - }, - - // todo - either import soloplays immune to trap check or add config option for immune to traps - // since this is the base file probably better to leave the option available rather than hard code it - // check if unit is still attackable after each cast? - placeTraps: function (unit, amount = 5) { - let traps = 0; - this.lastTrapPos = {x: unit.x, y: unit.y}; - - for (let i = -1; i <= 1; i += 1) { - for (let j = -1; j <= 1; j += 1) { - // used for X formation - if (Math.abs(i) === Math.abs(j)) { - // unit can be an object with x, y props too, that's why having "mode" prop is checked - if (traps >= amount || (unit.hasOwnProperty("mode") && unit.dead)) return true; - - // Duriel, Mephisto, Diablo, Baal, other players - why not andy? - if ((unit.hasOwnProperty("classid") && [sdk.monsters.Duriel, sdk.monsters.Mephisto, sdk.monsters.Diablo, sdk.monsters.Baal].includes(unit.classid)) - || (unit.hasOwnProperty("type") && unit.isPlayer)) { - if (traps >= Config.BossTraps.length) return true; - - Skill.cast(Config.BossTraps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j); - } else { - if (traps >= Config.Traps.length) return true; - - Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j); - } - - traps += 1; - } - } - } - - return true; - }, -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Barbarian.js b/d2bs/kolbot/libs/common/Attacks/Barbarian.js deleted file mode 100644 index c8305aa14..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Barbarian.js +++ /dev/null @@ -1,210 +0,0 @@ -/** -* @filename Barbarian.js -* @author kolton, theBGuy -* @desc Barbarian attack sequence -* -*/ - -// todo - add howl - -const ClassAttack = { - doAttack: function (unit, preattack = false) { - if (!unit) return Attack.Result.SUCCESS; - let gid = unit.gid; - let needRepair = Town.needRepair(); - - if ((Config.MercWatch && Town.needMerc()) || needRepair.length > 0) { - print("towncheck"); - - if (Town.visitTown(!!needRepair.length)) { - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - return Attack.Result.SUCCESS; // lost reference to the mob we were attacking - } - } - } - - if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Attack.getSkillElement(Config.AttackSkill[0])) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - let attackSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; - - if (!Attack.checkResist(unit, attackSkill)) { - attackSkill = -1; - - if (Config.AttackSkill[index + 1] > -1 && Attack.checkResist(unit, Config.AttackSkill[index + 1])) { - attackSkill = Config.AttackSkill[index + 1]; - } - } - - // Low mana skill - if (Skill.getManaCost(attackSkill) > me.mp && Config.LowManaSkill[0] > -1 && Attack.checkResist(unit, Config.LowManaSkill[0])) { - attackSkill = Config.LowManaSkill[0]; - } - - // low weapon-quantity -> use secondary skill if we can - if (attackSkill === sdk.skills.DoubleThrow && (me.getWeaponQuantity() <= 3 || me.getWeaponQuantity(sdk.body.LeftArm) <= 3) - && Skill.canUse(Config.AttackSkill[index + 1]) && Attack.checkResist(unit, Config.AttackSkill[index + 1])) { - attackSkill = Config.AttackSkill[index + 1]; - } - - // Telestomp with barb is pointless - return this.doCast(unit, attackSkill); - }, - - afterAttack: function (pickit = true) { - Precast.doPrecast(false); - - let needRepair = (Town.needRepair() || []); - - // Repair check - needRepair.length > 0 && Town.visitTown(true); - pickit && this.findItem(me.inArea(sdk.areas.Travincal) ? 60 : 20); - }, - - doCast: function (unit, attackSkill = -1) { - if (attackSkill < 0) return Attack.Result.CANTATTACK; - // check if unit became invalidated - if (!unit || !unit.attackable) return Attack.Result.SUCCESS; - - switch (attackSkill) { - case sdk.skills.Whirlwind: - if (unit.distance > Skill.getRange(attackSkill) || checkCollision(me, unit, sdk.collision.BlockWall)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.BlockWall, 2)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Attack.whirlwind(unit); - - return Attack.Result.SUCCESS; - default: - if (Skill.getRange(attackSkill) < 4 && !Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(attackSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - let walk = Skill.getRange(attackSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); - - return Attack.Result.SUCCESS; - } - }, - - checkCloseMonsters: function (range = 10) { - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.distance <= range && monster.attackable && !checkCollision(me, monster, sdk.collision.Ranged) - && (Attack.checkResist(monster, Attack.getSkillElement(Config.AttackSkill[monster.isSpecial ? 1 : 3])) - || (Config.AttackSkill[3] > -1 && Attack.checkResist(monster, Attack.getSkillElement(Config.AttackSkill[3]))))) { - return true; - } - } while (monster.getNext()); - } - - return false; - }, - - findItem: function (range = 10) { - if (!Skill.canUse(sdk.skills.FindItem)) return false; - - let retry = false; - let corpseList = []; - let orgX = me.x; - let orgY = me.y; - - MainLoop: - for (let i = 0; i < 3; i += 1) { - let corpse = Game.getMonster(); - - if (corpse) { - do { - if (corpse.dead && getDistance(corpse, orgX, orgY) <= range && this.checkCorpse(corpse)) { - corpseList.push(copyUnit(corpse)); - } - } while (corpse.getNext()); - } - - while (corpseList.length > 0) { - if (this.checkCloseMonsters(5)) { - Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot()); - Attack.clear(10, false, false, false, false); - retry = true; - - break MainLoop; - } - - corpseList.sort(Sort.units); - corpse = corpseList.shift(); - - if (this.checkCorpse(corpse)) { - (corpse.distance > 30 || checkCollision(me, corpse, sdk.collision.BlockWall)) && Pather.moveToUnit(corpse); - Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot() ^ 1); - - CorpseLoop: - for (let j = 0; j < 3; j += 1) { - Skill.cast(sdk.skills.FindItem, sdk.skills.hand.Right, corpse); - - let tick = getTickCount(); - - while (getTickCount() - tick < 1000) { - if (corpse.getState(sdk.states.CorpseNoSelect)) { - Pickit.fastPick(); - - break CorpseLoop; - } - - delay(10); - } - } - } - } - } - - if (retry) { - return this.findItem(me.inArea(sdk.areas.Travincal) ? 60 : 20); - } - - Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot()); - Pickit.pickItems(); - - return true; - }, - - checkCorpse: function (unit) { - if (!unit || unit.mode !== sdk.monsters.mode.Death && unit.mode !== sdk.monsters.mode.Dead) return false; - if ([sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3].indexOf(unit.classid) === -1 - && unit.spectype === sdk.monsters.spectype.All) { - return false; - } - - // monstats2 doesn't contain guest monsters info. sigh.. - if (unit.classid <= sdk.monsters.BurningDeadArcher2 && !getBaseStat("monstats2", unit.classid, "corpseSel")) { - return false; - } - - let states = [ - sdk.states.FrozenSolid, sdk.states.Revive, sdk.states.Redeemed, - sdk.states.CorpseNoDraw, sdk.states.Shatter, sdk.states.RestInPeace, sdk.states.CorpseNoSelect - ]; - - return !!(unit.distance <= 25 && !checkCollision(me, unit, sdk.collision.Ranged) && states.every(state => !unit.getState(state))); - } -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Druid.js b/d2bs/kolbot/libs/common/Attacks/Druid.js deleted file mode 100644 index 2373c058f..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Druid.js +++ /dev/null @@ -1,185 +0,0 @@ -/** -* @filename Druid.js -* @author kolton, theBGuy -* @desc Druid attack sequence -* -*/ - -const ClassAttack = { - doAttack: function (unit, preattack = false) { - if (!unit) return Attack.Result.SUCCESS; - let gid = unit.gid; - - if (Config.MercWatch && Town.needMerc()) { - print("mercwatch"); - - if (Town.visitTown()) { - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - return Attack.Result.SUCCESS; // lost reference to the mob we were attacking - } - } - } - - // Rebuff Hurricane - Skill.canUse(sdk.skills.Hurricane) && !me.getState(sdk.states.Hurricane) && Skill.cast(sdk.skills.Hurricane, sdk.skills.hand.Right); - // Rebuff Cyclone Armor - Skill.canUse(sdk.skills.CycloneArmor) && !me.getState(sdk.states.CycloneArmor) && Skill.cast(sdk.skills.CycloneArmor, sdk.skills.hand.Right); - - if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - let mercRevive = 0; - let timedSkill = -1; - let untimedSkill = -1; - let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - let classid = unit.classid; - - // Get timed skill - let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - timedSkill = checkSkill; - } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { - timedSkill = Config.AttackSkill[5]; - } - - // Get untimed skill - checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - untimedSkill = checkSkill; - } else if (Config.AttackSkill[6] > -1 && Attack.checkResist(unit, Config.AttackSkill[6]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { - untimedSkill = Config.AttackSkill[6]; - } - - // Low mana timed skill - if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(timedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) { - timedSkill = Config.LowManaSkill[0]; - } - - // Low mana untimed skill - if (Config.LowManaSkill[1] > -1 && Skill.getManaCost(untimedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[1])) { - untimedSkill = Config.LowManaSkill[1]; - } - - let result = this.doCast(unit, timedSkill, untimedSkill); - - if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { - let merc = me.getMerc(); - - while (unit.attackable) { - if (Misc.townCheck()) { - if (!unit || !copyUnit(unit).x) { - unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); - } - } - - if (!unit) return Attack.Result.SUCCESS; - - if (Town.needMerc()) { - if (Config.MercWatch && mercRevive++ < 1) { - Town.visitTown(); - } else { - return Attack.Result.CANTATTACK; - } - - (merc === undefined || !merc) && (merc = me.getMerc()); - } - - if (!!merc && getDistance(merc, unit) > 5) { - Pather.moveToUnit(unit); - - let spot = Attack.findSafeSpot(unit, 10, 5, 9); - !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); - } - - let closeMob = Attack.getNearestMonster({skipGid: gid}); - !!closeMob && this.doCast(closeMob, timedSkill, untimedSkill); - } - - return Attack.Result.SUCCESS; - } - - return result; - }, - - afterAttack: function () { - Precast.doPrecast(false); - }, - - // Returns: 0 - fail, 1 - success, 2 - no valid attack skills - doCast: function (unit, timedSkill = -1, untimedSkill = -1) { - // No valid skills can be found - if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; - // unit became invalidated - if (!unit || !unit.attackable) return Attack.Result.SUCCESS; - - let walk; - let classid = unit.classid; - - if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { - switch (timedSkill) { - case sdk.skills.Tornado: - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - // Randomized x coord changes tornado path and prevents constant missing - !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit.x + rand(-2, 2), unit.y); - - return Attack.Result.SUCCESS; - default: - if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(timedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); - - return Attack.Result.SUCCESS; - } - } - - if (untimedSkill > -1) { - if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(untimedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); - - return Attack.Result.SUCCESS; - } - - Misc.poll(() => !me.skillDelay, 1000, 40); - - return Attack.Result.SUCCESS; - } -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Necromancer.js b/d2bs/kolbot/libs/common/Attacks/Necromancer.js deleted file mode 100644 index 938edefb3..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Necromancer.js +++ /dev/null @@ -1,509 +0,0 @@ -/** -* @filename Necromancer.js -* @author kolton, theBGuy -* @desc Necromancer attack sequence -* -*/ - -const ClassAttack = { - novaTick: 0, - maxSkeletons: 0, - maxMages: 0, - maxRevives: 0, - - setArmySize: function () { - this.maxSkeletons = Config.Skeletons === "max" ? Skill.getMaxSummonCount(sdk.skills.RaiseSkeleton) : Config.Skeletons; - this.maxMages = Config.SkeletonMages === "max" ? Skill.getMaxSummonCount(sdk.skills.RaiseSkeletalMage) : Config.SkeletonMages; - this.maxRevives = Config.Revives === "max" ? Skill.getMaxSummonCount(sdk.skills.Revive) : Config.Revives; - }, - - // Returns: true - doesn't use summons or has all he can summon, false - not full of summons yet - isArmyFull: function () { - // This necro doesn't summon anything so assume he's full - if (Config.Skeletons + Config.SkeletonMages + Config.Revives === 0) { - return true; - } - - // Make sure we have a current count of summons needed - this.setArmySize(); - - // See if we're at full army count - if ((me.getMinionCount(sdk.summons.type.Skeleton) < this.maxSkeletons) - && (me.getMinionCount(sdk.summons.type.SkeletonMage) < this.maxMages) - && (me.getMinionCount(sdk.summons.type.Revive) < this.maxRevives)) { - return false; - } - - // If we got this far this necro has all the summons he needs - return true; - }, - - canCurse: function (unit, curseID) { - if (unit === undefined || unit.dead || !Skill.canUse(curseID)) return false; - - let state = (() => { - switch (curseID) { - case sdk.skills.AmplifyDamage: - return sdk.states.AmplifyDamage; - case sdk.skills.DimVision: - // dim doesn't work on oblivion knights - if ([sdk.monsters.OblivionKnight1, sdk.monsters.OblivionKnight2, sdk.monsters.OblivionKnight3].includes(unit.classid)) return false; - return sdk.states.DimVision; - case sdk.skills.Weaken: - return sdk.states.Weaken; - case sdk.skills.IronMaiden: - return sdk.states.IronMaiden; - case sdk.skills.Terror: - return unit.scareable ? sdk.states.Terror : false; - case sdk.skills.Confuse: - // doens't work on specials - return unit.scareable ? sdk.states.Confuse : false; - case sdk.skills.LifeTap: - return sdk.states.LifeTap; - case sdk.skills.Attract: - // doens't work on specials - return unit.scareable ? sdk.states.Attract : false; - case sdk.skills.Decrepify: - return sdk.states.Decrepify; - case sdk.skills.LowerResist: - return sdk.states.LowerResist; - default: - console.warn("(ÿc9canCurse) :: ÿc1Invalid Curse ID: " + curseID); - - return false; - } - })(); - - return state ? !unit.getState(state) : false; - }, - - getCustomCurse: function (unit) { - if (Config.CustomCurse.length <= 0) return false; - - let curse = Config.CustomCurse - .findIndex(function (unitID) { - if ((typeof unitID[0] === "number" && unit.classid && unit.classid === unitID[0]) - || (typeof unitID[0] === "string" && unit.name && unit.name.toLowerCase() === unitID[0].toLowerCase())) { - return true; - } - return false; - }); - if (curse > -1) { - // format [id, curse, spectype] - if (Config.CustomCurse[curse].length === 3) { - return ((unit.spectype & Config.CustomCurse[curse][2]) ? Config.CustomCurse[curse][1] : false); - } else { - return Config.CustomCurse[curse][1]; - } - } - - return false; - }, - - doAttack: function (unit, preattack = false) { - if (!unit || unit.dead) return Attack.Result.SUCCESS; - - let mercRevive = 0; - let gid = unit.gid; - let classid = unit.classid; - let [timedSkill, untimedSkill, customCurse] = [-1, -1, -1]; - const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - - if (Config.MercWatch && Town.needMerc()) { - print("mercwatch"); - - if (Town.visitTown()) { - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - return Attack.Result.SUCCESS; // lost reference to the mob we were attacking - } - } - } - - if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - // only continue if we can actually curse the unit otherwise its a waste of time - if (unit.curseable) { - customCurse = this.getCustomCurse(unit); - - if (customCurse && this.canCurse(unit, customCurse)) { - if (unit.distance > 25 || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, 25, sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(customCurse, sdk.skills.hand.Right, unit); - - return Attack.Result.SUCCESS; - } else if (!customCurse) { - if (Config.Curse[0] > 0 && unit.isSpecial && this.canCurse(unit, Config.Curse[0])) { - if (unit.distance > 25 || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, 25, sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.Curse[0], sdk.skills.hand.Right, unit); - - return Attack.Result.SUCCESS; - } - - if (Config.Curse[1] > 0 && !unit.isSpecial && this.canCurse(unit, Config.Curse[1])) { - if (unit.distance > 25 || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, 25, sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.Curse[1], sdk.skills.hand.Right, unit); - - return Attack.Result.SUCCESS; - } - } - } - - // Get timed skill - let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - timedSkill = checkSkill; - } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { - timedSkill = Config.AttackSkill[5]; - } - - // Get untimed skill - checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - untimedSkill = checkSkill; - } else if (Config.AttackSkill[6] > -1 && Attack.checkResist(unit, Config.AttackSkill[6]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { - untimedSkill = Config.AttackSkill[6]; - } - - // Low mana timed skill - if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(timedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) { - timedSkill = Config.LowManaSkill[0]; - } - - // Low mana untimed skill - if (Config.LowManaSkill[1] > -1 && Skill.getManaCost(untimedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[1])) { - untimedSkill = Config.LowManaSkill[1]; - } - - let result = this.doCast(unit, timedSkill, untimedSkill); - - if (result === 1) { - Config.ActiveSummon && this.raiseArmy(); - this.explodeCorpses(unit); - } else if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { - let merc = me.getMerc(); - - while (unit.attackable) { - if (Misc.townCheck()) { - if (!unit || !copyUnit(unit).x) { - unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); - } - } - - if (!unit) return Attack.Result.SUCCESS; - - if (Town.needMerc()) { - if (Config.MercWatch && mercRevive++ < 1) { - Town.visitTown(); - } else { - return Attack.Result.CANTATTACK; - } - - (merc === undefined || !merc) && (merc = me.getMerc()); - } - - if (!!merc && getDistance(merc, unit) > 5) { - Pather.moveToUnit(unit); - - let spot = Attack.findSafeSpot(unit, 10, 5, 9); - !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); - } - - Config.ActiveSummon && this.raiseArmy(); - this.explodeCorpses(unit); - let closeMob = Attack.getNearestMonster({skipGid: gid}); - !!closeMob && this.doCast(closeMob, timedSkill, untimedSkill); - } - - return Attack.Result.SUCCESS; - } - - return result; - }, - - afterAttack: function () { - Precast.doPrecast(false); - this.raiseArmy(); - this.novaTick = 0; - }, - - // Returns: 0 - fail, 1 - success, 2 - no valid attack skills - doCast: function (unit, timedSkill = -1, untimedSkill = -1) { - // No valid skills can be found - if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; - // unit became invalidated - if (!unit || !unit.attackable) return Attack.Result.SUCCESS; - - let walk; - let classid = unit.classid; - - // Check for bodies to exploit for CorpseExplosion before committing to an attack for non-summoner type necros - this.isArmyFull() && this.checkCorpseNearMonster(unit) && this.explodeCorpses(unit); - - if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { - switch (timedSkill) { - case sdk.skills.PoisonNova: - if (!this.novaTick || getTickCount() - this.novaTick > Config.PoisonNovaDelay * 1000) { - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - if (!unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit)) { - this.novaTick = getTickCount(); - } - } - - break; - case sdk.skills.Summoner: // Pure Summoner - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - delay(300); - - break; - default: - if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) return Attack.Result.FAILED; - - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - let walk = Skill.getRange(timedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); - - break; - } - } - - if (untimedSkill > -1) { - if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) return Attack.Result.FAILED; - - if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(untimedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); - - return Attack.Result.SUCCESS; - } - - Misc.poll(() => !me.skillDelay, 1000, 40); - - // Delay for Poison Nova - while (timedSkill === sdk.skills.PoisonNova && this.novaTick && getTickCount() - this.novaTick < Config.PoisonNovaDelay * 1000) { - delay(40); - } - - return Attack.Result.SUCCESS; - }, - - raiseArmy: function (range = 25) { - let tick, count; - - this.setArmySize(); - - for (let i = 0; i < 3; i += 1) { - let corpse = Game.getMonster(-1, sdk.monsters.mode.Dead); - let corpseList = []; - - if (corpse) { - do { - // within casting distance - if (corpse.distance <= range && this.checkCorpse(corpse)) { - corpseList.push(copyUnit(corpse)); - } - } while (corpse.getNext()); - } - - while (corpseList.length > 0) { - corpse = corpseList.shift(); - - // should probably have a way to priortize which ones we summon first - if (me.getMinionCount(sdk.summons.type.Skeleton) < this.maxSkeletons) { - if (!Skill.cast(sdk.skills.RaiseSkeleton, sdk.skills.hand.Right, corpse)) { - return false; - } - - count = me.getMinionCount(sdk.summons.type.Skeleton); - tick = getTickCount(); - - while (getTickCount() - tick < 200) { - if (me.getMinionCount(sdk.summons.type.Skeleton) > count) { - break; - } - - delay(10); - } - } else if (me.getMinionCount(sdk.summons.type.SkeletonMage) < this.maxMages) { - if (!Skill.cast(sdk.skills.RaiseSkeletalMage, sdk.skills.hand.Right, corpse)) { - return false; - } - - count = me.getMinionCount(sdk.summons.type.SkeletonMage); - tick = getTickCount(); - - while (getTickCount() - tick < 200) { - if (me.getMinionCount(sdk.summons.type.SkeletonMage) > count) { - break; - } - - delay(10); - } - } else if (me.getMinionCount(sdk.summons.type.Revive) < this.maxRevives) { - if (this.checkCorpse(corpse, true)) { - print("Reviving " + corpse.name); - - if (!Skill.cast(sdk.skills.Revive, sdk.skills.hand.Right, corpse)) { - return false; - } - - count = me.getMinionCount(sdk.summons.type.Revive); - tick = getTickCount(); - - while (getTickCount() - tick < 200) { - if (me.getMinionCount(sdk.summons.type.Revive) > count) { - break; - } - - delay(10); - } - } - } else { - return true; - } - } - } - - return true; - }, - - explodeCorpses: function (unit) { - if (Config.ExplodeCorpses === 0 || unit.dead) return false; - - let corpseList = []; - let range = Math.floor((me.getSkill(Config.ExplodeCorpses, sdk.skills.subindex.SoftPoints) + 7) / 3); - let corpse = Game.getMonster(-1, sdk.monsters.mode.Dead); - - if (corpse) { - do { - if (getDistance(unit, corpse) <= range && this.checkCorpse(corpse)) { - corpseList.push(copyUnit(corpse)); - } - } while (corpse.getNext()); - - // Shuffle the corpseList so if running multiple necrobots they explode separate corpses not the same ones - corpseList.length > 1 && (corpseList = corpseList.shuffle()); - - if (this.isArmyFull()) { - // We don't need corpses as we are not a Summoner Necro, Spam CE till monster dies or we run out of bodies. - do { - corpse = corpseList.shift(); - - if (corpse) { - if (!unit.dead && this.checkCorpse(corpse) && getDistance(corpse, unit) <= range) { - // Added corpse ID so I can see when it blows another monster with the same ClassID and Name - me.overhead("Exploding: " + corpse.classid + " " + corpse.name + " id:" + corpse.gid); - - if (Skill.cast(Config.ExplodeCorpses, sdk.skills.hand.Right, corpse)) { - delay(me.ping + 1); - } - } - } - } while (corpseList.length > 0); - } else { - // We are a Summoner Necro, we should conserve corpses, only blow 2 at a time so we can check for needed re-summons. - for (let i = 0; i <= 1; i += 1) { - if (corpseList.length > 0) { - corpse = corpseList.shift(); - - if (corpse) { - me.overhead("Exploding: " + corpse.classid + " " + corpse.name); - - if (Skill.cast(Config.ExplodeCorpses, sdk.skills.hand.Right, corpse)) { - delay(200); - } - } - } else { - break; - } - } - } - } - - return true; - }, - - checkCorpseNearMonster: function (monster, range) { - let corpse = Game.getMonster(-1, sdk.monsters.mode.Dead); - - // Assume CorpseExplosion if no range specified - range === undefined && (range = Math.floor((me.getSkill(Config.ExplodeCorpses, sdk.skills.subindex.SoftPoints) + 7) / 3)); - - if (corpse) { - do { - if (getDistance(corpse, monster) <= range) { - return true; - } - } while (corpse.getNext()); - } - - return false; - }, - - checkCorpse: function (unit, revive = false) { - if (!unit || unit.mode !== sdk.monsters.mode.Dead) return false; - - let baseId = getBaseStat("monstats", unit.classid, "baseid"), badList = [312, 571]; - let states = [ - sdk.states.FrozenSolid, sdk.states.Revive, sdk.states.Redeemed, - sdk.states.CorpseNoDraw, sdk.states.Shatter, sdk.states.RestInPeace, sdk.states.CorpseNoSelect - ]; - - if (revive && (unit.isSpecial || badList.includes(baseId) || (Config.ReviveUnstackable && getBaseStat("monstats2", baseId, "sizex") === 3))) { - return false; - } - - if (!getBaseStat("monstats2", baseId, revive ? "revive" : "corpseSel")) return false; - - return !!(unit.distance <= 25 && !checkCollision(me, unit, sdk.collision.Ranged) && states.every(state => !unit.getState(state))); - } -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Paladin.js b/d2bs/kolbot/libs/common/Attacks/Paladin.js deleted file mode 100644 index 042d844fd..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Paladin.js +++ /dev/null @@ -1,343 +0,0 @@ -/** -* @filename Paladin.js -* @author kolton, theBGuy -* @desc Paladin attack sequence -* -*/ - -const ClassAttack = { - attackAuras: [sdk.skills.HolyFire, sdk.skills.HolyFreeze, sdk.skills.HolyShock], - - doAttack: function (unit, preattack) { - if (!unit) return Attack.Result.SUCCESS; - let gid = unit.gid; - - if (Config.MercWatch && Town.needMerc()) { - print("mercwatch"); - - if (Town.visitTown()) { - // lost reference to the mob we were attacking - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - return Attack.Result.SUCCESS; - } - } - } - - if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - let mercRevive = 0; - let [attackSkill, aura] = [-1, -1]; - const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - - if (Attack.getCustomAttack(unit)) { - [attackSkill, aura] = Attack.getCustomAttack(unit); - } else { - attackSkill = Config.AttackSkill[index]; - aura = Config.AttackSkill[index + 1]; - } - - // Classic auradin check - if (this.attackAuras.includes(aura)) { - // Monster immune to primary aura - if (!Attack.checkResist(unit, aura)) { - // Reset skills - [attackSkill, aura] = [-1, -1]; - - // Set to secondary if not immune, check if using secondary attack aura if not check main skill for immunity - if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, (this.attackAuras.includes(Config.AttackSkill[6]) ? Config.AttackSkill[6] : Config.AttackSkill[5]))) { - attackSkill = Config.AttackSkill[5]; - aura = Config.AttackSkill[6]; - } - } - } else { - // Monster immune to primary skill - if (!Attack.checkResist(unit, attackSkill)) { - // Reset skills - [attackSkill, aura] = [-1, -1]; - - // Set to secondary if not immune - if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5])) { - attackSkill = Config.AttackSkill[5]; - aura = Config.AttackSkill[6]; - } - } - } - - // Low mana skill - if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(attackSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) { - [attackSkill, aura] = Config.LowManaSkill; - } - - let result = this.doCast(unit, attackSkill, aura); - - if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { - let merc = me.getMerc(); - - while (unit.attackable) { - if (Misc.townCheck()) { - if (!unit || !copyUnit(unit).x) { - unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); - } - } - - if (!unit) return Attack.Result.SUCCESS; - - if (Town.needMerc()) { - if (Config.MercWatch && mercRevive++ < 1) { - Town.visitTown(); - } else { - return Attack.Result.CANTATTACK; - } - - (merc === undefined || !merc) && (merc = me.getMerc()); - } - - if (!!merc && getDistance(merc, unit) > 5) { - Pather.moveToUnit(unit); - - let spot = Attack.findSafeSpot(unit, 10, 5, 9); - !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); - } - - let closeMob = Attack.getNearestMonster({skipGid: gid}); - !!closeMob && this.doCast(closeMob, attackSkill, aura); - } - - return Attack.Result.SUCCESS; - } - - return result; - }, - - afterAttack: function () { - Precast.doPrecast(false); - - // only proceed with other checks if we can use redemption and the config values aren't 0 - if (Skill.canUse(sdk.skills.Redemption) && Config.Redemption.some(v => v > 0)) { - if ((me.hpPercent < Config.Redemption[0] || me.mpPercent < Config.Redemption[1]) - && Attack.checkNearCorpses(me) > 2 && Skill.setSkill(sdk.skills.Redemption, sdk.skills.hand.Right)) { - delay(1500); - } - } - }, - - doCast: function (unit, attackSkill = -1, aura = -1) { - if (attackSkill < 0) return Attack.Result.CANTATTACK; - // unit became invalidated - if (!unit || !unit.attackable) return Attack.Result.SUCCESS; - - switch (attackSkill) { - case sdk.skills.BlessedHammer: - // todo: add doll avoid to other classes - if (Config.AvoidDolls && unit.isDoll) { - this.dollAvoid(unit); - aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); - Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); - - return Attack.Result.SUCCESS; - } - - // todo: maybe if we are currently surrounded and no tele to just attack from where we are - // hammers cut a pretty wide arc so likely this would be enough to clear our path - if (!this.getHammerPosition(unit)) { - // Fallback to secondary skill if it exists - if (Config.AttackSkill[5] > -1 && Config.AttackSkill[5] !== sdk.skills.BlessedHammer && Attack.checkResist(unit, Config.AttackSkill[5])) { - return this.doCast(unit, Config.AttackSkill[5], Config.AttackSkill[6]); - } - - return Attack.Result.FAILED; - } - - if (unit.distance > 9 || !unit.attackable) return Attack.Result.SUCCESS; - - aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); - - for (let i = 0; i < 3; i += 1) { - Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); - - if (!unit.attackable || unit.distance > 9 || unit.isPlayer) { - break; - } - } - - return Attack.Result.SUCCESS; - case sdk.skills.HolyBolt: - if (unit.distance > Skill.getRange(attackSkill) + 3 || CollMap.checkColl(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - CollMap.reset(); - - if (unit.distance > Skill.getRange(attackSkill) || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.FriendlyRanged, true)) { - return Attack.Result.FAILED; - } - } - - if (!unit.dead) { - aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); - Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); - } - - return Attack.Result.SUCCESS; - case sdk.skills.FistoftheHeavens: - if (!me.skillDelay) { - if (unit.distance > Skill.getRange(attackSkill) || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.FriendlyRanged, true)) { - return Attack.Result.FAILED; - } - } - - if (!unit.dead) { - aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); - Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); - - return Attack.Result.SUCCESS; - } - } - - break; - case sdk.skills.Attack: - case sdk.skills.Sacrifice: - case sdk.skills.Zeal: - case sdk.skills.Vengeance: - if (!Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) { - return Attack.Result.FAILED; - } - - // 3591 - wall/line of sight/ranged/items/objects/closeddoor - if (unit.distance > 3 || checkCollision(me, unit, sdk.collision.WallOrRanged)) { - if (!Attack.getIntoPosition(unit, 3, sdk.collision.WallOrRanged, true)) { - return Attack.Result.FAILED; - } - } - - if (unit.attackable) { - aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); - return (Skill.cast(attackSkill, sdk.skills.hand.LeftNoShift, unit) ? Attack.Result.SUCCESS : Attack.Result.FAILED); - } - - break; - default: - if (Skill.getRange(attackSkill) < 4 && !Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) return Attack.Result.FAILED; - - if (unit.distance > Skill.getRange(attackSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - let walk = (attackSkill !== sdk.skills.Smite && Skill.getRange(attackSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall)); - - // walk short distances instead of tele for melee attacks. teleport if failed to walk - if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged, walk)) return Attack.Result.FAILED; - } - - if (!unit.dead) { - aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); - Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); - } - - return Attack.Result.SUCCESS; - } - - Misc.poll(() => !me.skillDelay, 1000, 40); - - return Attack.Result.SUCCESS; - }, - - dollAvoid: function (unit) { - let distance = 14; - - for (let i = 0; i < 2 * Math.PI; i += Math.PI / 6) { - let cx = Math.round(Math.cos(i) * distance); - let cy = Math.round(Math.sin(i) * distance); - - if (Attack.validSpot(unit.x + cx, unit.y + cy)) { - // don't clear while trying to reposition - return Pather.moveToEx(unit.x + cx, unit.y + cy, {clearSettings: {allowClearing: false}}); - } - } - - return false; - }, - - getHammerPosition: function (unit) { - let x, y, positions, baseId = getBaseStat("monstats", unit.classid, "baseid"); - let size = getBaseStat("monstats2", baseId, "sizex"); - let canTele = Pather.canTeleport(); - - // in case base stat returns something outrageous - (typeof size !== "number" || size < 1 || size > 3) && (size = 3); - - switch (unit.type) { - case sdk.unittype.Player: - x = unit.x; - y = unit.y; - positions = [[x + 2, y], [x + 2, y + 1]]; - - break; - case sdk.unittype.Monster: - let commonCheck = (unit.isMoving && unit.distance < 10); - x = commonCheck && getDistance(me, unit.targetx, unit.targety) > 5 ? unit.targetx : unit.x; - y = commonCheck && getDistance(me, unit.targetx, unit.targety) > 5 ? unit.targety : unit.y; - positions = [[x + 2, y + 1], [x, y + 3], [x + 2, y - 1], [x - 2, y + 2], [x - 5, y]]; - size === 3 && positions.unshift([x + 2, y + 2]); - - break; - } - - // If one of the valid positions is a position im at already - for (let i = 0; i < positions.length; i += 1) { - let check = { x: positions[i][0], y: positions[i][1] }; - - if (canTele && [check.x, check.y].distance < 1) { - return true; - } else if (!canTele && ([check.x, check.y].distance < 1 && !CollMap.checkColl(unit, check, sdk.collision.BlockWalk, 0)) - || ([check.x, check.y].distance <= 4 && me.getMobCount(6) > 2)) { - return true; - } - } - - for (let i = 0; i < positions.length; i += 1) { - let check = { x: positions[i][0], y: positions[i][1] }; - - if (Attack.validSpot(check.x, check.y) && !CollMap.checkColl(unit, check, sdk.collision.BlockWalk, 0)) { - if (this.reposition(check.x, check.y)) return true; - } - } - - console.debug("Failed to find a hammer position for " + unit.name + " distance from me: " + unit.distance); - - return false; - }, - - reposition: function (x, y) { - if (typeof x !== "number" || typeof y !== "number") return false; - if ([x, y].distance > 0) { - if (Pather.useTeleport()) { - [x, y].distance > 30 ? Pather.moveTo(x, y) : Pather.teleportTo(x, y, 3); - } else { - if ([x, y].distance <= 4) { - Misc.click(0, 0, x, y); - } else if (!CollMap.checkColl(me, {x: x, y: y}, sdk.collision.BlockWalk, 3)) { - Pather.walkTo(x, y); - } else { - // don't clear while trying to reposition - Pather.moveToEx(x, y, {clearSettings: {allowClearing: false}}); - } - - delay(200); - } - } - - return true; - } -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Sorceress.js b/d2bs/kolbot/libs/common/Attacks/Sorceress.js deleted file mode 100644 index 319b03e9b..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Sorceress.js +++ /dev/null @@ -1,233 +0,0 @@ -/** -* @filename Sorceress.js -* @author kolton, theBGuy -* @desc Sorceress attack sequence -* -*/ - -const ClassAttack = { - decideSkill: function (unit) { - let skills = {timed: -1, untimed: -1}; - if (!unit || !unit.attackable) return skills; - - let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - let classid = unit.classid; - - // Get timed skill - let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - skills.timed = checkSkill; - } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { - skills.timed = Config.AttackSkill[5]; - } - - // Get untimed skill - checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; - - if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { - skills.untimed = checkSkill; - } else if (Config.AttackSkill[6] > -1 && Attack.checkResist(unit, Config.AttackSkill[6]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { - skills.untimed = Config.AttackSkill[6]; - } - - // Low mana timed skill - if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(skills.timed) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) { - skills.timed = Config.LowManaSkill[0]; - } - - // Low mana untimed skill - if (Config.LowManaSkill[1] > -1 && Skill.getManaCost(skills.untimed) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[1])) { - skills.untimed = Config.LowManaSkill[1]; - } - - return skills; - }, - - doAttack: function (unit, preattack = false) { - if (!unit) return Attack.Result.SUCCESS; - let gid = unit.gid; - - if (Config.MercWatch && Town.needMerc()) { - if (Town.visitTown()) { - print("mercwatch"); - - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - console.debug("Lost reference to unit"); - return Attack.Result.SUCCESS; - } - } - } - - // Keep Energy Shield active - Skill.canUse(sdk.skills.EnergyShield) && !me.getState(sdk.states.EnergyShield) && Skill.cast(sdk.skills.EnergyShield, sdk.skills.hand.Right); - - // Keep Thunder-Storm active - Skill.canUse(sdk.skills.ThunderStorm) && !me.getState(sdk.states.ThunderStorm) && Skill.cast(sdk.skills.ThunderStorm, sdk.skills.hand.Right); - - if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - let useStatic = (Config.StaticList.length > 0 && Config.CastStatic < 100 && Skill.canUse(sdk.skills.StaticField) && Attack.checkResist(unit, "lightning")); - let idCheck = function (id) { - if (unit) { - switch (true) { - case typeof id === "number" && unit.classid && unit.classid === id: - case typeof id === "string" && unit.name && unit.name.toLowerCase() === id.toLowerCase(): - case typeof id === "function" && id(unit): - return true; - default: - return false; - } - } - - return false; - }; - - // Static - needs to be re-done - if (useStatic && Config.StaticList.some(id => idCheck(id)) && unit.hpPercent > Config.CastStatic) { - let staticRange = Skill.getRange(sdk.skills.StaticField); - let casts = 0; - - while (!me.dead && unit.hpPercent > Config.CastStatic && unit.attackable) { - if (unit.distance > staticRange || checkCollision(me, unit, sdk.collision.Ranged)) { - if (!Attack.getIntoPosition(unit, staticRange, sdk.collision.Ranged)) { - return Attack.Result.FAILED; - } - } - - // if we fail to cast or we've casted 3 or more times - do something else - if (!Skill.cast(sdk.skills.StaticField, sdk.skills.hand.Right) || casts >= 3) { - break; - } else { - casts++; - } - } - - // re-check mob after static - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - console.debug("Lost reference to unit"); - return Attack.Result.SUCCESS; - } - } - - let skills = this.decideSkill(unit); - let result = this.doCast(unit, skills.timed, skills.untimed); - - if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { - let merc = me.getMerc(); - let mercRevive = 0; - - while (unit.attackable) { - if (Misc.townCheck()) { - if (!unit || !copyUnit(unit).x) { - unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); - } - } - - if (!unit) return Attack.Result.SUCCESS; - - if (Town.needMerc()) { - if (Config.MercWatch && mercRevive++ < 1) { - Town.visitTown(); - } else { - return Attack.Result.CANTATTACK; - } - - (merc === undefined || !merc) && (merc = me.getMerc()); - } - - if (!!merc && getDistance(merc, unit) > 7) { - Pather.moveToUnit(unit); - - let spot = Attack.findSafeSpot(unit, 10, 5, 9); - !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); - } - - let closeMob = Attack.getNearestMonster({skipGid: gid}); - - if (!!closeMob) { - let findSkill = this.decideSkill(closeMob); - (this.doCast(closeMob, findSkill.timed, findSkill.untimed) === 1) || (Skill.haveTK && Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit)); - } - } - - return Attack.Result.SUCCESS; - } - - return result; - }, - - afterAttack: function () { - Precast.doPrecast(false); - }, - - // Returns: 0 - fail, 1 - success, 2 - no valid attack skills - doCast: function (unit, timedSkill = -1, untimedSkill = -1) { - // No valid skills can be found - if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; - // unit became invalidated - if (!unit || !unit.attackable) return Attack.Result.SUCCESS; - - let walk, noMana = false; - let classid = unit.classid; - - if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill)) && Skill.getManaCost(timedSkill) < me.mp) { - if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(timedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && !checkCollision(me, unit, sdk.collision.Ranged) && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); - - return Attack.Result.SUCCESS; - } else { - noMana = !me.skillDelay; - } - - if (untimedSkill > -1 && Skill.getManaCost(untimedSkill) < me.mp) { - if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { - // Allow short-distance walking for melee skills - walk = Skill.getRange(untimedSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall); - - if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { - return Attack.Result.FAILED; - } - } - - !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); - - return Attack.Result.SUCCESS; - } else { - noMana = true; - } - - // don't count as failed - if (noMana) return Attack.Result.NEEDMANA; - - Misc.poll(() => !me.skillDelay, 1000, 40); - - return Attack.Result.SUCCESS; - } -}; diff --git a/d2bs/kolbot/libs/common/Attacks/Wereform.js b/d2bs/kolbot/libs/common/Attacks/Wereform.js deleted file mode 100644 index 93ce8dcbd..000000000 --- a/d2bs/kolbot/libs/common/Attacks/Wereform.js +++ /dev/null @@ -1,153 +0,0 @@ -/** -* @filename Wereform.js -* @author kolton, theBGuy -* @desc Wereform attack sequence -* -*/ - -// todo - handle a Bear necro summonmancer - -const ClassAttack = { - feralBoost: 0, - baseLL: me.getStat(sdk.stats.LifeLeech), - baseED: me.getStat(sdk.stats.DamagePercent), - maulBoost: 0, - - doAttack: function (unit, preattack) { - if (!unit) return Attack.Result.SUCCESS; - let gid = unit.gid; - - if (Config.MercWatch && Town.needMerc()) { - console.debug("mercwatch"); - - if (Town.visitTown()) { - if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { - return Attack.Result.SUCCESS; // lost reference to the mob we were attacking - } - } - } - - if (!this.feralBoost && Config.AttackSkill.includes(sdk.skills.FeralRage)) { - // amount of life leech with max rage - this.feralBoost = ((Math.floor(me.getSkill(sdk.skills.FeralRage, sdk.skills.subindex.SoftPoints) / 2) + 3) * 4) + this.baseLL; - } - - if (!this.maulBoost && Config.AttackSkill.includes(sdk.skills.Maul)) { - // amount of enhanced damage with max maul - this.maulBoost = ((Math.floor(me.getSkill(sdk.skills.Maul, sdk.skills.subindex.SoftPoints) / 2) + 3) * 20) + this.baseED; - } - - Misc.shapeShift(Config.Wereform); - - if (((Config.AttackSkill[0] === sdk.skills.FeralRage && (!me.getState(sdk.states.FeralRage) || me.getStat(sdk.stats.LifeLeech) < this.feralBoost)) - || (Config.AttackSkill[0] === sdk.skills.Maul && (!me.getState(sdk.states.Maul) || me.getStat(sdk.stats.DamagePercent) < this.maulBoost)) - || (Config.AttackSkill[0] === sdk.skills.ShockWave && !unit.isSpecial && !unit.getState(sdk.states.Stunned)) - || (preattack && Config.AttackSkill[0] > 0)) - && Attack.checkResist(unit, Config.AttackSkill[0]) - && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0])) - && (Skill.wereFormCheck(Config.AttackSkill[0]) || !me.shapeshifted)) { - if (unit.distance > Skill.getRange(Config.AttackSkill[0]) || checkCollision(me, unit, sdk.collision.WallOrRanged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.WallOrRanged, true)) { - return Attack.Result.FAILED; - } - } - - Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); - - return Attack.Result.SUCCESS; - } - - // Rebuff Armageddon - Skill.canUse(sdk.skills.Armageddon) && !me.getState(sdk.states.Armageddon) && Skill.cast(sdk.skills.Armageddon, sdk.skills.hand.Right); - - let timedSkill = -1; - let untimedSkill = -1; - let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; - - // Get timed skill - let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; - - if (Attack.checkResist(unit, checkSkill) && Skill.wereFormCheck(checkSkill) && Attack.validSpot(unit.x, unit.y)) { - timedSkill = checkSkill; - } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y)) { - timedSkill = Config.AttackSkill[5]; - } - - // Get untimed skill - checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; - - if (Attack.checkResist(unit, checkSkill) && Skill.wereFormCheck(checkSkill) && Attack.validSpot(unit.x, unit.y)) { - untimedSkill = checkSkill; - } else if (Config.AttackSkill[6] > -1 && Attack.checkResist(unit, Config.AttackSkill[6]) && Attack.validSpot(unit.x, unit.y)) { - untimedSkill = Config.AttackSkill[6]; - } - - // eval skills - switch (true) { - case timedSkill === sdk.skills.Fury && untimedSkill === sdk.skills.FeralRage: - if (!me.getState(sdk.states.FeralRage) || me.getStat(sdk.stats.LifeLeech) < this.feralBoost) { - timedSkill = sdk.skills.FeralRage; - } - - break; - case timedSkill === sdk.skills.Fury && untimedSkill === sdk.skills.Rabies: - case timedSkill === sdk.skills.FireClaws && untimedSkill === sdk.skills.Rabies: - if (!unit.getState(sdk.states.Rabies)) { - timedSkill = sdk.skills.Rabies; - } - - break; - case timedSkill === sdk.skills.ShockWave && untimedSkill === sdk.skills.Maul: - case timedSkill === sdk.skills.Maul && untimedSkill === sdk.skills.ShockWave: - case timedSkill === sdk.skills.Maul && untimedSkill === sdk.skills.FireClaws: - if (!me.getState(sdk.states.Maul)) { - timedSkill = sdk.skills.Maul; - } - - break; - } - - // Low mana timed skill - if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(timedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) { - timedSkill = Config.LowManaSkill[0]; - } - - // Low mana untimed skill - if (Config.LowManaSkill[1] > -1 && Skill.getManaCost(untimedSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[1])) { - untimedSkill = Config.LowManaSkill[1]; - } - - // use our secondary skill if we can't use our primary - let choosenSkill = (Skill.isTimed(timedSkill) && me.skillDelay && untimedSkill > -1 ? untimedSkill : timedSkill); - - return this.doCast(unit, choosenSkill); - }, - - afterAttack: function () { - Precast.doPrecast(false); - }, - - // Returns: 0 - fail, 1 - success, 2 - no valid attack skills - doCast: function (unit, skill) { - // unit reference no longer valid or it died - if (!unit || unit.dead) return Attack.Result.SUCCESS; - // No valid skills can be found - if (skill < 0) return Attack.Result.CANTATTACK; - - if (Skill.getRange(skill) < 4 && !Attack.validSpot(unit.x, unit.y)) { - return Attack.Result.FAILED; - } - - if (unit.distance > Skill.getRange(skill) || checkCollision(me, unit, sdk.collision.WallOrRanged)) { - if (!Attack.getIntoPosition(unit, Skill.getRange(skill), sdk.collision.WallOrRanged, true)) { - return Attack.Result.FAILED; - } - } - - unit.attackable && Skill.cast(skill, Skill.getHand(skill), unit); - - Misc.poll(() => !me.skillDelay, 1000, 40); - - return Attack.Result.SUCCESS; - } -}; diff --git a/d2bs/kolbot/libs/common/AutoAssign.js b/d2bs/kolbot/libs/common/AutoAssign.js deleted file mode 100644 index 6382d1c8a..000000000 --- a/d2bs/kolbot/libs/common/AutoAssign.js +++ /dev/null @@ -1,252 +0,0 @@ -/** -* @filename AutoAssign.js -* @author ? -* @desc ? -* -*/ -let answer = false; -let request = false; - -const AutoAssign = { - recursion: true, - Barbs: [], - Sorcs: [], - Pallys: [], - Jobs: { - Barb: "", - Sorc: "", - Pally: "", - Mine: 0 - }, - - init: function () { - AutoAssign.updateNames(); // initiates all scripts - // Do something else? What else do we need to do... - - return true; - }, - - receiveCopyData: function (mode, msg) { - switch (mode) { - case 69: // request - msg === me.name && D2Bot.shoutGlobal("bot", 70); - - break; - case 70: // Received answer - msg === "bot" && request === true && (answer = true); - - break; - default: - break; - } - }, - - // eslint-disable-next-line no-unused-vars - gameEvent: function (mode, param1, param2, name1) { - switch (mode) { - case 0x00: // Left game due to time-out - AutoAssign.updateNames(name1); - - break; - case 0x02: // Joined game - AutoAssign.updateNames(); - - break; - case 0x03: //left game - AutoAssign.updateNames(name1); - - break; - - } - delay(250); - }, - - getJobs: function () { - let quitCheck; - let array = [this.Barbs, this.Pallys, this.Sorcs]; - - for (let i = 0; i < array.length; i++) { - let current = array[i]; - - switch (i) { - case 0: - quitCheck = getParty(this.Jobs.Barb); - !quitCheck && (this.Jobs.Barb = ""); - - if (current.length > 0) { - this.Jobs.Barb = current[0].name; - //print ("setting leader Barb to: " + AutoAssign.Jobs.Barb); - } - break; - case 1: - quitCheck = getParty(this.Jobs.Pally); - !quitCheck && (this.Jobs.Pally = ""); - - if (current.length > 0) { - this.Jobs.Pally = current[0].name; - //print ("setting leader Pally to: " + AutoAssign.Jobs.Pally); - } - break; - case 2: - quitCheck = getParty(this.Jobs.Sorc); - !quitCheck && (this.Jobs.Sorc = ""); - - if (current.length > 0) { - this.Jobs.Sorc = current[0].name; - //print ("setting leader Sorc to: " + AutoAssign.Jobs.Sorc); - } - break; - } - - for (let y = 0; y < current.length; y++) { - if (current[y].name === me.name) { - this.Jobs.Mine = y; - } - } - } - return true; - }, - - pushNames: function (name, level, classid) { - let obj = {name: name, level: level}; - - switch (classid) { - case sdk.player.class.Sorceress: - this.Sorcs.push(obj); - break; - case sdk.player.class.Paladin: - this.Pallys.push(obj); - break; - case sdk.player.class.Barbarian: - this.Barbs.push(obj); - break; - } - return true; - }, - - checkNames: function (name, type) { - let i, timeout = 1000; - - for (i = 0; i < type.length; i++) { - if (type[i].name === name) { - break; - } - } - - if (i === type.length) { - D2Bot.shoutGlobal(name, 69); - let tick = getTickCount(); - request = true; - - while (!answer) { - if (getTickCount() - tick > timeout) { - break; - } - delay (100); - } - } - - if (answer) { - answer = false; - request = false; - return true; - } - - answer = false; - request = false; - - return false; - }, - - sortNames: function () { - let arrays = [this.Barbs, this.Pallys, this.Sorcs]; - - for (let i = 0; i < arrays.length; i++) { - let type = arrays[i]; - - type.sort(function (a, b) { - if (a.name > b.name) return 1; - if (a.name < b.name) return -1; - return 0; - }); - - type.sort(function (a, b) { - return b.level - a.level; - }); - - } - return true; - }, - - removeNames: function (quitter) { - print(quitter + " has left. updating.."); - let arrays = [this.Barbs, this.Pallys, this.Sorcs]; - - for (let i = 0; i < arrays.length; i++) { - let currentClass = arrays[i]; - - for (let y = 0; y < currentClass.length; y++) { - if (currentClass[y].name === quitter) { - currentClass.splice(y, 1); - } - } - } - return true; - }, - - getNames: function () { - print("Updating names."); - - for (let i = 0; i < 3; i++) { - let party = getParty(); - - if (party) { - do { - switch (party.classid) { - case sdk.player.class.Sorceress: - if (this.checkNames(party.name, this.Sorcs)) { - this.pushNames(party.name, party.level, party.classid); - } - - break; - case sdk.player.class.Paladin: - if (this.checkNames(party.name, this.Pallys)) { - this.pushNames(party.name, party.level, party.classid); - } - - break; - case sdk.player.class.Barbarian: - if (this.checkNames(party.name, this.Barbs)) { - this.pushNames(party.name, party.level, party.classid); - } - - break; - default: - break; - } - } while (party.getNext()); - } - } - - this.sortNames(); - this.getJobs(); - - return this.Jobs; - }, - - updateNames: function (quitter) { - if (this.recursion) { - this.recursion = false; - try { - quitter && this.removeNames(quitter); - this.getNames(); - } finally { - this.recursion = true; - } - } - return true; - } -}; - //addEventListener("scriptmsg", AutoAssign.ScriptMsgEvent); -addEventListener("copydata", AutoAssign.receiveCopyData); -addEventListener("gameevent", AutoAssign.gameEvent); diff --git a/d2bs/kolbot/libs/common/AutoBuild.js b/d2bs/kolbot/libs/common/AutoBuild.js deleted file mode 100644 index 2b86d09fe..000000000 --- a/d2bs/kolbot/libs/common/AutoBuild.js +++ /dev/null @@ -1,106 +0,0 @@ -/** -* @filename AutoBuild.js -* @author alogwe -* @desc This script is included when any script includes libs/common/Config.js and calls Config.init(). -* If enabled, loads a threaded helper script that will monitor changes in character level and -* upon level up detection, it will spend skill and stat points based on a configurable -* character build template file located in libs/config/Builds/*. -* -* Any skill and stat points obtained as quest rewards are currently -* invisible to this script and must be spent manually. -* -*/ -js_strict(true); - -!isIncluded("common/Prototypes.js") && include("common/Prototypes.js"); -!isIncluded("common/Cubing.js") && include("common/Cubing.js"); -!isIncluded("common/Runewords.js") && include("common/Runewords.js"); - -const AutoBuild = new function AutoBuild () { - Config.AutoBuild.DebugMode && (Config.AutoBuild.Verbose = true); - - let debug = !!Config.AutoBuild.DebugMode; - let verbose = !!Config.AutoBuild.Verbose; - let configUpdateLevel = 0; - - // Apply all Update functions from the build template in order from level 1 to me.charlvl. - // By reapplying all of the changes to the Config object, we preserve - // the state of the Config file without altering the saved char config. - function applyConfigUpdates () { - debug && this.print("Updating Config from level " + configUpdateLevel + " to " + me.charlvl); - while (configUpdateLevel < me.charlvl) { - configUpdateLevel += 1; - Skill.init(); - AutoBuildTemplate[configUpdateLevel].Update.apply(Config); - } - } - - function getBuildType () { - let build = Config.AutoBuild.Template; - if (!build) { - this.print("Config.AutoBuild.Template is either 'false', or invalid (" + build + ")"); - throw new Error("Invalid build template, read libs/config/Builds/README.txt for information"); - } - return build; - } - - function getCurrentScript () { - return getScript(true).name.toLowerCase(); - } - - function getLogFilename () { - let d = new Date(); - let dateString = d.getMonth() + "_" + d.getDate() + "_" + d.getFullYear(); - return ("logs/AutoBuild." + me.realm + "." + me.charname + "." + dateString + ".log"); - } - - function getTemplateFilename () { - let build = getBuildType(); - let template = "config/Builds/" + sdk.player.class.nameOf(me.classid) + "." + build + ".js"; - return template.toLowerCase(); - } - - function initialize () { - let currentScript = getCurrentScript(); - let template = getTemplateFilename(); - this.print("Including build template " + template + " into " + currentScript); - if (!include(template)) throw new Error("Failed to include template: " + template); - - // Only load() helper thread from default.dbj if it isn't loaded - if (currentScript === "default.dbj" && !getScript("tools\\autobuildthread.js")) { - load("tools/autobuildthread.js"); - } - - // All threads except autobuildthread.js use this event listener - // to update their thread-local Config object - if (currentScript !== "tools\\autobuildthread.js") { - addEventListener("scriptmsg", levelUpHandler); - } - - // Resynchronize our Config object with all past changes - // made to it by AutoBuild system - applyConfigUpdates(); - } - - function levelUpHandler (obj) { - if (typeof obj === "object" && obj.hasOwnProperty("event") && obj.event === "level up") { - applyConfigUpdates(); - } - } - - function log (message) { FileTools.appendText(getLogFilename(), message + "\n"); } - - // Only print to console from autobuildthread.js, - // but log from all scripts - function myPrint () { - let args = Array.prototype.slice.call(arguments); - args.unshift("AutoBuild:"); - let result = args.join(" "); - verbose && print.call(this, result); - debug && log.call(this, result); - } - - this.print = myPrint; - this.initialize = initialize; - this.applyConfigUpdates = applyConfigUpdates; -}; diff --git a/d2bs/kolbot/libs/common/AutoSkill.js b/d2bs/kolbot/libs/common/AutoSkill.js deleted file mode 100644 index 9b580a0da..000000000 --- a/d2bs/kolbot/libs/common/AutoSkill.js +++ /dev/null @@ -1,154 +0,0 @@ -/** -* @filename AutoSkill.js -* @author Original work by Nad42, edited by IMBA -* @desc Automatically allocate skill points and its pre-requisites if necessary -* -*/ - -const AutoSkill = new function () { - this.skillBuildOrder = []; - this.save = 0; - - /* skillBuildOrder - array of skill points to spend in order - save - number of skill points that will not be spent and saved - - skillBuildOrder Settings - Set skillBuildOrder in the array form: [[skill, count, satisfy], [skill, count, satisfy], ... [skill, count, satisfy]] - skill - skill id number (see /sdk/skills.txt) - count - maximum number of skill points to allocate for that skill - satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - - skillBuildOrder = [ - [37, 1, true], [42, 1, true], [54, 1, true], //warmth, static, teleport - [59, 1, false], [55, 7, true], [45, 13, true], //blizzard, glacial spike, ice blast - [59, 7, false], [65, 1, true], //blizzard, cold mastery - [59, 20, false], [65, 20, true], //max blizzard, max cold mastery - [55, 20, true], [45, 20, true], //max glacial spike, max ice blast - ]; - */ - - //a function to return false if have all prereqs or a skill if not - this.needPreReq = function (skillid) { - //a loop to go through each reqskill - for (let t = sdk.stats.PreviousSkillLeft; t >= sdk.stats.PreviousSkillRight; t--) { - // Check ReqSkills - let preReq = (getBaseStat("skills", skillid, t)); - - if (preReq > sdk.skills.Attack && preReq < 356 && !me.getSkill(preReq, sdk.skills.subindex.HardPoints)) { - return preReq; - } - } - - return false; - }; - - this.skillCheck = function (skillid, count) { - if (me.getSkill(skillid, sdk.skills.subindex.HardPoints) <= me.charlvl - getBaseStat("skills", skillid, sdk.stats.MinimumRequiredLevel) && me.getSkill(skillid, sdk.skills.subindex.HardPoints) < count) { - return true; - } - - return false; - }; - - this.skillToAdd = function (inputArray) { - for (let i = 0; i < inputArray.length; i += 1) { - // limit maximum allocation count to 20 - if (inputArray[i][1] > 20) { - print("AutoSkill: Skill build index " + i + " has allocation count of " + inputArray[i][1] + " and it will be limited to 20"); - inputArray[i][1] = 20; - } - - // set satify condition as default if not specified - if (inputArray[i][2] === undefined) { - inputArray[i][2] = true; - } - - // check to see if skill count in previous array is satisfied - if (i > 0 && inputArray[i - 1][2] && (!me.getSkill(inputArray[i - 1][0], sdk.skills.subindex.HardPoints) ? 0 : me.getSkill(inputArray[i - 1][0], sdk.skills.subindex.HardPoints)) < inputArray[i - 1][1]) { - return false; - } - - if (me.getSkill(inputArray[i][0], sdk.skills.subindex.HardPoints) && this.skillCheck(inputArray[i][0], inputArray[i][1])) { - return inputArray[i][0]; - } - - let reqIn; - let reqOut = this.needPreReq(inputArray[i][0]); - - if (!reqOut && this.skillCheck(inputArray[i][0], inputArray[i][1])) { - return inputArray[i][0]; - } - - while (reqOut) { - reqIn = reqOut; - reqOut = this.needPreReq(reqIn); - } - - if (this.skillCheck(reqIn, 1)) { - return reqIn; - } - } - - return false; - }; - - this.allocate = function () { - let tick = getTickCount(); - - this.remaining = me.getStat(sdk.stats.NewSkills); - - if (!getUIFlag(sdk.uiflags.TradePrompt)) { - let addTo = this.skillToAdd(this.skillBuildOrder); - - if (addTo) { - print("AutoSkill: Using skill point in Skill: " + getSkillById(addTo) + " ID: " + addTo); - delay(100); - useSkillPoint(addTo, 1); - } - } - - while (getTickCount() - tick < 1500 + 2 * me.ping) { - if (this.remaining > me.getStat(sdk.stats.NewSkills)) { - return true; - } - - delay(100); - } - - return false; - }; - - this.remaining = 0; - this.count = 0; - - this.init = function (skillBuildOrder, save = 0) { - this.skillBuildOrder = skillBuildOrder; - this.save = save; - - if (!this.skillBuildOrder || !this.skillBuildOrder.length) { - print("AutoSkill: No build array specified"); - - return false; - } - - while (me.getStat(sdk.stats.NewSkills) > this.save) { - this.allocate(); - delay(200 + me.ping); // may need longer delay under high ping - - // break out of loop if we have skill points available but cannot allocate further due to unsatisfied skill - if (me.getStat(sdk.stats.NewSkills) === this.remaining) { - this.count += 1; - } - - if (this.count > 2) { - break; - } - } - - print("AutoSkill: Finished allocating skill points"); - - return true; - }; - - return true; -}; diff --git a/d2bs/kolbot/libs/common/AutoStat.js b/d2bs/kolbot/libs/common/AutoStat.js deleted file mode 100644 index b58fd31c1..000000000 --- a/d2bs/kolbot/libs/common/AutoStat.js +++ /dev/null @@ -1,730 +0,0 @@ -/* eslint-disable no-labels */ -/** -* @filename AutoStat.js -* @author IMBA -* @desc Automatically allocate stat points -* -*/ - -const AutoStat = new function () { - this.statBuildOrder = []; - this.save = 0; - this.block = 0; - this.bulkStat = true; - - /* statBuildOrder - array of stat points to spend in order - save - remaining stat points that will not be spent and saved. - block - an integer value set to desired block chance. This is ignored in classic. - bulkStat - set true to spend multiple stat points at once (up to 100), or false to spend 1 point at a time. - - statBuildOrder Settings - The script will stat in the order of precedence. You may want to stat strength or dexterity first. - - Set stats to desired integer value, and it will stat *hard points up to the desired value. - You can also set to string value "all", and it will spend all the remaining points. - Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - - statBuildOrder = [ - ["strength", 25], ["energy", 75], ["vitality", 75], - ["strength", 55], ["vitality", "all"] - ]; - */ - - this.getBlock = function () { - if (!me.usingShield()) return this.block; - - // cast holy shield if available - if (Skill.canUse(sdk.skills.HolyShield) && !me.getState(sdk.states.HolyShield)) { - if (Precast.cast(sdk.skills.HolyShield)) { - delay(1000); - } else { - return this.block; - } - } - - if (me.classic) { - return Math.floor(me.getStat(sdk.stats.ToBlock) + getBaseStat(15, me.classid, 23)); - } - - return Math.min(75, Math.floor((me.getStat(sdk.stats.ToBlock) + getBaseStat(15, me.classid, 23)) * (me.getStat(sdk.stats.Dexterity) - 15) / (me.charlvl * 2))); - }; - - // this check may not be necessary with this.validItem(), but consider it double check - // verify that the set bonuses are there - this.verifySetStats = function (unit, type, stats) { - let string = type === sdk.stats.Strength ? sdk.locale.text.ToStrength : sdk.locale.text.ToDexterity; - - if (unit) { - let temp = unit.description.split("\n"); - - for (let i = 0; i < temp.length; i += 1) { - if (temp[i].match(getLocaleString(string), "i")) { - if (parseInt(temp[i].replace(/(y|ÿ)c[0-9!"+<;.*]/, ""), 10) === stats) { - return true; - } - } - } - } - - return false; - }; - - this.validItem = function (item) { - // ignore item bonuses from secondary weapon slot - if (me.expansion && item.isOnSwap) return false; - // check if character meets str, dex, and level requirement since stat bonuses only apply when they are active - return me.getStat(sdk.stats.Strength) >= item.strreq && me.getStat(sdk.stats.Dexterity) >= item.dexreq && me.charlvl >= item.lvlreq; - }; - - // get stats from set bonuses - this.setBonus = function (type) { - // set bonuses do not have energy or vitality (we can ignore this) - if (type === sdk.stats.Energy || type === sdk.stats.Vitality) return 0; - - // these are the only sets with possible stat bonuses - let sets = { - "angelic": [], "artic": [], "civerb": [], "iratha": [], - "isenhart": [], "vidala": [], "cowking": [], "disciple": [], - "griswold": [], "mavina": [], "naj": [], "orphan": [] - }; - - let i, j, setStat = 0; - let items = me.getItems(); - - if (items) { - for (i = 0; i < items.length; i += 1) { - if (items[i].isEquipped && items[i].set && this.validItem(items[i])) { - idSwitch: - switch (items[i].classid) { - case sdk.items.Crown: - if (items[i].getStat(sdk.stats.LightResist) === 30) { - sets.iratha.push(items[i]); - } - - break; - case sdk.items.LightGauntlets: - if (items[i].getStat(sdk.stats.MaxHp) === 20) { - sets.artic.push(items[i]); - } else if (items[i].getStat(sdk.stats.ColdResist) === 30) { - sets.iratha.push(items[i]); - } - - break; - case sdk.items.HeavyBoots: - if (items[i].getStat(sdk.stats.Dexterity) === 20) { - sets.cowking.push(items[i]); - } - - break; - case sdk.items.HeavyBelt: - if (items[i].getStat(sdk.stats.MinDamage) === 5) { - sets.iratha.push(items[i]); - } - - break; - case sdk.items.Amulet: - if (items[i].getStat(sdk.stats.DamagetoMana) === 20) { - sets.angelic.push(items[i]); - } else if (items[i].getStat(sdk.stats.HpRegen) === 4) { - sets.civerb.push(items[i]); - } else if (items[i].getStat(sdk.stats.PoisonLengthResist) === 75) { - sets.iratha.push(items[i]); - } else if (items[i].getStat(sdk.stats.ColdResist) === 20) { - sets.vidala.push(items[i]); - } else if (items[i].getStat(sdk.stats.ColdResist) === 18) { - sets.disciple.push(items[i]); - } - - break; - case sdk.items.Ring: - if (items[i].getStat(sdk.stats.HpRegen) === 6) { - // do not count ring twice - for (j = 0; j < sets.angelic.length; j += 1) { - if (sets.angelic[j].classid === items[i].classid) { - break idSwitch; - } - } - - sets.angelic.push(items[i]); - } - - break; - case sdk.items.Sabre: - // do not count twice in case of dual wield - for (j = 0; j < sets.angelic.length; j += 1) { - if (sets.angelic[j].classid === items[i].classid) { - break idSwitch; - } - } - - sets.angelic.push(items[i]); - - break; - case sdk.items.RingMail: - sets.angelic.push(items[i]); - - break; - case sdk.items.ShortWarBow: - case sdk.items.QuiltedArmor: - case sdk.items.LightBelt: - sets.artic.push(items[i]); - - break; - case sdk.items.GrandScepter: - // do not count twice in case of dual wield - for (j = 0; j < sets.civerb.length; j += 1) { - if (sets.civerb[j].classid === items[i].classid) { - break idSwitch; - } - } - - sets.civerb.push(items[i]); - - break; - case sdk.items.LargeShield: - sets.civerb.push(items[i]); - - break; - case sdk.items.BroadSword: - // do not count twice in case of dual wield - for (j = 0; j < sets.isenhart.length; j += 1) { - if (sets.isenhart[j].classid === items[i].classid) { - break idSwitch; - } - } - - sets.isenhart.push(items[i]); - - break; - case sdk.items.FullHelm: - case sdk.items.BreastPlate: - case sdk.items.GothicShield: - sets.isenhart.push(items[i]); - - break; - case sdk.items.LongBattleBow: - case sdk.items.LeatherArmor: - case sdk.items.LightPlatedBoots: - sets.vidala.push(items[i]); - - break; - case sdk.items.StuddedLeather: - case sdk.items.WarHat: - sets.cowking.push(items[i]); - - break; - case sdk.items.DemonhideBoots: - case sdk.items.DuskShroud: - case sdk.items.BrambleMitts: - case sdk.items.MithrilCoil: - sets.disciple.push(items[i]); - - break; - case sdk.items.Caduceus: - // do not count twice in case of dual wield - for (j = 0; j < sets.griswold.length; j += 1) { - if (sets.griswold[j].classid === items[i].classid) { - break idSwitch; - } - } - - sets.griswold.push(items[i]); - - break; - case sdk.items.OrnatePlate: - case sdk.items.Corona: - case sdk.items.VortexShield: - sets.griswold.push(items[i]); - - break; - case sdk.items.GrandMatronBow: - case sdk.items.BattleGauntlets: - case sdk.items.SharkskinBelt: - case sdk.items.Diadem: - case sdk.items.KrakenShell: - sets.mavina.push(items[i]); - - break; - case sdk.items.ElderStaff: - case sdk.items.Circlet: - case sdk.items.HellforgePlate: - sets.naj.push(items[i]); - - break; - case sdk.items.WingedHelm: - case sdk.items.RoundShield: - case sdk.items.SharkskinGloves: - case sdk.items.BattleBelt: - sets.orphan.push(items[i]); - - break; - } - } - } - } - - for (i in sets) { - if (sets.hasOwnProperty(i)) { - MainSwitch: - switch (i) { - case "angelic": - if (sets[i].length >= 2 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 10)) { - break MainSwitch; - } - } - - setStat += 10; - } - - break; - case "artic": - if (sets[i].length >= 2 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 5)) { - break MainSwitch; - } - } - - setStat += 5; - } - - break; - case "civerb": - if (sets[i].length === 3 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 15)) { - break MainSwitch; - } - } - - setStat += 15; - } - - break; - case "iratha": - if (sets[i].length === 4 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 15)) { - break MainSwitch; - } - } - - setStat += 15; - } - - break; - case "isenhart": - if (sets[i].length >= 2 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 10)) { - break MainSwitch; - } - } - - setStat += 10; - } - - if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 10)) { - break MainSwitch; - } - } - - setStat += 10; - } - - break; - case "vidala": - if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 15)) { - break MainSwitch; - } - } - - setStat += 15; - } - - if (sets[i].length === 4 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 10)) { - break MainSwitch; - } - } - - setStat += 10; - } - - break; - case "cowking": - if (sets[i].length === 3 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 20)) { - break MainSwitch; - } - } - - setStat += 20; - } - - break; - case "disciple": - if (sets[i].length >= 4 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 10)) { - break MainSwitch; - } - } - - setStat += 10; - } - - break; - case "griswold": - if (sets[i].length >= 2 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 20)) { - break MainSwitch; - } - } - - setStat += 20; - } - - if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 30)) { - break MainSwitch; - } - } - - setStat += 30; - } - - break; - case "mavina": - if (sets[i].length >= 2 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 20)) { - break MainSwitch; - } - } - - setStat += 20; - } - - if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 30)) { - break MainSwitch; - } - } - - setStat += 30; - } - - break; - case "naj": - if (sets[i].length === 3 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 15)) { - break MainSwitch; - } - } - - setStat += 15; - } - - if (sets[i].length === 3 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 20)) { - break MainSwitch; - } - } - - setStat += 20; - } - - break; - case "orphan": - if (sets[i].length === 4 && type === sdk.stats.Dexterity) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 10)) { - break MainSwitch; - } - } - - setStat += 10; - } - - if (sets[i].length === 4 && type === sdk.stats.Strength) { - for (j = 0; j < sets[i].length; j += 1) { - if (!this.verifySetStats(sets[i][j], type, 20)) { - break MainSwitch; - } - } - - setStat += 20; - } - - break; - } - } - } - - return setStat; - }; - - // return stat values excluding stat bonuses from sets and/or items - this.getHardStats = function (type) { - let i, statID; - let addedStat = 0; - let items = me.getItems(); - - switch (type) { - case sdk.stats.Strength: - type = sdk.stats.Strength; - statID = sdk.stats.PerLevelStrength; - - break; - case sdk.stats.Energy: - type = sdk.stats.Energy; - statID = sdk.stats.PerLevelEnergy; - - break; - case sdk.stats.Dexterity: - type = sdk.stats.Dexterity; - statID = sdk.stats.PerLevelDexterity; - - break; - case sdk.stats.Vitality: - type = sdk.stats.Vitality; - statID = sdk.stats.PerLevelVitality; - - break; - } - - if (items) { - for (i = 0; i < items.length; i += 1) { - // items equipped or charms in inventory - if ((items[i].isEquipped || items[i].isEquippedCharm) && this.validItem(items[i])) { - // stats - items[i].getStat(type) && (addedStat += items[i].getStat(type)); - - // stats per level - if (items[i].getStat(statID)) { - addedStat += Math.floor(items[i].getStat(statID) / 8 * me.charlvl); - } - } - } - } - - return (me.getStat(type) - addedStat - this.setBonus(type)); - }; - - this.requiredDex = function () { - let set = false; - let inactiveDex = 0; - let items = me.getItems(); - - if (items) { - for (let i = 0; i < items.length; i += 1) { - // items equipped but inactive (these are possible dex sources unseen by me.getStat(sdk.stats.Dexterity)) - if (items[i].isEquipped && !items[i].isOnSwap && !this.validItem(items[i])) { - if (items[i].quality === sdk.items.quality.Set) { - set = true; - - break; - } - - // stats - items[i].getStat(sdk.stats.Dexterity) && (inactiveDex += items[i].getStat(sdk.stats.Dexterity)); - - // stats per level - if (items[i].getStat(sdk.stats.PerLevelDexterity)) { - inactiveDex += Math.floor(items[i].getStat(sdk.stats.PerLevelDexterity) / 8 * me.charlvl); - } - } - } - } - - // just stat 1 at a time if there's set item (there could be dex bonus for currently inactive set) - if (set) { - return 1; - } - - // returns amount of dexterity required to get the desired block chance - return Math.ceil((2 * me.charlvl * this.block) / (me.getStat(sdk.stats.ToBlock) + getBaseStat(15, me.classid, 23)) + 15) - me.getStat(sdk.stats.Dexterity) - inactiveDex; - }; - - this.useStats = function (type, goal = false) { - let currStat = me.getStat(sdk.stats.StatPts); - let tick = getTickCount(); - let statIDToString = [ - getLocaleString(sdk.locale.text.Strength), getLocaleString(sdk.locale.text.Energy), - getLocaleString(sdk.locale.text.Dexterity), getLocaleString(sdk.locale.text.Vitality) - ]; - - // use 0x3a packet to spend multiple stat points at once (up to 100) - if (this.bulkStat) { - if (goal) { - sendPacket(1, sdk.packets.send.AddStat, 1, type, 1, Math.min(me.getStat(sdk.stats.StatPts) - this.save - 1, goal - 1, 99)); - } else { - sendPacket(1, sdk.packets.send.AddStat, 1, type, 1, Math.min(me.getStat(sdk.stats.StatPts) - this.save - 1, 99)); - } - } else { - useStatPoint(type); - } - - while (getTickCount() - tick < 3000) { - if (currStat > me.getStat(sdk.stats.StatPts)) { - print("AutoStat: Using " + (currStat - me.getStat(sdk.stats.StatPts)) + " stat points in " + statIDToString[type]); - return true; - } - - delay(100); - } - - return false; - }; - - this.addStatPoint = function () { - this.remaining = me.getStat(sdk.stats.StatPts); - - let hardStats; - - for (let i = 0; i < this.statBuildOrder.length; i += 1) { - switch (this.statBuildOrder[i][0]) { - case sdk.stats.Strength: - case "s": - case "str": - case "strength": - if (typeof this.statBuildOrder[i][1] === "string") { - switch (this.statBuildOrder[i][1]) { - case "all": - return this.useStats(sdk.stats.Strength); - default: - break; - } - } else { - hardStats = this.getHardStats(sdk.stats.Strength); - - if (hardStats < this.statBuildOrder[i][1]) { - return this.useStats(sdk.stats.Strength, this.statBuildOrder[i][1] - hardStats); - } - } - - break; - case sdk.stats.Energy: - case "e": - case "enr": - case "energy": - if (typeof this.statBuildOrder[i][1] === "string") { - switch (this.statBuildOrder[i][1]) { - case "all": - return this.useStats(sdk.stats.Energy); - default: - break; - } - } else { - hardStats = this.getHardStats(sdk.stats.Energy); - - if (hardStats < this.statBuildOrder[i][1]) { - return this.useStats(sdk.stats.Energy, this.statBuildOrder[i][1] - hardStats); - } - } - - break; - case sdk.stats.Dexterity: - case "d": - case "dex": - case "dexterity": - if (typeof this.statBuildOrder[i][1] === "string") { - switch (this.statBuildOrder[i][1]) { - case "block": - if (me.expansion) { - if (this.getBlock() < this.block) { - return this.useStats(sdk.stats.Dexterity, this.requiredDex()); - } - } - - break; - case "all": - return this.useStats(sdk.stats.Dexterity); - default: - break; - } - } else { - hardStats = this.getHardStats(sdk.stats.Dexterity); - - if (hardStats < this.statBuildOrder[i][1]) { - return this.useStats(sdk.stats.Dexterity, this.statBuildOrder[i][1] - hardStats); - } - } - - break; - case sdk.stats.Vitality: - case "v": - case "vit": - case "vitality": - if (typeof this.statBuildOrder[i][1] === "string") { - switch (this.statBuildOrder[i][1]) { - case "all": - return this.useStats(sdk.stats.Vitality); - default: - break; - } - } else { - hardStats = this.getHardStats(sdk.stats.Vitality); - - if (hardStats < this.statBuildOrder[i][1]) { - return this.useStats(sdk.stats.Vitality, this.statBuildOrder[i][1] - hardStats); - } - } - - break; - } - } - - return false; - }; - - this.remaining = 0; - this.count = 0; - - this.init = function (statBuildOrder, save = 0, block = 0, bulkStat = true) { - this.statBuildOrder = statBuildOrder; - this.save = save; - this.block = block; - this.bulkStat = bulkStat; - - if (!this.statBuildOrder || !this.statBuildOrder.length) { - print("AutoStat: No build array specified"); - - return false; - } - - while (me.getStat(sdk.stats.StatPts) > this.save) { - this.addStatPoint(); - delay(150 + me.ping); // spending multiple single stat at a time with short delay may cause r/d - - // break out of loop if we have stat points available but finished allocating as configured - if (me.getStat(sdk.stats.StatPts) === this.remaining) { - this.count += 1; - } - - if (this.count > 2) { - break; - } - } - - print("AutoStat: Finished allocating stat points"); - - return true; - }; - - return true; -}; diff --git a/d2bs/kolbot/libs/common/CollMap.js b/d2bs/kolbot/libs/common/CollMap.js deleted file mode 100644 index c0dd0f802..000000000 --- a/d2bs/kolbot/libs/common/CollMap.js +++ /dev/null @@ -1,182 +0,0 @@ -/** -* @filename CollMap.js -* @author kolton -* @desc manipulate map collision data -* -*/ - -const CollMap = new function () { - this.rooms = []; - this.maps = []; - - this.getNearbyRooms = function (x, y) { - let room = getRoom(x, y); - if (!room) return false; - - let rooms = room.getNearby(); - if (!rooms) return false; - - for (let i = 0; i < rooms.length; i += 1) { - if (this.getRoomIndex(rooms[i].x * 5 + rooms[i].xsize / 2, rooms[i].y * 5 + rooms[i].ysize / 2, true) === undefined) { - this.addRoom(rooms[i]); - } - } - - return true; - }; - - this.addRoom = function (x, y) { - let room = x instanceof Room ? x : getRoom(x, y); - - // Coords are not in the returned room. - if (arguments.length === 2 && !this.coordsInRoom(x, y, room)) { - return false; - } - - let coll = !!room ? room.getCollision() : null; - - if (coll) { - this.rooms.push({x: room.x, y: room.y, xsize: room.xsize, ysize: room.ysize}); - this.maps.push(coll); - - return true; - } - - return false; - }; - - this.getColl = function (x, y, cacheOnly) { - let index = this.getRoomIndex(x, y, cacheOnly); - - if (index === undefined) { - return 5; - } - - let j = x - this.rooms[index].x * 5; - let i = y - this.rooms[index].y * 5; - - if (this.maps[index] !== undefined && this.maps[index][i] !== undefined && this.maps[index][i][j] !== undefined) { - return this.maps[index][i][j]; - } - - return 5; - }; - - this.getRoomIndex = function (x, y, cacheOnly) { - this.rooms.length > 25 && this.reset(); - - let i; - - for (i = 0; i < this.rooms.length; i += 1) { - if (this.coordsInRoom(x, y, this.rooms[i])) { - return i; - } - } - - if (!cacheOnly && this.addRoom(x, y)) { - return i; - } - - return undefined; - }; - - this.coordsInRoom = function (x, y, room) { - if (room && x >= room.x * 5 && x < room.x * 5 + room.xsize && y >= room.y * 5 && y < room.y * 5 + room.ysize) { - return true; - } - - return false; - }; - - this.reset = function () { - this.rooms = []; - this.maps = []; - }; - - // Check collision between unitA and unitB. true = collision present, false = collision not present - // If checking for blocking collisions (0x1, 0x4), true means blocked, false means not blocked - this.checkColl = function (unitA, unitB, coll, thickness) { - thickness === undefined && (thickness = 1); - - let i, k, l, cx, cy; - let angle = Math.atan2(unitA.y - unitB.y, unitA.x - unitB.x); - let distance = Math.round(getDistance(unitA, unitB)); - - for (i = 1; i < distance; i += 1) { - cx = Math.round((Math.cos(angle)) * i + unitB.x); - cy = Math.round((Math.sin(angle)) * i + unitB.y); - - // check thicker line - for (k = cx - thickness; k <= cx + thickness; k += 1) { - for (l = cy - thickness; l <= cy + thickness; l += 1) { - if (this.getColl(k, l, false) & coll) { - return true; - } - } - } - } - - return false; - }; - - this.getTelePoint = function (room) { - // returns {x, y, distance} of a valid point with lowest distance from room center - // distance is from room center, handy for keeping bot from trying to teleport on walls - - if (!room) throw new Error("Invalid room passed to getTelePoint"); - - let roomx = room.x * 5, roomy = room.y * 5; - - if (getCollision(room.area, roomx, roomy) & 1) { - let collision = room.getCollision(), validTiles = []; - let aMid = Math.round(collision.length / 2), bMid = Math.round(collision[0].length / 2); - - for (let a = 0; a < collision.length; a++) { - for (let b = 0; b < collision[a].length; b++) { - if (!(collision[a][b] & 1)) { - validTiles.push({x: roomx + b - bMid, y: roomy + a - aMid, distance: getDistance(0, 0, a - aMid, b - bMid)}); - } - } - } - - if (validTiles.length) { - validTiles.sort((a, b) => a.distance - b.distance); - - return validTiles[0]; - } - - return null; - } - - return {x: roomx, y: roomy, distance: 0}; - }; - - this.getRandCoordinate = function (cX, xmin, xmax, cY, ymin, ymax, factor = 1) { - // returns randomized {x, y} object with valid coordinates - let coordX, coordY; - let retry = 0; - - do { - if (retry > 30) { - print("failed to get valid coordinate"); - coordX = cX; - coordY = cY; - - break; - } - - coordX = cX + factor * rand(xmin, xmax); - coordY = cY + factor * rand(ymin, ymax); - - if (cX === coordX && cY === coordY) { // recalculate if same coordiante - coordX = 0; - continue; - } - - retry++; - } while (getCollision(me.area, coordX, coordY) & 1); - - // print("Move " + retry + " from (" + cX + ", " + cY + ") to (" + coordX + ", " + coordY + ")"); - return {x: coordX, y: coordY}; - }; -}; diff --git a/d2bs/kolbot/libs/common/Common.js b/d2bs/kolbot/libs/common/Common.js deleted file mode 100644 index 68df34b66..000000000 --- a/d2bs/kolbot/libs/common/Common.js +++ /dev/null @@ -1,1478 +0,0 @@ -/** -* @filename Common.js -* @author theBGuy -* @desc collection of functions shared between muliple scripts -* -*/ - -const Common = { - Questing: { - activateStone: function (stone) { - for (let i = 0; i < 3; i++) { - // don't use tk if we are right next to it - let useTK = (stone.distance > 5 && Skill.useTK(stone) && i === 0); - if (useTK) { - stone.distance > 13 && Attack.getIntoPosition(stone, 13, sdk.collision.Ranged); - if (!Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, stone)) { - console.debug("Failed to tk: attempt: " + i); - continue; - } - } else { - [(stone.x + 1), (stone.y + 2)].distance > 5 && Pather.moveTo(stone.x + 1, stone.y + 2, 3); - Misc.click(0, 0, stone); - } - - if (Misc.poll(() => stone.mode, 1000, 50)) { - return true; - } else { - Packet.flash(me.gid); - } - } - - // Click to stop walking in case we got stuck - !me.idle && Misc.click(0, 0, me.x, me.y); - - return false; - }, - - cain: function () { - MainLoop: - while (true) { - switch (true) { - case !Game.getItem(sdk.quest.item.ScrollofInifuss) && !Game.getItem(sdk.quest.item.KeytotheCairnStones) && !Misc.checkQuest(sdk.quest.id.TheSearchForCain, 4): - Pather.useWaypoint(sdk.areas.DarkWood, true); - Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.DarkWood, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) { - throw new Error("Failed to move to Tree of Inifuss"); - } - - let tree = Game.getObject(sdk.quest.chest.InifussTree); - !!tree && tree.distance > 5 && Pather.moveToUnit(tree); - Misc.openChest(tree); - let scroll = Misc.poll(() => Game.getItem(sdk.quest.item.ScrollofInifuss), 1000, 100); - - Pickit.pickItem(scroll); - Town.goToTown(); - Town.npcInteract("Akara"); - - break; - case Game.getItem(sdk.quest.item.ScrollofInifuss): - Town.goToTown(1); - Town.npcInteract("Akara"); - - break; - case Game.getItem(sdk.quest.item.KeytotheCairnStones) && !me.inArea(sdk.areas.StonyField): - Pather.journeyTo(sdk.areas.StonyField); - Precast.doPrecast(true); - - break; - case Game.getItem(sdk.quest.item.KeytotheCairnStones) && me.inArea(sdk.areas.StonyField): - Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 10, 10, false, true); - Attack.securePosition(me.x, me.y, 40, 3000, true); - Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Object, sdk.quest.chest.StoneAlpha, null, null, true); - let stones = [ - Game.getObject(sdk.quest.chest.StoneAlpha), - Game.getObject(sdk.quest.chest.StoneBeta), - Game.getObject(sdk.quest.chest.StoneGamma), - Game.getObject(sdk.quest.chest.StoneDelta), - Game.getObject(sdk.quest.chest.StoneLambda) - ]; - - while (stones.some((stone) => !stone.mode)) { - for (let i = 0; i < stones.length; i++) { - let stone = stones[i]; - - if (this.activateStone(stone)) { - stones.splice(i, 1); - i--; - } - delay(10); - } - } - - let tick = getTickCount(); - // wait up to two minutes - while (getTickCount() - tick < Time.minutes(2)) { - if (Pather.getPortal(sdk.areas.Tristram)) { - Pather.usePortal(sdk.areas.Tristram); - - break; - } - } - - break; - case me.inArea(sdk.areas.Tristram) && !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed): - let gibbet = Game.getObject(sdk.quest.chest.CainsJail); - - if (gibbet && !gibbet.mode) { - Pather.moveTo(gibbet.x, gibbet.y); - if (Misc.poll(() => Misc.openChest(gibbet), 2000, 100)) { - Town.goToTown(1); - Town.npcInteract("Akara") && console.log("Akara done"); - } - } - - break; - default: - break MainLoop; - } - } - - return true; - }, - - smith: function () { - if (!Pather.moveToPreset(sdk.areas.Barracks, sdk.unittype.Object, sdk.quest.chest.MalusHolder)) { - throw new Error("Failed to move to the Smith"); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.TheSmith)); - let malusChest = Game.getObject(sdk.quest.chest.MalusHolder); - !!malusChest && malusChest.distance > 5 && Pather.moveToUnit(malusChest); - Misc.openChest(malusChest); - let malus = Misc.poll(() => Game.getItem(sdk.quest.item.HoradricMalus), 1000, 100); - Pickit.pickItem(malus); - Town.goToTown(); - Town.npcInteract("Charsi"); - } - }, - - Cows: { - buildCowRooms: function () { - let finalRooms = []; - let indexes = []; - - let kingPreset = Game.getPresetMonster(sdk.areas.MooMooFarm, sdk.monsters.preset.TheCowKing); - let badRooms = getRoom(kingPreset.roomx * 5 + kingPreset.x, kingPreset.roomy * 5 + kingPreset.y).getNearby(); - - for (let i = 0; i < badRooms.length; i += 1) { - let badRooms2 = badRooms[i].getNearby(); - - for (let j = 0; j < badRooms2.length; j += 1) { - if (indexes.indexOf(badRooms2[j].x + "" + badRooms2[j].y) === -1) { - indexes.push(badRooms2[j].x + "" + badRooms2[j].y); - } - } - } - - let room = getRoom(); - - do { - if (indexes.indexOf(room.x + "" + room.y) === -1) { - finalRooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]); - } - } while (room.getNext()); - - return finalRooms; - }, - - clearCowLevel: function () { - function roomSort(a, b) { - return getDistance(myRoom[0], myRoom[1], a[0], a[1]) - getDistance(myRoom[0], myRoom[1], b[0], b[1]); - } - - Config.MFLeader && Pather.makePortal() && say("cows"); - - let myRoom; - let rooms = this.buildCowRooms(); - - while (rooms.length > 0) { - // get the first room + initialize myRoom var - !myRoom && (room = getRoom(me.x, me.y)); - - if (room) { - // use previous room to calculate distance - if (room instanceof Array) { - myRoom = [room[0], room[1]]; - } else { - // create a new room to calculate distance (first room, done only once) - myRoom = [room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]; - } - } - - rooms.sort(roomSort); - let room = rooms.shift(); - let result = Pather.getNearestWalkable(room[0], room[1], 10, 2); - - if (result) { - Pather.moveTo(result[0], result[1], 3); - if (!Attack.clear(30)) return false; - } - } - - return true; - }, - }, - - Diablo: { - diabloSpawned: false, - diaWaitTime: Time.seconds(30), - clearRadius: 30, - done: false, - waitForGlow: false, - sealOrder: [], - vizLayout: -1, - seisLayout: -1, - infLayout: -1, - entranceCoords: {x: 7790, y: 5544}, - starCoords: {x: 7791, y: 5293}, - // path coordinates - entranceToStar: [7794, 5517, 7791, 5491, 7768, 5459, 7775, 5424, 7817, 5458, 7777, 5408, 7769, 5379, 7777, 5357, 7809, 5359, 7805, 5330, 7780, 5317, 7791, 5293], - starToVizA: [7759, 5295, 7734, 5295, 7716, 5295, 7718, 5276, 7697, 5292, 7678, 5293, 7665, 5276, 7662, 5314], - starToVizB: [7759, 5295, 7734, 5295, 7716, 5295, 7701, 5315, 7666, 5313, 7653, 5284], - starToSeisA: [7781, 5259, 7805, 5258, 7802, 5237, 7776, 5228, 7775, 5205, 7804, 5193, 7814, 5169, 7788, 5153], - starToSeisB: [7781, 5259, 7805, 5258, 7802, 5237, 7776, 5228, 7811, 5218, 7807, 5194, 7779, 5193, 7774, 5160, 7803, 5154], - starToInfA: [7809, 5268, 7834, 5306, 7852, 5280, 7852, 5310, 7869, 5294, 7895, 5295, 7919, 5290], - starToInfB: [7809, 5268, 7834, 5306, 7852, 5280, 7852, 5310, 7869, 5294, 7895, 5274, 7927, 5275, 7932, 5297, 7923, 5313], - // check for strays array - cleared: [], - - diabloLightsEvent: function (bytes = []) { - if (me.inArea(sdk.areas.ChaosSanctuary) && bytes && bytes.length === 2 && bytes[0] === 0x89 && bytes[1] === 0x0C) { - Common.Diablo.diabloSpawned = true; - } - }, - - sort: function (a, b) { - if (Config.BossPriority) { - if ((a.isSuperUnique) && (b.isSuperUnique)) return getDistance(me, a) - getDistance(me, b); - if (a.isSuperUnique) return -1; - if (b.isSuperUnique) return 1; - } - - // Entrance to Star / De Seis - if (me.y > 5325 || me.y < 5260) return (a.y > b.y ? -1 : 1); - // Vizier - if (me.x < 7765) return (a.x > b.x ? -1 : 1); - // Infector - if (me.x > 7825) return (!checkCollision(me, a, sdk.collision.BlockWall) && a.x < b.x ? -1 : 1); - - return getDistance(me, a) - getDistance(me, b); - }, - - getLayout: function (seal, value) { - let sealPreset = Game.getPresetObject(sdk.areas.ChaosSanctuary, seal); - if (!seal) throw new Error("Seal preset not found. Can't continue."); - - if (sealPreset.roomy * 5 + sealPreset.y === value - || sealPreset.roomx * 5 + sealPreset.x === value) { - return 1; - } - - return 2; - }, - - initLayout: function () { - // 1 = "Y", 2 = "L" - this.vizLayout = this.getLayout(sdk.objects.DiabloSealVizier, 5275); - // 1 = "2", 2 = "5" - this.seisLayout = this.getLayout(sdk.objects.DiabloSealSeis, 7773); - // 1 = "I", 2 = "J" - this.infLayout = this.getLayout(sdk.objects.DiabloSealInfector, 7893); - }, - - followPath: function (path) { - if (Config.Diablo.Fast) { - let len = path.length; - let lastNode = {x: path[len - 2], y: path[len - 1]}; - Pather.moveToUnit(lastNode); - return; - } - - for (let i = 0; i < path.length; i += 2) { - this.cleared.length > 0 && this.clearStrays(); - - // no monsters at the next node, skip it - if ([path[i], path[i + 1]].distance < 40 && [path[i], path[i + 1]].mobCount({range: 35}) === 0) { - continue; - } - - Pather.moveTo(path[i], path[i + 1], 3, getDistance(me, path[i], path[i + 1]) > 50); - Attack.clear(this.clearRadius, 0, false, Common.Diablo.sort); - - // Push cleared positions so they can be checked for strays - this.cleared.push([path[i], path[i + 1]]); - - // After 5 nodes go back 2 nodes to check for monsters - if (i === 10 && path.length > 16) { - path = path.slice(6); - i = 0; - } - } - }, - - clearStrays: function () { - let oldPos = { x: me.x, y: me.y }; - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.attackable) { - for (let i = 0; i < this.cleared.length; i += 1) { - if (getDistance(monster, this.cleared[i][0], this.cleared[i][1]) < 30 && Attack.validSpot(monster.x, monster.y)) { - Pather.moveToUnit(monster); - Attack.clear(15, 0, false, Common.Diablo.sort); - - break; - } - } - } - } while (monster.getNext()); - } - - getDistance(me, oldPos.x, oldPos.y) > 5 && Pather.moveTo(oldPos.x, oldPos.y); - - return true; - }, - - runSeals: function (sealOrder, openSeals = true) { - print("seal order: " + sealOrder); - this.sealOrder = sealOrder; - let seals = { - 1: () => this.vizierSeal(openSeals), - 2: () => this.seisSeal(openSeals), - 3: () => this.infectorSeal(openSeals), - "vizier": () => this.vizierSeal(openSeals), - "seis": () => this.seisSeal(openSeals), - "infector": () => this.infectorSeal(openSeals), - }; - sealOrder.forEach(seal => {seals[seal]();}); - }, - - tkSeal: function (seal) { - if (!Skill.useTK(seal)) return false; - - for (let i = 0; i < 5; i++) { - seal.distance > 20 && Attack.getIntoPosition(seal, 18, sdk.collision.WallOrRanged); - - if (Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, seal) && Misc.poll(() => seal.mode, 1000, 100)) { - break; - } - } - - return !!seal.mode; - }, - - openSeal: function (classid) { - let seal; - let warn = Config.PublicMode && [sdk.objects.DiabloSealVizier, sdk.objects.DiabloSealSeis, sdk.objects.DiabloSealInfector].includes(classid) && Loader.scriptName() === "Diablo"; - let usetk = (Skill.haveTK && (classid !== sdk.objects.DiabloSealSeis || this.seisLayout !== 1)); - let seisSeal = classid === sdk.objects.DiabloSealSeis; - - for (let i = 0; i < 5; i++) { - if (!seal) { - usetk - ? Pather.moveNearPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, classid, 15) - : Pather.moveToPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, classid, seisSeal ? 5 : 2, seisSeal ? 5 : 0); - seal = Misc.poll(() => Game.getObject(classid), 1000, 100); - } - - if (!seal) { - console.debug("Couldn't find seal: " + classid); - return false; - } - - if (seal.mode) { - warn && say(Config.Diablo.SealWarning); - return true; - } - - // Clear around Infector seal, Any leftover abyss knights casting decrep is bad news with Infector - if (([sdk.objects.DiabloSealInfector, sdk.objects.DiabloSealInfector2].includes(classid) || i > 1) && me.getMobCount() > 1) { - Attack.clear(15); - // Move back to seal - usetk ? Pather.moveNearUnit(seal, 15) : Pather.moveToUnit(seal, seisSeal ? 5 : 2, seisSeal ? 5 : 0); - } - - if (usetk && this.tkSeal(seal)) { - return seal.mode; - } else { - usetk && (usetk = false); - - if (classid === sdk.objects.DiabloSealInfector && me.assassin && this.infLayout === 1) { - if (Config.UseTraps) { - let check = ClassAttack.checkTraps({x: 7899, y: 5293}); - check && ClassAttack.placeTraps({x: 7899, y: 5293}, check); - } - } - - seisSeal ? Misc.poll(function () { - // stupid diablo shit, walk around the de-seis seal clicking it until we find "the spot"...sigh - if (!seal.mode) { - Pather.walkTo(seal.x + (rand(-1, 1)), seal.y + (rand(-1, 1))); - clickUnitAndWait(0, 0, seal) || seal.interact(); - } - return !!seal.mode; - }, 3000, 60) : seal.interact(); - - // de seis optimization - if (seisSeal && Attack.validSpot(seal.x + 15, seal.y)) { - Pather.walkTo(seal.x + 15, seal.y); - } else { - Pather.walkTo(seal.x - 5, seal.y - 5); - } - } - - delay(seisSeal ? 1000 + me.ping : 500 + me.ping); - - if (seal.mode) { - break; - } - } - - return (!!seal && seal.mode); - }, - - vizierSeal: function (openSeal = true) { - print("Viz layout " + Common.Diablo.vizLayout); - let path = (Common.Diablo.vizLayout === 1 ? this.starToVizA : this.starToVizB); - let distCheck = { - x: path[path.length - 2], - y: (path.last()) - }; - - if (Config.Diablo.SealLeader || Config.Diablo.Fast) { - Common.Diablo.vizLayout === 1 ? Pather.moveTo(7708, 5269) : Pather.moveTo(7647, 5267); - Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, 35, 3000, true); - Config.Diablo.SealLeader && Pather.makePortal() && say("in"); - } - - distCheck.distance > 30 && this.followPath(Common.Diablo.vizLayout === 1 ? this.starToVizA : this.starToVizB); - - if (openSeal && (!Common.Diablo.openSeal(sdk.objects.DiabloSealVizier2) || !Common.Diablo.openSeal(sdk.objects.DiabloSealVizier))) { - throw new Error("Failed to open Vizier seals."); - } - - delay(1 + me.ping); - Common.Diablo.vizLayout === 1 ? Pather.moveTo(7691, 5292) : Pather.moveTo(7695, 5316); - - if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.GrandVizierofChaos))) { - throw new Error("Failed to kill Vizier"); - } - - Config.Diablo.SealLeader && say("out"); - - return true; - }, - - seisSeal: function (openSeal = true) { - print("Seis layout " + Common.Diablo.seisLayout); - let path = (Common.Diablo.seisLayout === 1 ? this.starToSeisA : this.starToSeisB); - let distCheck = { - x: path[path.length - 2], - y: (path.last()) - }; - - if (Config.Diablo.SealLeader || Config.Diablo.Fast) { - Common.Diablo.seisLayout === 1 ? Pather.moveTo(7767, 5147) : Pather.moveTo(7820, 5147); - Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, 35, 3000, true); - Config.Diablo.SealLeader && Pather.makePortal() && say("in"); - } - - distCheck.distance > 30 && this.followPath(Common.Diablo.seisLayout === 1 ? this.starToSeisA : this.starToSeisB); - - if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealSeis)) throw new Error("Failed to open de Seis seal."); - Common.Diablo.seisLayout === 1 ? Pather.moveTo(7798, 5194) : Pather.moveTo(7796, 5155); - if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.LordDeSeis))) throw new Error("Failed to kill de Seis"); - - Config.Diablo.SealLeader && say("out"); - - return true; - }, - - infectorSeal: function (openSeal = true) { - Precast.doPrecast(true); - print("Inf layout " + Common.Diablo.infLayout); - let path = (Common.Diablo.infLayout === 1 ? this.starToInfA : this.starToInfB); - let distCheck = { - x: path[path.length - 2], - y: (path.last()) - }; - - if (Config.Diablo.SealLeader || Config.Diablo.Fast) { - Common.Diablo.infLayout === 1 ? Pather.moveTo(7860, 5314) : Pather.moveTo(7909, 5317); - Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, 35, 3000, true); - Config.Diablo.SealLeader && Pather.makePortal() && say("in"); - } - - distCheck.distance > 70 && this.followPath(Common.Diablo.infLayout === 1 ? this.starToInfA : this.starToInfB); - - if (Config.Diablo.Fast) { - if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealInfector2)) throw new Error("Failed to open Infector seals."); - if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealInfector)) throw new Error("Failed to open Infector seals."); - - if (Common.Diablo.infLayout === 1) { - (me.sorceress || me.assassin) && Pather.moveTo(7876, 5296); - delay(1 + me.ping); - } else { - delay(1 + me.ping); - Pather.moveTo(7928, 5295); - } - - if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.InfectorofSouls))) throw new Error("Failed to kill Infector"); - } else { - if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealInfector)) throw new Error("Failed to open Infector seals."); - - if (Common.Diablo.infLayout === 1) { - (me.sorceress || me.assassin) && Pather.moveTo(7876, 5296); - delay(1 + me.ping); - } else { - delay(1 + me.ping); - Pather.moveTo(7928, 5295); - } - - if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.InfectorofSouls))) throw new Error("Failed to kill Infector"); - if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealInfector2)) throw new Error("Failed to open Infector seals."); - // wait until seal has been popped to avoid missing diablo due to wait time ending before he spawns, happens if leader does town chores after seal boss - !openSeal && [3, "infector"].includes(Common.Diablo.sealOrder.last()) && Misc.poll(() => { - if (Common.Diablo.diabloSpawned) return true; - - let lastSeal = Game.getObject(sdk.objects.DiabloSealInfector2); - if (lastSeal && lastSeal.mode) { - return true; - } - return false; - }, Time.minutes(3), 1000); - } - - Config.Diablo.SealLeader && say("out"); - - return true; - }, - - hammerdinPreAttack: function (name, amount = 5) { - if (me.paladin && Config.AttackSkill[1] === sdk.skills.BlessedHammer) { - let target = Game.getMonster(name); - - if (!target || !target.attackable) return true; - - let positions = [[6, 11], [0, 8], [8, -1], [-9, 2], [0, -11], [8, -8]]; - - for (let i = 0; i < positions.length; i += 1) { - // check if we can move there - if (Attack.validSpot(target.x + positions[i][0], target.y + positions[i][1])) { - Pather.moveTo(target.x + positions[i][0], target.y + positions[i][1]); - Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); - - for (let n = 0; n < amount; n += 1) { - Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Left); - } - - return true; - } - } - } - - return false; - }, - - preattack: function (id) { - let coords = (() => { - switch (id) { - case getLocaleString(sdk.locale.monsters.GrandVizierofChaos): - return Common.Diablo.vizLayout === 1 ? [7676, 5295] : [7684, 5318]; - case getLocaleString(sdk.locale.monsters.LordDeSeis): - return Common.Diablo.seisLayout === 1 ? [7778, 5216] : [7775, 5208]; - case getLocaleString(sdk.locale.monsters.InfectorofSouls): - return Common.Diablo.infLayout === 1 ? [7913, 5292] : [7915, 5280]; - default: - return []; - } - })(); - if (!coords.length) return false; - - switch (me.classid) { - case sdk.player.class.Sorceress: - if ([sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb, sdk.skills.FireWall].includes(Config.AttackSkill[1])) { - me.skillDelay && delay(500); - Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, coords[0], coords[1]); - - return true; - } - - break; - case sdk.player.class.Paladin: - return this.hammerdinPreAttack(id, 8); - case sdk.player.class.Assassin: - if (Config.UseTraps) { - let trapCheck = ClassAttack.checkTraps({x: coords[0], y: coords[1]}); - - if (trapCheck) { - ClassAttack.placeTraps({x: coords[0], y: coords[1]}, 5); - - return true; - } - } - - break; - } - - return false; - }, - - getBoss: function (name) { - let glow = Game.getObject(sdk.objects.SealGlow); - - if (this.waitForGlow) { - while (true) { - if (!this.preattack(name)) { - delay(500); - } - - glow = Game.getObject(sdk.objects.SealGlow); - - if (glow) { - break; - } - } - } - - for (let i = 0; i < 16; i += 1) { - let boss = Game.getMonster(name); - - if (boss) { - Common.Diablo.hammerdinPreAttack(name, 8); - return (Config.Diablo.Fast ? Attack.kill(name) : Attack.clear(40, 0, name, this.sort)); - } - - delay(250); - } - - return !!glow; - }, - - moveToStar: function () { - switch (me.classid) { - case sdk.player.class.Amazon: - case sdk.player.class.Sorceress: - case sdk.player.class.Necromancer: - case sdk.player.class.Assassin: - return Pather.moveNear(7791, 5293, (me.sorceress ? 35 : 25), {returnSpotOnError: true}); - case sdk.player.class.Paladin: - case sdk.player.class.Druid: - case sdk.player.class.Barbarian: - return Pather.moveTo(7788, 5292); - } - - return false; - }, - - diabloPrep: function () { - if (Config.Diablo.SealLeader) { - Pather.moveTo(7763, 5267); - Pather.makePortal() && say("in"); - Pather.moveTo(7788, 5292); - } - - this.moveToStar(); - - let tick = getTickCount(); - - while (getTickCount() - tick < this.diaWaitTime) { - if (getTickCount() - tick >= Time.seconds(8)) { - switch (me.classid) { - case sdk.player.class.Sorceress: - if ([sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb, sdk.skills.FireWall].includes(Config.AttackSkill[1])) { - Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 7793 + rand(-1, 1), 5293); - } - - delay(500); - - break; - case sdk.player.class.Paladin: - Skill.setSkill(Config.AttackSkill[2]); - Config.AttackSkill[1] === sdk.skills.BlessedHammer && Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Left); - - break; - case sdk.player.class.Druid: - if ([sdk.skills.Tornado, sdk.skills.Fissure, sdk.skills.Volcano].includes(Config.AttackSkill[3])) { - Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 7793 + rand(-1, 1), 5293); - - break; - } - - delay(500); - - break; - case sdk.player.class.Assassin: - if (Config.UseTraps) { - let trapCheck = ClassAttack.checkTraps({x: 7793, y: 5293}); - trapCheck && ClassAttack.placeTraps({x: 7793, y: 5293, classid: sdk.monsters.Diablo}, trapCheck); - } - - Config.AttackSkill[1] === sdk.skills.ShockWeb && Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 7793, 5293); - - delay(500); - - break; - default: - delay(500); - - break; - } - } else { - delay(500); - } - - if (Game.getMonster(sdk.monsters.Diablo)) { - return true; - } - } - - throw new Error("Diablo not found"); - }, - }, - - Ancients: { - altarSpot: {x: 10047, y: 12622}, - - canAttack: function () { - let ancient = Game.getMonster(); - - if (ancient) { - do { - if (!ancient.getParent() && !Attack.canAttack(ancient)) { - console.log("Can't attack ancients"); - return false; - } - } while (ancient.getNext()); - } - - return true; - }, - - touchAltar: function () { - let tick = getTickCount(); - - while (getTickCount() - tick < 5000) { - if (Game.getObject(sdk.objects.AncientsAltar)) { - break; - } - - delay(20 + me.ping); - } - - let altar = Game.getObject(sdk.objects.AncientsAltar); - - if (altar) { - while (altar.mode !== sdk.objects.mode.Active) { - Pather.moveToUnit(altar); - altar.interact(); - delay(200 + me.ping); - me.cancel(); - } - - // wait for ancients to spawn - while (!Game.getMonster(sdk.monsters.TalictheDefender)) { - delay(250 + me.ping); - } - - return true; - } - - return false; - }, - - checkStatues: function () { - let statues = getUnits(sdk.unittype.Object) - .filter(u => [sdk.objects.KorlictheProtectorStatue, sdk.objects.TalictheDefenderStatue, sdk.objects.MadawctheGuardianStatue].includes(u.classid) - && u.mode === sdk.objects.mode.Active); - return statues.length === 3; - }, - - checkCorners: function () { - let pos = [ - { x: 10036, y: 12592 }, { x: 10066, y: 12589 }, - { x: 10065, y: 12623 }, { x: 10058, y: 12648 }, - { x: 10040, y: 12660 }, { x: 10036, y: 12630 }, - { x: 10038, y: 12611 } - ]; - Pather.moveToUnit(this.altarSpot); - if (!this.checkStatues()) { - return pos.forEach((node) => { - // no mobs at that next, skip it - if ([node.x, node.y].distance < 35 && [node.x, node.y].mobCount({range: 30}) === 0) { - return; - } - Pather.moveTo(node.x, node.y); - Attack.clear(30); - }); - } - - return true; - }, - - killAncients: function (checkQuest = false) { - let retry = 0; - Pather.moveToUnit(this.altarSpot); - - while (!this.checkStatues()) { - if (retry > 5) { - console.log("Failed to kill anicents."); - - break; - } - - Attack.clearClassids(sdk.monsters.KorlictheProtector, sdk.monsters.TalictheDefender, sdk.monsters.MadawctheGuardian); - delay(1000); - - if (checkQuest) { - if (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed)) { - break; - } else { - console.log("Failed to kill anicents. Attempt: " + retry); - } - } - - this.checkCorners(); - retry++; - } - }, - - ancientsPrep: function () { - Town.goToTown(); - Town.fillTome(sdk.items.TomeofTownPortal); - [sdk.items.StaminaPotion, sdk.items.AntidotePotion, sdk.items.ThawingPotion].forEach(p => Town.buyPots(10, p, true)); - Town.buyPotions(); - Pather.usePortal(sdk.areas.ArreatSummit, me.name); - }, - - startAncients: function (preTasks = false, checkQuest = false) { - let retry = 0; - Pather.moveToUnit(this.altarSpot); - this.touchAltar(); - - while (!this.canAttack()) { - if (retry > 10) throw new Error("I think I'm unable to complete ancients, I've rolled them 10 times"); - preTasks ? this.ancientsPrep() : Pather.makePortal(); - Pather.moveToUnit(this.altarSpot); - this.touchAltar(); - retry++; - } - - this.killAncients(checkQuest); - }, - }, - - Baal: { - checkHydra: function () { - let hydra = Game.getMonster(getLocaleString(sdk.locale.monsters.Hydra)); - if (hydra) { - do { - if (hydra.mode !== sdk.monsters.mode.Dead && hydra.getStat(sdk.stats.Alignment) !== 2) { - Pather.moveTo(15072, 5002); - while (hydra.mode !== sdk.monsters.mode.Dead) { - delay(500); - if (!copyUnit(hydra).x) { - break; - } - } - - break; - } - } while (hydra.getNext()); - } - - return true; - }, - - checkThrone: function (clear = true) { - let monster = Game.getMonster(); - - if (monster) { - do { - if (monster.attackable && monster.y < 5080) { - switch (monster.classid) { - case sdk.monsters.WarpedFallen: - case sdk.monsters.WarpedShaman: - return 1; - case sdk.monsters.BaalSubjectMummy: - case sdk.monsters.BaalColdMage: - return 2; - case sdk.monsters.Council4: - return 3; - case sdk.monsters.VenomLord2: - return 4; - case sdk.monsters.ListerTheTormenter: - return 5; - default: - if (clear) { - Attack.getIntoPosition(monster, 10, sdk.collision.Ranged); - Attack.clear(15); - } - - return false; - } - } - } while (monster.getNext()); - } - - return false; - }, - - clearThrone: function () { - if (!Game.getMonster(sdk.monsters.ThroneBaal)) return true; - - let monList = []; - - if (Config.AvoidDolls) { - let mon = Game.getMonster(sdk.monsters.SoulKiller); - - if (mon) { - do { - // exclude dolls from the list - if (!mon.isDoll && mon.x >= 15072 && mon.x <= 15118 && mon.y >= 5002 && mon.y <= 5079 && mon.attackable && !Attack.skipCheck(mon)) { - monList.push(copyUnit(mon)); - } - } while (mon.getNext()); - } - - if (monList.length > 0) { - return Attack.clearList(monList); - } - } - - let pos = [ - { x: 15097, y: 5054 }, { x: 15079, y: 5014 }, - { x: 15085, y: 5053 }, { x: 15085, y: 5040 }, - { x: 15098, y: 5040 }, { x: 15099, y: 5022 }, - { x: 15086, y: 5024 }, { x: 15079, y: 5014 } - ]; - return pos.forEach((node) => { - // no mobs at that next, skip it - if ([node.x, node.y].distance < 35 && [node.x, node.y].mobCount({range: 30}) === 0) { - return; - } - Pather.moveTo(node.x, node.y); - Attack.clear(30); - }); - }, - - preattack: function () { - switch (me.classid) { - case sdk.player.class.Sorceress: - if ([sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb, sdk.skills.FireWall].includes(Config.AttackSkill[1])) { - if (me.getState(sdk.states.SkillDelay)) { - delay(50); - } else { - Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 15094 + rand(-1, 1), 5024); - } - } - - break; - case sdk.player.class.Paladin: - if (Config.AttackSkill[3] === sdk.skills.BlessedHammer) { - Config.AttackSkill[4] > 0 && Skill.setSkill(Config.AttackSkill[4], sdk.skills.hand.Right); - - return Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Left); - } - - break; - case sdk.player.class.Druid: - if ([sdk.skills.Tornado, sdk.skills.Fissure, sdk.skills.Volcano].includes(Config.AttackSkill[3])) { - Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Right, 15094 + rand(-1, 1), 5029); - - return true; - } - - break; - case sdk.player.class.Assassin: - if (Config.UseTraps) { - let check = ClassAttack.checkTraps({x: 15094, y: 5028}); - - if (check) { - return ClassAttack.placeTraps({x: 15094, y: 5028}, 5); - } - } - - if (Config.AttackSkill[3] === sdk.skills.ShockWeb) { - return Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Right, 15094, 5028); - } - - break; - } - - return false; - }, - - clearWaves: function () { - Pather.moveTo(15094, me.paladin ? 5029 : 5038); - - let tick = getTickCount(); - let totalTick = getTickCount(); - - MainLoop: - while (true) { - if (!Game.getMonster(sdk.monsters.ThroneBaal)) return true; - - switch (this.checkThrone()) { - case 1: - Attack.clearClassids(sdk.monsters.WarpedFallen, sdk.monsters.WarpedShaman) && (tick = getTickCount()); - - break; - case 2: - Attack.clearClassids(sdk.monsters.BaalSubjectMummy, sdk.monsters.BaalColdMage) && (tick = getTickCount()); - - break; - case 3: - Attack.clearClassids(sdk.monsters.Council4) && (tick = getTickCount()); - this.checkHydra() && (tick = getTickCount()); - - break; - case 4: - Attack.clearClassids(sdk.monsters.VenomLord2) && (tick = getTickCount()); - - break; - case 5: - Attack.clearClassids(sdk.monsters.ListerTheTormenter, sdk.monsters.Minion1, sdk.monsters.Minion2) && (tick = getTickCount()); - - break MainLoop; - default: - if (getTickCount() - tick < Time.seconds(7)) { - if (Skill.canUse(sdk.skills.Cleansing) && me.getState(sdk.states.Poison)) { - Skill.setSkill(sdk.skills.Cleansing, sdk.skills.hand.Right); - Misc.poll(() => { - if (Config.AttackSkill[3] === sdk.skills.BlessedHammer) { - Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Left); - } - return !me.getState(sdk.states.Poison) || me.mode === sdk.player.mode.GettingHit; - }, Time.seconds(3), 100); - } - } - - if (getTickCount() - tick > Time.seconds(20)) { - this.clearThrone(); - tick = getTickCount(); - } - - if (!this.preattack()) { - delay(100); - } - - break; - } - - switch (me.classid) { - case sdk.player.class.Amazon: - case sdk.player.class.Sorceress: - case sdk.player.class.Necromancer: - case sdk.player.class.Assassin: - [15116, 5026].distance > 3 && Pather.moveTo(15116, 5026); - - break; - case sdk.player.class.Paladin: - if (Config.AttackSkill[3] === sdk.skills.BlessedHammer) { - [15094, 5029].distance > 3 && Pather.moveTo(15094, 5029); - - break; - } - // eslint-disable-next-line no-fallthrough - case sdk.player.class.Druid: - if ([sdk.skills.Fissure, sdk.skills.Volcano].includes(Config.AttackSkill[3])) { - [15116, 5026].distance > 3 && Pather.moveTo(15116, 5026); - - break; - } - - if (Config.AttackSkill[3] === sdk.skills.Tornado) { - [15094, 5029].distance > 3 && Pather.moveTo(15106, 5041); - - break; - } - // eslint-disable-next-line no-fallthrough - case sdk.player.class.Barbarian: - [15101, 5045].distance > 3 && Pather.moveTo(15101, 5045); - - break; - } - - // If we've been in the throne for 30 minutes that's way too long - if (getTickCount() - totalTick > Time.minutes(30)) { - return false; - } - - delay(10); - } - - this.clearThrone(); - - return true; - }, - - killBaal: function () { - if (me.inArea(sdk.areas.ThroneofDestruction)) { - Config.PublicMode && Loader.scriptName() === "Baal" && say(Config.Baal.BaalMessage); - me.checkForMobs({range: 30}) && this.clearWaves(); // ensure waves are actually done - Pather.moveTo(15090, 5008); - delay(5000); - Precast.doPrecast(true); - Misc.poll(() => { - if (me.mode === sdk.player.mode.GettingHit || me.checkForMobs({range: 15})) { - Common.Baal.clearThrone(); - Pather.moveTo(15090, 5008); - } - return !Game.getMonster(sdk.monsters.ThroneBaal); - }, Time.minutes(3), 1000); - - let portal = Game.getObject(sdk.objects.WorldstonePortal); - - if (portal) { - Pather.usePortal(null, null, portal); - } else { - throw new Error("Couldn't find portal."); - } - } - - if (me.inArea(sdk.areas.WorldstoneChamber)) { - Pather.moveTo(15134, 5923); - Attack.kill(sdk.monsters.Baal); - Pickit.pickItems(); - - return true; - } - - return false; - } - }, - Toolsthread: { - pots: { - Health: 0, - Mana: 1, - Rejuv: 2, - MercHealth: 3, - MercRejuv: 4 - }, - pingTimer: [], - pauseScripts: [], - stopScripts: [], - timerLastDrink: [], - cloneWalked: false, - - checkPing: function (print = true) { - // Quit after at least 5 seconds in game - if (getTickCount() - me.gamestarttime < 5000 || !me.gameReady) return false; - - for (let i = 0; i < Config.PingQuit.length; i += 1) { - if (Config.PingQuit[i].Ping > 0) { - if (me.ping >= Config.PingQuit[i].Ping) { - me.overhead("High Ping"); - - if (this.pingTimer[i] === undefined || this.pingTimer[i] === 0) { - this.pingTimer[i] = getTickCount(); - } - - if (getTickCount() - this.pingTimer[i] >= Config.PingQuit[i].Duration * 1000) { - print && D2Bot.printToConsole("High ping (" + me.ping + "/" + Config.PingQuit[i].Ping + ") - leaving game.", sdk.colors.D2Bot.Red); - scriptBroadcast("pingquit"); - scriptBroadcast("quit"); - - return true; - } - } else { - this.pingTimer[i] = 0; - } - } - } - - return false; - }, - - initQuitList: function () { - let temp = []; - - for (let i = 0; i < Config.QuitList.length; i += 1) { - if (FileTools.exists("data/" + Config.QuitList[i] + ".json")) { - let string = Misc.fileAction("data/" + Config.QuitList[i] + ".json", 0); - - if (string) { - let obj = JSON.parse(string); - - if (obj && obj.hasOwnProperty("name")) { - temp.push(obj.name); - } - } - } - } - - Config.QuitList = temp.slice(0); - }, - - togglePause: function () { - for (let i = 0; i < this.pauseScripts.length; i++) { - let script = getScript(this.pauseScripts[i]); - - if (script) { - if (script.running) { - this.pauseScripts[i] === "default.dbj" && console.log("ÿc1Pausing."); - - // don't pause townchicken during clone walk - if (this.pauseScripts[i] !== "tools/townchicken.js" || !this.cloneWalked) { - script.pause(); - } - } else { - this.pauseScripts[i] === "default.dbj" && console.log("ÿc2Resuming."); - script.resume(); - } - } - } - - return true; - }, - - stopDefault: function () { - for (let i = 0; i < this.stopScripts.length; i++) { - let script = getScript(this.stopScripts[i]); - !!script && script.running && script.stop(); - } - - return true; - }, - - exit: function (chickenExit = false) { - chickenExit && D2Bot.updateChickens(); - Config.LogExperience && Experience.log(); - console.log("ÿc8Run duration ÿc2" + (Time.format(getTickCount() - me.gamestarttime))); - this.stopDefault(); - quit(); - }, - - getPotion: function (pottype, type) { - if (!pottype) return false; - - let items = me.getItemsEx().filter((item) => item.itemType === pottype); - if (items.length === 0) return false; - - // Get highest id = highest potion first - items.sort(function (a, b) { - return b.classid - a.classid; - }); - - for (let i = 0; i < items.length; i += 1) { - if (type < this.pots.MercHealth && items[i].isInInventory && items[i].itemType === pottype) { - console.log("ÿc2Drinking potion from inventory."); - return copyUnit(items[i]); - } - - if (items[i].isInBelt && items[i].itemType === pottype) { - console.log("ÿc2" + (type > 2 ? "Giving Merc" : "Drinking") + " potion from belt."); - return copyUnit(items[i]); - } - } - - return false; - }, - - drinkPotion: function (type) { - if (type === undefined) return false; - let pottype, tNow = getTickCount(); - - switch (type) { - case this.pots.Health: - case this.pots.Mana: - if ((this.timerLastDrink[type] && (tNow - this.timerLastDrink[type] < 1000)) || me.getState(type === this.pots.Health ? sdk.states.HealthPot : sdk.states.ManaPot)) { - return false; - } - - break; - case this.pots.Rejuv: - // small delay for juvs just to prevent using more at once - if (this.timerLastDrink[type] && (tNow - this.timerLastDrink[type] < 300)) { - return false; - } - - break; - case this.pots.MercRejuv: - // larger delay for juvs just to prevent using more at once, considering merc update rate - if (this.timerLastDrink[type] && (tNow - this.timerLastDrink[type] < 2000)) { - return false; - } - - break; - default: - if (this.timerLastDrink[type] && (tNow - this.timerLastDrink[type] < 8000)) { - return false; - } - - break; - } - - // mode 18 - can't drink while leaping/whirling etc. - if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) return false; - - switch (type) { - case this.pots.Health: - case this.pots.MercHealth: - pottype = sdk.items.type.HealingPotion; - - break; - case this.pots.Mana: - pottype = sdk.items.type.ManaPotion; - - break; - default: - pottype = sdk.items.type.RejuvPotion; - - break; - } - - let potion = this.getPotion(pottype, type); - - if (!!potion) { - if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) return false; - - try { - type < this.pots.MercHealth ? potion.interact() : Packet.useBeltItemForMerc(potion); - } catch (e) { - console.error(e); - } - - this.timerLastDrink[type] = getTickCount(); - - return true; - } - - return false; - }, - - checkVipers: function () { - let monster = Game.getMonster(sdk.monsters.TombViper2); - - if (monster) { - do { - if (monster.getState(sdk.states.Revive)) { - let owner = monster.getParent(); - - if (owner && owner.name !== me.name) { - D2Bot.printToConsole("Revived Tomb Vipers found. Leaving game.", sdk.colors.D2Bot.Red); - - return true; - } - } - } while (monster.getNext()); - } - - return false; - }, - - getIronGolem: function () { - let golem = Game.getMonster(sdk.summons.IronGolem); - - if (golem) { - do { - let owner = golem.getParent(); - - if (owner && owner.name === me.name) { - return copyUnit(golem); - } - } while (golem.getNext()); - } - - return false; - }, - - getNearestPreset: function () { - let id; - let unit = getPresetUnits(me.area); - let dist = 99; - - for (let i = 0; i < unit.length; i += 1) { - if (getDistance(me, unit[i].roomx * 5 + unit[i].x, unit[i].roomy * 5 + unit[i].y) < dist) { - dist = getDistance(me, unit[i].roomx * 5 + unit[i].x, unit[i].roomy * 5 + unit[i].y); - id = unit[i].type + " " + unit[i].id; - } - } - - return id || ""; - }, - - getStatsString: function (unit) { - let realFCR = unit.getStat(sdk.stats.FCR); - let realIAS = unit.getStat(sdk.stats.IAS); - let realFBR = unit.getStat(sdk.stats.FBR); - let realFHR = unit.getStat(sdk.stats.FHR); - // me.getStat(sdk.stats.FasterCastRate) will return real FCR from gear + Config.FCR from char cfg - - if (unit === me) { - realFCR -= Config.FCR; - realIAS -= Config.IAS; - realFBR -= Config.FBR; - realFHR -= Config.FHR; - } - - let maxHellFireRes = 75 + unit.getStat(sdk.stats.MaxFireResist); - let hellFireRes = unit.getRes(sdk.stats.FireResist, sdk.difficulty.Hell); - hellFireRes > maxHellFireRes && (hellFireRes = maxHellFireRes); - - let maxHellColdRes = 75 + unit.getStat(sdk.stats.MaxColdResist); - let hellColdRes = unit.getRes(sdk.stats.ColdResist, sdk.difficulty.Hell); - hellColdRes > maxHellColdRes && (hellColdRes = maxHellColdRes); - - let maxHellLightRes = 75 + unit.getStat(sdk.stats.MaxLightResist); - let hellLightRes = unit.getRes(sdk.stats.LightResist, sdk.difficulty.Hell); - hellLightRes > maxHellLightRes && (hellLightRes = maxHellLightRes); - - let maxHellPoisonRes = 75 + unit.getStat(sdk.stats.MaxPoisonResist); - let hellPoisonRes = unit.getRes(sdk.stats.PoisonResist, sdk.difficulty.Hell); - hellPoisonRes > maxHellPoisonRes && (hellPoisonRes = maxHellPoisonRes); - - let str = - "ÿc4Character Level: ÿc0" + unit.charlvl + (unit === me ? " ÿc4Difficulty: ÿc0" + sdk.difficulty.nameOf(me.diff) + " ÿc4HighestActAvailable: ÿc0" + me.highestAct : "") + "\n" - + "ÿc1FR: ÿc0" + unit.getStat(sdk.stats.FireResist) + "ÿc1 Applied FR: ÿc0" + unit.fireRes - + "/ÿc3 CR: ÿc0" + unit.getStat(sdk.stats.ColdResist) + "ÿc3 Applied CR: ÿc0" + unit.coldRes - + "/ÿc9 LR: ÿc0" + unit.getStat(sdk.stats.LightResist) + "ÿc9 Applied LR: ÿc0" + unit.lightRes - + "/ÿc2 PR: ÿc0" + unit.getStat(sdk.stats.PoisonResist) + "ÿc2 Applied PR: ÿc0" + unit.poisonRes + "\n" - + (!me.hell ? "Hell res: ÿc1" + hellFireRes + "ÿc0/ÿc3" + hellColdRes + "ÿc0/ÿc9" + hellLightRes + "ÿc0/ÿc2" + hellPoisonRes + "ÿc0\n" : "") - + "ÿc4MF: ÿc0" + unit.getStat(sdk.stats.MagicBonus) + "ÿc4 GF: ÿc0" + unit.getStat(sdk.stats.GoldBonus) - + " ÿc4FCR: ÿc0" + realFCR + " ÿc4IAS: ÿc0" + realIAS + " ÿc4FBR: ÿc0" + realFBR - + " ÿc4FHR: ÿc0" + realFHR + " ÿc4FRW: ÿc0" + unit.getStat(sdk.stats.FRW) + "\n" - + "ÿc4CB: ÿc0" + unit.getStat(sdk.stats.CrushingBlow) + " ÿc4DS: ÿc0" + unit.getStat(sdk.stats.DeadlyStrike) - + " ÿc4OW: ÿc0" + unit.getStat(sdk.stats.OpenWounds) - + " ÿc1LL: ÿc0" + unit.getStat(sdk.stats.LifeLeech) + " ÿc3ML: ÿc0" + unit.getStat(sdk.stats.ManaLeech) - + " ÿc8DR: ÿc0" + unit.getStat(sdk.stats.DamageResist) + "% + " + unit.getStat(sdk.stats.NormalDamageReduction) - + " ÿc8MDR: ÿc0" + unit.getStat(sdk.stats.MagicResist) + "% + " + unit.getStat(sdk.stats.MagicDamageReduction) + "\n" - + (unit.getStat(sdk.stats.CannotbeFrozen) > 0 ? "ÿc3Cannot be Frozenÿc1\n" : "\n"); - - return str; - }, - }, - - Leecher: { - leadTick: 0, - leader: null, - killLeaderTracker: false, - currentScript: "", - nextScriptAreas: [sdk.areas.TowerCellarLvl5, sdk.areas.PitLvl1, sdk.areas.PitLvl2, sdk.areas.BurialGrounds, - sdk.areas.CatacombsLvl4, sdk.areas.MooMooFarm, sdk.areas.DuranceofHateLvl3, - sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber - ], - - leaderTracker: function () { - if (Common.Leecher.killLeaderTracker) return false; - // check every 3 seconds - if (getTickCount() - Common.Leecher.leadTick < 3000) return true; - Common.Leecher.leadTick = getTickCount(); - - // check again in another 3 seconds if game wasn't ready - if (!me.gameReady) return true; - if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); - - let party = getParty(Common.Leecher.leader); - - if (party) { - // Player has moved on to another script - if (Common.Leecher.nextScriptAreas.includes(party.area)) { - if (Loader.scriptName() === Common.Leecher.currentScript) { - Common.Leecher.killLeaderTracker = true; - throw new Error("Party leader is running a new script"); - } else { - // kill process - return false; - } - } - } - - return true; - } - } -}; diff --git a/d2bs/kolbot/libs/common/Config.js b/d2bs/kolbot/libs/common/Config.js deleted file mode 100644 index 9b4bf1633..000000000 --- a/d2bs/kolbot/libs/common/Config.js +++ /dev/null @@ -1,652 +0,0 @@ -/** -* @filename Config.js -* @author kolton -* @desc config loading and default config values storage -* -*/ - -const Scripts = {}; - -let Config = { - init: function (notify) { - let configFilename = ""; - let classes = ["Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin"]; - - for (let i = 0; i < 5; i++) { - switch (i) { - case 0: // Custom config - if (!isIncluded("config/_customconfig.js")) { - include("config/_customconfig.js"); - } - - for (let n in CustomConfig) { - if (CustomConfig.hasOwnProperty(n)) { - if (CustomConfig[n].indexOf(me.profile) > -1) { - if (notify) { - print("ÿc2Loading custom config: ÿc9" + n + ".js"); - } - - configFilename = n + ".js"; - - break; - } - } - } - - break; - case 1:// Class.Profile.js - configFilename = classes[me.classid] + "." + me.profile + ".js"; - - break; - case 2: // Realm.Class.Charname.js - configFilename = me.realm + "." + classes[me.classid] + "." + me.charname + ".js"; - - break; - case 3: // Class.Charname.js - configFilename = classes[me.classid] + "." + me.charname + ".js"; - - break; - case 4: // Profile.js - configFilename = me.profile + ".js"; - - break; - } - - if (configFilename && FileTools.exists("libs/config/" + configFilename)) { - break; - } - } - - if (FileTools.exists("libs/config/" + configFilename)) { - try { - if (!include("config/" + configFilename)) { - throw new Error(); - } - } catch (e1) { - throw new Error("Failed to load character config."); - } - } else { - if (notify) { - print("ÿc1" + classes[me.classid] + "." + me.charname + ".js not found!"); // Use the primary format - print("ÿc1Loading default config."); - } - - // Try to find default config - if (!FileTools.exists("libs/config/" + classes[me.classid] + ".js")) { - D2Bot.printToConsole("Not going well? Read the guides: https://github.com/blizzhackers/documentation"); - throw new Error("ÿc1Default config not found. \nÿc9 Try reading the kolbot guides."); - } - - try { - if (!include("config/" + classes[me.classid] + ".js")) { - throw new Error(); - } - } catch (e) { - throw new Error("ÿc1Failed to load default config."); - } - } - - try { - LoadConfig.call(); - Config.Loaded = true; - } catch (e2) { - if (notify) { - print("ÿc8Error in " + e2.fileName.substring(e2.fileName.lastIndexOf("\\") + 1, e2.fileName.length) + "(line " + e2.lineNumber + "): " + e2.message); - - throw new Error("Config.init: Error in character config."); - } - } - - if (Config.Silence && !Config.LocalChat.Enabled) { - // Override the say function with print, so it just gets printed to console - global._say = global.say; - global.say = (what) => print("Tryed to say: " + what); - } - - try { - if (Config.AutoBuild.Enabled === true && !isIncluded("common/AutoBuild.js") && include("common/AutoBuild.js")) { - AutoBuild.initialize(); - } - } catch (e3) { - print("ÿc8Error in libs/common/AutoBuild.js (AutoBuild system is not active!)"); - print(e3.toSource()); - } - }, - - // dev - Loaded: false, - DebugMode: false, - - // Time - StartDelay: 0, - PickDelay: 0, - AreaDelay: 0, - MinGameTime: 0, - MaxGameTime: 0, - - // Healing and chicken - LifeChicken: 0, - ManaChicken: 0, - UseHP: 0, - UseMP: 0, - UseRejuvHP: 0, - UseRejuvMP: 0, - UseMercHP: 0, - UseMercRejuv: 0, - MercChicken: 0, - IronGolemChicken: 0, - HealHP: 0, - HealMP: 0, - HealStatus: false, - TownHP: 0, - TownMP: 0, - - // special pots - StackThawingPots: { - enabled: false, - quantity: 12, - }, - StackAntidotePots: { - enabled: false, - quantity: 12, - }, - StackStaminaPots: { - enabled: false, - quantity: 12, - }, - - // General - AutoMap: false, - LastMessage: "", - UseMerc: false, - MercWatch: false, - LowGold: 0, - StashGold: 0, - FieldID: { - Enabled: false, - PacketID: true, - UsedSpace: 90, - }, - DroppedItemsAnnounce: { - Enable: false, - Quality: [], - LogToOOG: false, - OOGQuality: [] - }, - CainID: { - Enable: false, - MinGold: 0, - MinUnids: 0 - }, - Inventory: [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ], - LocalChat: { - Enabled: false, - Toggle: false, - Mode: 0 - }, - Silence: false, - PublicMode: false, - PartyAfterScript: false, - Greetings: [], - DeathMessages: [], - Congratulations: [], - ShitList: false, - UnpartyShitlisted: false, - Leader: "", - QuitList: [], - QuitListMode: 0, - QuitListDelay: [], - HPBuffer: 0, - MPBuffer: 0, - RejuvBuffer: 0, - PickRange: 40, - MakeRoom: true, - ClearInvOnStart: false, - FastPick: false, - ManualPlayPick: false, - OpenChests: { - Enabled: false, - Range: 15, - Types: ["chest", "chest3", "armorstand", "weaponrack"] - }, - PickitFiles: [], - BeltColumn: [], - MinColumn: [], - SkipId: [], - SkipEnchant: [], - SkipImmune: [], - SkipAura: [], - SkipException: [], - ScanShrines: [], - Debug: false, - - AutoMule: { - Trigger: [], - Force: [], - Exclude: [] - }, - - ItemInfo: false, - ItemInfoQuality: [], - - LogKeys: false, - LogOrgans: true, - LogLowRunes: false, - LogMiddleRunes: false, - LogHighRunes: true, - LogLowGems: false, - LogHighGems: false, - SkipLogging: [], - ShowCubingInfo: true, - - Cubing: false, - CubeRepair: false, - RepairPercent: 40, - Recipes: [], - MakeRunewords: false, - Runewords: [], - KeepRunewords: [], - Gamble: false, - GambleItems: [], - GambleGoldStart: 0, - GambleGoldStop: 0, - MiniShopBot: false, - TeleSwitch: false, - MFSwitchPercent: 0, - PrimarySlot: -1, - LogExperience: false, - TownCheck: false, - PingQuit: [{Ping: 0, Duration: 0}], - PacketShopping: false, - - // Fastmod - FCR: 0, - FHR: 0, - FBR: 0, - IAS: 0, - PacketCasting: 0, - WaypointMenu: true, - - // Anti-hostile - AntiHostile: false, - RandomPrecast: false, - HostileAction: 0, - TownOnHostile: false, - ViperCheck: false, - - // DClone - StopOnDClone: false, - SoJWaitTime: 0, - KillDclone: false, - DCloneQuit: false, - DCloneWaitTime: 30, - - // Experimental - FastParty: false, - AutoEquip: false, - - // GameData - ChampionBias: 60, - - UseCta: true, - - // Attack specific - Dodge: false, - DodgeRange: 15, - DodgeHP: 100, - AttackSkill: [], - LowManaSkill: [], - CustomAttack: {}, - TeleStomp: false, - NoTele: false, - ClearType: false, - ClearPath: false, - BossPriority: false, - MaxAttackCount: 300, - - // Amazon specific - LightningFuryDelay: 0, - UseInnerSight: false, - UseSlowMissiles: false, - UseDecoy: false, - SummonValkyrie: false, - - // Sorceress specific - UseTelekinesis: false, - CastStatic: false, - StaticList: [], - UseEnergyShield: false, - UseColdArmor: true, - - // Necromancer specific - Golem: 0, - ActiveSummon: false, - Skeletons: 0, - SkeletonMages: 0, - Revives: 0, - ReviveUnstackable: false, - PoisonNovaDelay: 2000, - Curse: [], - CustomCurse: [], - ExplodeCorpses: 0, - - // Paladin speficic - Redemption: [0, 0], - Charge: false, - Vigor: false, - AvoidDolls: false, - - // Barbarian specific - FindItem: false, - FindItemSwitch: false, - UseWarcries: true, - - // Druid specific - Wereform: 0, - SummonRaven: 0, - SummonAnimal: 0, - SummonVine: 0, - SummonSpirit: 0, - - // Assassin specific - UseTraps: false, - Traps: [], - BossTraps: [], - UseFade: false, - UseBoS: false, - UseVenom: false, - UseBladeShield: false, - UseCloakofShadows: false, - AggressiveCloak: false, - SummonShadow: false, - - // Custom Attack - CustomClassAttack: "", // If set it loads common/Attack/[CustomClassAttack].js - - MapMode: { - UseOwnItemFilter: false, - }, - - // Script specific - MFLeader: false, - Mausoleum: { - KillBishibosh: false, - KillBloodRaven: false, - ClearCrypt: false - }, - Cows: { - DontMakePortal: false, - JustMakePortal: false, - KillKing: false - }, - Tombs: { - KillDuriel: false, - }, - Eldritch: { - OpenChest: false, - KillSharptooth: false, - KillShenk: false, - KillDacFarren: false - }, - Pindleskin: { - UseWaypoint: false, - KillNihlathak: false, - ViperQuit: false - }, - Nihlathak: { - ViperQuit: false, - UseWaypoint: false, - }, - Pit: { - ClearPath: false, - ClearPit1: false - }, - Snapchip: { - ClearIcyCellar: false - }, - Frozenstein: { - ClearFrozenRiver: false - }, - Rakanishu: { - KillGriswold: false - }, - AutoBaal: { - Leader: "", - FindShrine: false, - LeechSpot: [15115, 5050], - LongRangeSupport: false - }, - KurastChests: { - LowerKurast: false, - Bazaar: false, - Sewers1: false, - Sewers2: false - }, - Countess: { - KillGhosts: false - }, - Baal: { - DollQuit: false, - SoulQuit: false, - KillBaal: false, - HotTPMessage: "Hot TP!", - SafeTPMessage: "Safe TP!", - BaalMessage: "Baal!" - }, - BaalAssistant: { - KillNihlathak: false, - FastChaos: false, - Wait: 120, - Helper: false, - GetShrine: false, - GetShrineWaitForHotTP: false, - DollQuit: false, - SoulQuit: false, - SkipTP: false, - WaitForSafeTP: false, - KillBaal: false, - HotTPMessage: [], - SafeTPMessage: [], - BaalMessage: [], - NextGameMessage: [] - }, - BaalHelper: { - Wait: 120, - KillNihlathak: false, - FastChaos: false, - DollQuit: false, - KillBaal: false, - SkipTP: false - }, - Corpsefire: { - ClearDen: false - }, - Hephasto: { - ClearRiver: false, - ClearType: false - }, - Diablo: { - WalkClear: false, - Entrance: false, - JustViz: false, - SealLeader: false, - Fast: false, - SealWarning: "Leave the seals alone!", - EntranceTP: "Entrance TP up", - StarTP: "Star TP up", - DiabloMsg: "Diablo", - ClearRadius: 30, - SealOrder: ["vizier", "seis", "infector"] - }, - DiabloHelper: { - Wait: 120, - Entrance: false, - SkipIfBaal: false, - SkipTP: false, - OpenSeals: false, - SafePrecast: true, - ClearRadius: 30, - SealOrder: ["vizier", "seis", "infector"], - RecheckSeals: false - }, - MFHelper: { - BreakClearLevel: false - }, - Wakka: { - Wait: 1, - StopAtLevel: 99, - StopProfile: false, - SkipIfBaal: true, - }, - BattleOrders: { - Mode: 0, - Getters: [], - Idle: false, - QuitOnFailure: false, - SkipIfTardy: true, - Wait: 10 - }, - BoBarbHelper: { - Mode: -1, - Wp: 35 - }, - ControlBot: { - Bo: false, - Cows: { - MakeCows: false, - GetLeg: false, - }, - Chant: { - Enchant: false, - AutoEnchant: false, - }, - Wps: { - GiveWps: false, - SecurePortal: false, - }, - EndMessage: "", - GameLength: 20 - }, - IPHunter: { - IPList: [], - GameLength: 3 - }, - Follower: { - Leader: "" - }, - Mephisto: { - MoatTrick: false, - KillCouncil: false, - TakeRedPortal: false - }, - ShopBot: { - ScanIDs: [], - ShopNPC: "anya", - CycleDelay: 0, - QuitOnMatch: false - }, - Coldworm: { - KillBeetleburst: false, - ClearMaggotLair: false - }, - Summoner: { - FireEye: false - }, - AncientTunnels: { - OpenChest: false, - KillDarkElder: false - }, - OrgTorch: { - WaitForKeys: false, - WaitTimeout: false, - UseSalvation: false, - GetFade: false, - MakeTorch: true, - PreGame: { - Thawing: {Drink: 0, At: []}, - Antidote: {Drink: 0, At: []}, - } - }, - Synch: { - WaitFor: [] - }, - TristramLeech: { - Leader: "", - Helper: false, - Wait: 5 - }, - TravincalLeech: { - Leader: "", - Helper: false, - Wait: 5 - }, - Tristram: { - PortalLeech: false, - WalkClear: false - }, - Travincal: { - PortalLeech: false - }, - SkillStat: { - Skills: [] - }, - Bonesaw: { - ClearDrifterCavern: false - }, - ChestMania: { - Act1: [], - Act2: [], - Act3: [], - Act4: [], - Act5: [] - }, - ClearAnyArea: { - AreaList: [] - }, - Rusher: { - WaitPlayerCount: 0, - Cain: false, - Radament: false, - LamEsen: false, - Izual: false, - Shenk: false, - Anya: false, - HellAncients: false, - GiveWps: false, - LastRun: "" - }, - Rushee: { - Quester: false, - Bumper: false - }, - Questing: { - StopProfile: false - }, - AutoSkill: { - Enabled: false, - Build: [], - Save: 0 - }, - AutoStat: { - Enabled: false, - Build: [], - Save: 0, - BlockChance: 0, - UseBulk: true - }, - AutoBuild: { - Enabled: false, - Template: "", - Verbose: false, - DebugMode: false - }, - GemHunter: { - AreaList: [], - GemList: [] - } -}; diff --git a/d2bs/kolbot/libs/common/Cubing.js b/d2bs/kolbot/libs/common/Cubing.js deleted file mode 100644 index fc9389bb6..000000000 --- a/d2bs/kolbot/libs/common/Cubing.js +++ /dev/null @@ -1,1102 +0,0 @@ -/** -* @filename Cubing.js -* @author kolton -* @desc transmute Horadric Cube recipes -* -*/ - -const Roll = { - All: 0, - Eth: 1, - NonEth: 2 -}; - -const Recipe = { - Gem: 0, - HitPower: { - Helm: 1, - Boots: 2, - Gloves: 3, - Belt: 4, - Shield: 5, - Body: 6, - Amulet: 7, - Ring: 8, - Weapon: 9 - }, - Blood: { - Helm: 10, - Boots: 11, - Gloves: 12, - Belt: 13, - Shield: 14, - Body: 15, - Amulet: 16, - Ring: 17, - Weapon: 18 - }, - Caster: { - Helm: 19, - Boots: 20, - Gloves: 21, - Belt: 22, - Shield: 23, - Body: 24, - Amulet: 25, - Ring: 26, - Weapon: 27 - }, - Safety: { - Helm: 28, - Boots: 29, - Gloves: 30, - Belt: 31, - Shield: 32, - Body: 33, - Amulet: 34, - Ring: 35, - Weapon: 36 - }, - Unique: { - Weapon: { - ToExceptional: 37, - ToElite: 38 - }, - Armor: { - ToExceptional: 39, - ToElite: 40 - } - }, - Rare: { - Weapon: { - ToExceptional: 41, - ToElite: 42 - }, - Armor: { - ToExceptional: 43, - ToElite: 44 - } - }, - Socket: { - Shield: 45, - Weapon: 46, - Armor: 47, - Helm: 48 - }, - Reroll: { - Magic: 49, - Rare: 50, - HighRare: 51 - }, - Rune: 52, - Token: 53, - LowToNorm: { - Armor: 54, - Weapon: 55 - } -}; - -const Cubing = { - recipes: [], - gemList: [], - chippedGems: [ - sdk.items.gems.Chipped.Amethyst, sdk.items.gems.Chipped.Topaz, sdk.items.gems.Chipped.Sapphire, sdk.items.gems.Chipped.Emerald, - sdk.items.gems.Chipped.Ruby, sdk.items.gems.Chipped.Diamond, sdk.items.gems.Chipped.Skull - ], - - init: function () { - if (!Config.Cubing) return; - - //print("We have " + Config.Recipes.length + " cubing recipe(s)."); - - for (let i = 0; i < Config.Recipes.length; i += 1) { - if (Config.Recipes[i].length > 1 && isNaN(Config.Recipes[i][1])) { - if (NTIPAliasClassID.hasOwnProperty(Config.Recipes[i][1].replace(/\s+/g, "").toLowerCase())) { - Config.Recipes[i][1] = NTIPAliasClassID[Config.Recipes[i][1].replace(/\s+/g, "").toLowerCase()]; - } else { - Misc.errorReport("ÿc1Invalid cubing entry:ÿc0 " + Config.Recipes[i][1]); - Config.Recipes.splice(i, 1); - - i -= 1; - } - } - } - - this.buildRecipes(); - this.buildGemList(); - this.buildLists(); - }, - - buildGemList: function () { - let gemList = [ - sdk.items.gems.Perfect.Amethyst, sdk.items.gems.Perfect.Topaz, sdk.items.gems.Perfect.Sapphire, - sdk.items.gems.Perfect.Emerald, sdk.items.gems.Perfect.Ruby, sdk.items.gems.Perfect.Diamond, sdk.items.gems.Perfect.Skull - ]; - - for (let i = 0; i < this.recipes.length; i += 1) { - // Skip gems and other magic rerolling recipes - if ([Recipe.Gem, Recipe.Reroll.Magic].indexOf(this.recipes[i].Index) === -1) { - for (let j = 0; j < this.recipes[i].Ingredients.length; j += 1) { - if (gemList.includes(this.recipes[i].Ingredients[j])) { - gemList.splice(gemList.indexOf(this.recipes[i].Ingredients[j]), 1); - } - } - } - } - - this.gemList = gemList.slice(0); - - return true; - }, - - getCube: function () { - // Don't activate from townchicken - if (getScript(true).name === "tools\\townchicken.js") { - return false; - } - - console.log("Getting cube"); - me.overhead("Getting cube"); - let cube; - - Pather.useWaypoint(sdk.areas.HallsoftheDeadLvl2, true); - Precast.doPrecast(true); - - if (Pather.moveToExit(sdk.areas.HallsoftheDeadLvl3, true) && Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest)) { - let chest = Game.getObject(sdk.quest.chest.HoradricCubeChest); - - if (chest) { - Misc.openChest(chest); - Misc.poll(function () { - cube = Game.getItem(sdk.quest.item.Cube); - return !!cube && Pickit.pickItem(cube); - }, 1000, 2000); - } - } - - Town.goToTown(); - cube = me.getItem(sdk.quest.item.Cube); - - return (!!cube && Storage.Stash.MoveTo(cube)); - }, - - buildRecipes: function () { - this.recipes = []; - - for (let i = 0; i < Config.Recipes.length; i += 1) { - if (typeof Config.Recipes[i] !== "object" || (Config.Recipes[i].length > 2 && typeof Config.Recipes[i][2] !== "number") || Config.Recipes[i].length < 1) { - throw new Error("Cubing.buildRecipes: Invalid recipe format."); - } - - switch (Config.Recipes[i][0]) { - case Recipe.Gem: - this.recipes.push({Ingredients: [Config.Recipes[i][1], Config.Recipes[i][1], Config.Recipes[i][1]], Index: Recipe.Gem, AlwaysEnabled: true}); - - break; - case Recipe.HitPower.Helm: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 84, Index: Recipe.HitPower.Helm}); - - break; - case Recipe.HitPower.Boots: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 71, Index: Recipe.HitPower.Boots}); - - break; - case Recipe.HitPower.Gloves: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 79, Index: Recipe.HitPower.Gloves}); - - break; - case Recipe.HitPower.Belt: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 71, Index: Recipe.HitPower.Belt}); - - break; - case Recipe.HitPower.Shield: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 82, Index: Recipe.HitPower.Shield}); - - break; - case Recipe.HitPower.Body: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 85, Index: Recipe.HitPower.Body}); - - break; - case Recipe.HitPower.Amulet: - this.recipes.push({Ingredients: [sdk.items.Amulet, sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 90, Index: Recipe.HitPower.Amulet}); - - break; - case Recipe.HitPower.Ring: - this.recipes.push({Ingredients: [sdk.items.Ring, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 77, Index: Recipe.HitPower.Ring}); - - break; - case Recipe.HitPower.Weapon: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tir, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire], Level: 85, Index: Recipe.HitPower.Weapon}); - - break; - case Recipe.Blood.Helm: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 84, Index: Recipe.Blood.Helm}); - - break; - case Recipe.Blood.Boots: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 71, Index: Recipe.Blood.Boots}); - - break; - case Recipe.Blood.Gloves: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 79, Index: Recipe.Blood.Gloves}); - - break; - case Recipe.Blood.Belt: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 71, Index: Recipe.Blood.Belt}); - - break; - case Recipe.Blood.Shield: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 82, Index: Recipe.Blood.Shield}); - - break; - case Recipe.Blood.Body: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 85, Index: Recipe.Blood.Body}); - - break; - case Recipe.Blood.Amulet: - this.recipes.push({Ingredients: [sdk.items.Amulet, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 90, Index: Recipe.Blood.Amulet}); - - break; - case Recipe.Blood.Ring: - this.recipes.push({Ingredients: [sdk.items.Ring, sdk.items.runes.Sol, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 77, Index: Recipe.Blood.Ring}); - - break; - case Recipe.Blood.Weapon: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby], Level: 85, Index: Recipe.Blood.Weapon}); - - break; - case Recipe.Caster.Helm: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 84, Index: Recipe.Caster.Helm}); - - break; - case Recipe.Caster.Boots: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 71, Index: Recipe.Caster.Boots}); - - break; - case Recipe.Caster.Gloves: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 79, Index: Recipe.Caster.Gloves}); - - break; - case Recipe.Caster.Belt: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 71, Index: Recipe.Caster.Belt}); - - break; - case Recipe.Caster.Shield: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 82, Index: Recipe.Caster.Shield}); - - break; - case Recipe.Caster.Body: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 85, Index: Recipe.Caster.Body}); - - break; - case Recipe.Caster.Amulet: - this.recipes.push({Ingredients: [sdk.items.Amulet, sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 90, Index: Recipe.Caster.Amulet}); - - break; - case Recipe.Caster.Ring: - this.recipes.push({Ingredients: [sdk.items.Ring, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 77, Index: Recipe.Caster.Ring}); - - break; - case Recipe.Caster.Weapon: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tir, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst], Level: 85, Index: Recipe.Caster.Weapon}); - - break; - case Recipe.Safety.Helm: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 84, Index: Recipe.Safety.Helm}); - - break; - case Recipe.Safety.Boots: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 71, Index: Recipe.Safety.Boots}); - - break; - case Recipe.Safety.Gloves: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 79, Index: Recipe.Safety.Gloves}); - - break; - case Recipe.Safety.Belt: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 71, Index: Recipe.Safety.Belt}); - - break; - case Recipe.Safety.Shield: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 82, Index: Recipe.Safety.Shield}); - - break; - case Recipe.Safety.Body: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 85, Index: Recipe.Safety.Body}); - - break; - case Recipe.Safety.Amulet: - this.recipes.push({Ingredients: [sdk.items.Amulet, sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 90, Index: Recipe.Safety.Amulet}); - - break; - case Recipe.Safety.Ring: - this.recipes.push({Ingredients: [sdk.items.Ring, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 77, Index: Recipe.Safety.Ring}); - - break; - case Recipe.Safety.Weapon: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Sol, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald], Level: 85, Index: Recipe.Safety.Weapon}); - - break; - case Recipe.Unique.Weapon.ToExceptional: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ral, sdk.items.runes.Sol, sdk.items.gems.Perfect.Emerald], Index: Recipe.Unique.Weapon.ToExceptional, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Unique.Weapon.ToElite: // Ladder only - if (me.ladder) { - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Lum, sdk.items.runes.Pul, sdk.items.gems.Perfect.Emerald], Index: Recipe.Unique.Weapon.ToElite, Ethereal: Config.Recipes[i][2]}); - } - - break; - case Recipe.Unique.Armor.ToExceptional: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tal, sdk.items.runes.Shael, sdk.items.gems.Perfect.Diamond], Index: Recipe.Unique.Armor.ToExceptional, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Unique.Armor.ToElite: // Ladder only - if (me.ladder) { - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Lem, sdk.items.runes.Ko, sdk.items.gems.Perfect.Diamond], Index: Recipe.Unique.Armor.ToElite, Ethereal: Config.Recipes[i][2]}); - } - - break; - case Recipe.Rare.Weapon.ToExceptional: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ort, sdk.items.runes.Amn, sdk.items.gems.Perfect.Sapphire], Index: Recipe.Rare.Weapon.ToExceptional, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Rare.Weapon.ToElite: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Fal, sdk.items.runes.Um, sdk.items.gems.Perfect.Sapphire], Index: Recipe.Rare.Weapon.ToElite, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Rare.Armor.ToExceptional: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ral, sdk.items.runes.Thul, sdk.items.gems.Perfect.Amethyst], Index: Recipe.Rare.Armor.ToExceptional, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Rare.Armor.ToElite: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ko, sdk.items.runes.Pul, sdk.items.gems.Perfect.Amethyst], Index: Recipe.Rare.Armor.ToElite, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Socket.Shield: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tal, sdk.items.runes.Amn, sdk.items.gems.Perfect.Ruby], Index: Recipe.Socket.Shield, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Socket.Weapon: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ral, sdk.items.runes.Amn, sdk.items.gems.Perfect.Amethyst], Index: Recipe.Socket.Weapon, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Socket.Armor: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Tal, sdk.items.runes.Thul, sdk.items.gems.Perfect.Topaz], Index: Recipe.Socket.Armor, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Socket.Helm: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Ral, sdk.items.runes.Thul, sdk.items.gems.Perfect.Sapphire], Index: Recipe.Socket.Helm, Ethereal: Config.Recipes[i][2]}); - - break; - case Recipe.Reroll.Magic: // Hacky solution ftw - this.recipes.push({Ingredients: [Config.Recipes[i][1], "pgem", "pgem", "pgem"], Level: 91, Index: Recipe.Reroll.Magic}); - - break; - case Recipe.Reroll.Rare: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull], Index: Recipe.Reroll.Rare}); - - break; - case Recipe.Reroll.HighRare: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.gems.Perfect.Skull, sdk.items.Ring], Index: Recipe.Reroll.HighRare, Enabled: false}); - - break; - case Recipe.LowToNorm.Weapon: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.Eld, "cgem"], Index: Recipe.LowToNorm.Weapon}); - - break; - case Recipe.LowToNorm.Armor: - this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.runes.El, "cgem"], Index: Recipe.LowToNorm.Armor}); - - break; - case Recipe.Rune: - switch (Config.Recipes[i][1]) { - case sdk.items.runes.El: - case sdk.items.runes.Eld: - case sdk.items.runes.Tir: - case sdk.items.runes.Nef: - case sdk.items.runes.Eth: - case sdk.items.runes.Ith: - case sdk.items.runes.Tal: - case sdk.items.runes.Ral: - case sdk.items.runes.Ort: - this.recipes.push({Ingredients: [Config.Recipes[i][1], Config.Recipes[i][1], Config.Recipes[i][1]], Index: Recipe.Rune, AlwaysEnabled: true}); - - break; - case sdk.items.runes.Thul: // thul->amn - this.recipes.push({Ingredients: [sdk.items.runes.Thul, sdk.items.runes.Thul, sdk.items.runes.Thul, sdk.items.gems.Chipped.Topaz], Index: Recipe.Rune}); - - break; - case sdk.items.runes.Amn: // amn->sol - this.recipes.push({Ingredients: [sdk.items.runes.Amn, sdk.items.runes.Amn, sdk.items.runes.Amn, sdk.items.gems.Chipped.Amethyst], Index: Recipe.Rune}); - - break; - case sdk.items.runes.Sol: // sol->shael - this.recipes.push({Ingredients: [sdk.items.runes.Sol, sdk.items.runes.Sol, sdk.items.runes.Sol, sdk.items.gems.Chipped.Sapphire], Index: Recipe.Rune}); - - break; - case sdk.items.runes.Shael: // shael->dol - this.recipes.push({Ingredients: [sdk.items.runes.Shael, sdk.items.runes.Shael, sdk.items.runes.Shael, sdk.items.gems.Chipped.Ruby], Index: Recipe.Rune}); - - break; - case sdk.items.runes.Dol: // dol->hel - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Dol, sdk.items.runes.Dol, sdk.items.runes.Dol, sdk.items.gems.Chipped.Emerald], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Hel: // hel->io - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Hel, sdk.items.runes.Hel, sdk.items.runes.Hel, sdk.items.gems.Chipped.Diamond], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Io: // io->lum - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Io, sdk.items.runes.Io, sdk.items.runes.Io, sdk.items.gems.Flawed.Topaz], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Lum: // lum->ko - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Lum, sdk.items.runes.Lum, sdk.items.runes.Lum, sdk.items.gems.Flawed.Amethyst], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Ko: // ko->fal - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Ko, sdk.items.runes.Ko, sdk.items.runes.Ko, sdk.items.gems.Flawed.Sapphire], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Fal: // fal->lem - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Fal, sdk.items.runes.Fal, sdk.items.runes.Fal, sdk.items.gems.Flawed.Ruby], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Lem: // lem->pul - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Lem, sdk.items.runes.Lem, sdk.items.runes.Lem, sdk.items.gems.Flawed.Emerald], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Pul: // pul->um - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Pul, sdk.items.runes.Pul, sdk.items.gems.Flawed.Diamond], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Um: // um->mal - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Um, sdk.items.runes.Um, sdk.items.gems.Normal.Topaz], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Mal: // mal->ist - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Mal, sdk.items.runes.Mal, sdk.items.gems.Normal.Amethyst], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Ist: // ist->gul - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Ist, sdk.items.runes.Ist, sdk.items.gems.Normal.Sapphire], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Gul: // gul->vex - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Gul, sdk.items.runes.Gul, sdk.items.gems.Normal.Ruby], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Vex: // vex->ohm - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Vex, sdk.items.runes.Vex, sdk.items.gems.Normal.Emerald], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Ohm: // ohm->lo - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Ohm, sdk.items.runes.Ohm, sdk.items.gems.Normal.Diamond], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Lo: // lo->sur - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Lo, sdk.items.runes.Lo, sdk.items.gems.Flawless.Topaz], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Sur: // sur->ber - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Sur, sdk.items.runes.Sur, sdk.items.gems.Flawless.Amethyst], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Ber: // ber->jah - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Ber, sdk.items.runes.Ber, sdk.items.gems.Flawless.Sapphire], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Jah: // jah->cham - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Jah, sdk.items.runes.Jah, sdk.items.gems.Flawless.Ruby], Index: Recipe.Rune}); - } - - break; - case sdk.items.runes.Cham: // cham->zod - if (me.ladder) { - this.recipes.push({Ingredients: [sdk.items.runes.Cham, sdk.items.runes.Cham, sdk.items.gems.Flawless.Emerald], Index: Recipe.Rune}); - } - - break; - } - - break; - case Recipe.Token: - this.recipes.push({Ingredients: [sdk.quest.item.TwistedEssenceofSuffering, sdk.quest.item.ChargedEssenceofHatred, sdk.quest.item.BurningEssenceofTerror, sdk.quest.item.FesteringEssenceofDestruction], Index: Recipe.Token, AlwaysEnabled: true}); - - break; - } - } - }, - - validIngredients: [], // What we have - neededIngredients: [], // What we need - subRecipes: [], - - buildLists: function () { - CraftingSystem.checkSubrecipes(); - - this.validIngredients = []; - this.neededIngredients = []; - let items = me.findItems(-1, sdk.items.mode.inStorage); - - for (let i = 0; i < this.recipes.length; i += 1) { - // Set default Enabled property - true if recipe is always enabled, false otherwise - this.recipes[i].Enabled = this.recipes[i].hasOwnProperty("AlwaysEnabled"); - - IngredientLoop: - for (let j = 0; j < this.recipes[i].Ingredients.length; j += 1) { - for (let k = 0; k < items.length; k += 1) { - if (((this.recipes[i].Ingredients[j] === "pgem" && this.gemList.includes(items[k].classid)) - || (this.recipes[i].Ingredients[j] === "cgem" && this.chippedGems.includes(items[k].classid)) - || items[k].classid === this.recipes[i].Ingredients[j]) && this.validItem(items[k], this.recipes[i])) { - - // push the item's info into the valid ingredients array. this will be used to find items when checking recipes - this.validIngredients.push({classid: items[k].classid, gid: items[k].gid}); - - // Remove from item list to prevent counting the same item more than once - items.splice(k, 1); - - k -= 1; - - // Enable recipes for gem/jewel pickup - if (this.recipes[i].Index !== Recipe.Rune || (this.recipes[i].Index === Recipe.Rune && j >= 1)) { - // Enable rune recipe after 2 bases are found - this.recipes[i].Enabled = true; - } - - continue IngredientLoop; - } - } - - // add the item to needed list - enable pickup - this.neededIngredients.push({classid: this.recipes[i].Ingredients[j], recipe: this.recipes[i]}); - - // skip flawless gems adding if we don't have the main item (Recipe.Gem and Recipe.Rune for el-ort are always enabled) - if (!this.recipes[i].Enabled) { - break; - } - - // if the recipe is enabled (we have the main item), add flawless gem recipes (if needed) - - // Make perf amethyst - if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Amethyst) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Perfect.Amethyst || (this.recipes[i].Ingredients[j] === "pgem" && this.gemList.indexOf(sdk.items.gems.Perfect.Amethyst) > -1))) { - this.recipes.push({Ingredients: [sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Amethyst], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index}); - this.subRecipes.push(sdk.items.gems.Perfect.Amethyst); - } - - // Make perf topaz - if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Topaz) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Perfect.Topaz || (this.recipes[i].Ingredients[j] === "pgem" && this.gemList.indexOf(sdk.items.gems.Perfect.Topaz) > -1))) { - this.recipes.push({Ingredients: [sdk.items.gems.Flawless.Topaz, sdk.items.gems.Flawless.Topaz, sdk.items.gems.Flawless.Topaz], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index}); - this.subRecipes.push(sdk.items.gems.Perfect.Topaz); - } - - // Make perf sapphire - if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Sapphire) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Perfect.Sapphire || (this.recipes[i].Ingredients[j] === "pgem" && this.gemList.indexOf(sdk.items.gems.Perfect.Sapphire) > -1))) { - this.recipes.push({Ingredients: [sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Sapphire], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index}); - this.subRecipes.push(sdk.items.gems.Perfect.Sapphire); - } - - // Make perf emerald - if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Emerald) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Perfect.Emerald || (this.recipes[i].Ingredients[j] === "pgem" && this.gemList.indexOf(sdk.items.gems.Perfect.Emerald) > -1))) { - this.recipes.push({Ingredients: [sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Emerald], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index}); - this.subRecipes.push(sdk.items.gems.Perfect.Emerald); - } - - // Make perf ruby - if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Ruby) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Perfect.Ruby || (this.recipes[i].Ingredients[j] === "pgem" && this.gemList.indexOf(sdk.items.gems.Perfect.Ruby) > -1))) { - this.recipes.push({Ingredients: [sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Ruby], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index}); - this.subRecipes.push(sdk.items.gems.Perfect.Ruby); - } - - // Make perf diamond - if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Diamond) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Perfect.Diamond || (this.recipes[i].Ingredients[j] === "pgem" && this.gemList.indexOf(sdk.items.gems.Perfect.Diamond) > -1))) { - this.recipes.push({Ingredients: [sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Diamond], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index}); - this.subRecipes.push(sdk.items.gems.Perfect.Diamond); - } - - // Make perf skull - if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Skull) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Perfect.Skull || (this.recipes[i].Ingredients[j] === "pgem" && this.gemList.indexOf(sdk.items.gems.Perfect.Skull) > -1))) { - this.recipes.push({Ingredients: [sdk.items.gems.Flawless.Skull, sdk.items.gems.Flawless.Skull, sdk.items.gems.Flawless.Skull], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index}); - this.subRecipes.push(sdk.items.gems.Perfect.Skull); - } - } - } - }, - - // Remove unneeded flawless gem recipes - clearSubRecipes: function () { - this.subRecipes = []; - - for (let i = 0; i < this.recipes.length; i += 1) { - if (this.recipes[i].hasOwnProperty("MainRecipe")) { - this.recipes.splice(i, 1); - - i -= 1; - } - } - }, - - update: function () { - this.clearSubRecipes(); - this.buildLists(); - }, - - checkRecipe: function (recipe) { - let usedGids = []; - let matchList = []; - - for (let i = 0; i < recipe.Ingredients.length; i += 1) { - for (let j = 0; j < this.validIngredients.length; j += 1) { - if (usedGids.indexOf(this.validIngredients[j].gid) === -1 && ( - this.validIngredients[j].classid === recipe.Ingredients[i] - || (recipe.Ingredients[i] === "pgem" && this.gemList.includes(this.validIngredients[j].classid)) - || (recipe.Ingredients[i] === "cgem" && this.chippedGems.includes(this.validIngredients[j].classid)) - )) { - let item = me.getItem(this.validIngredients[j].classid, -1, this.validIngredients[j].gid); - - // 26.11.2012. check if the item actually belongs to the given recipe - if (item && this.validItem(item, recipe)) { - // don't repeat the same item - usedGids.push(this.validIngredients[j].gid); - // push the item into the match list - matchList.push(copyUnit(item)); - - break; - } - } - } - - // no new items in the match list = not enough ingredients - if (matchList.length !== i + 1) return false; - } - - // return the match list. these items go to cube - return matchList; - }, - - // debug function - get what each recipe needs - getRecipeNeeds: function (index) { - let rval = " ["; - - for (let i = 0; i < this.neededIngredients.length; i += 1) { - if (this.neededIngredients[i].recipe.Index === index) { - rval += this.neededIngredients[i].classid + (i === this.neededIngredients.length - 1 ? "" : " "); - } - } - - rval += "]"; - - return rval; - }, - - // Check an item on ground for pickup - checkItem: function (unit) { - if (!Config.Cubing) return false; - if (this.keepItem(unit)) return true; - - for (let i = 0; i < this.neededIngredients.length; i += 1) { - if (unit.classid === this.neededIngredients[i].classid && this.validItem(unit, this.neededIngredients[i].recipe)) { - //debugLog("Cubing: " + unit.name + " " + this.neededIngredients[i].recipe.Index + " " + (this.neededIngredients[i].recipe.hasOwnProperty("MainRecipe") ? this.neededIngredients[i].recipe.MainRecipe : "") + this.getRecipeNeeds(this.neededIngredients[i].recipe.Index)); - return true; - } - } - - return false; - }, - - // Don't drop an item from inventory if it's a part of cubing recipe - keepItem: function (unit) { - if (!Config.Cubing) return false; - - for (let i = 0; i < this.validIngredients.length; i += 1) { - if (unit.mode === sdk.items.mode.inStorage && unit.gid === this.validIngredients[i].gid) { - return true; - } - } - - return false; - }, - - validItem: function (unit, recipe) { - // Excluded items - // Don't use items in locked inventory space - or wanted by other systems - if ((unit.isInInventory && Storage.Inventory.IsLocked(unit, Config.Inventory) - || Runewords.validGids.includes(unit.gid) || CraftingSystem.validGids.includes(unit.gid))) { - return false; - } - - // Gems and runes - if ((unit.itemType >= sdk.items.type.Amethyst && unit.itemType <= sdk.items.type.Skull) || unit.itemType === sdk.items.type.Rune) { - if (!recipe.Enabled && recipe.Ingredients[0] !== unit.classid && recipe.Ingredients[1] !== unit.classid) { - return false; - } - - return true; - } - - // Token - if (recipe.Index === Recipe.Token) return true; - - // START - const ntipResult = NTIP.CheckItem(unit); - - if (recipe.Index >= Recipe.HitPower.Helm && recipe.Index <= Recipe.Safety.Weapon) { - // Junk jewels (NOT matching a pickit entry) - if (unit.itemType === sdk.items.type.Jewel) { - if (recipe.Enabled && ntipResult === Pickit.Result.UNWANTED) { - return true; - } - // Main item, NOT matching a pickit entry - } else if (unit.magic && Math.floor(me.charlvl / 2) + Math.floor(unit.ilvl / 2) >= recipe.Level && ntipResult === Pickit.Result.UNWANTED) { - return true; - } - - return false; - } - - let upgradeUnique = recipe.Index >= Recipe.Unique.Weapon.ToExceptional && recipe.Index <= Recipe.Unique.Armor.ToElite; - let upgradeRare = recipe.Index >= Recipe.Rare.Weapon.ToExceptional && recipe.Index <= Recipe.Rare.Armor.ToElite; - let socketNormal = recipe.Index >= Recipe.Socket.Shield && recipe.Index <= Recipe.Socket.Helm; - - if (upgradeUnique || upgradeRare || socketNormal) { - switch (true) { - case upgradeUnique && unit.unique && ntipResult === Pickit.Result.WANTED: // Unique item matching pickit entry - case upgradeRare && unit.rare && ntipResult === Pickit.Result.WANTED: // Rare item matching pickit entry - case socketNormal && unit.normal && unit.sockets === 0: // Normal item matching pickit entry, no sockets - switch (recipe.Ethereal) { - case Roll.All: - case undefined: - return ntipResult === Pickit.Result.WANTED; - case Roll.Eth: - return unit.ethereal && ntipResult === Pickit.Result.WANTED; - case Roll.NonEth: - return !unit.ethereal && ntipResult === Pickit.Result.WANTED; - } - - return false; - } - - return false; - } - - if (recipe.Index === Recipe.Reroll.Magic) { - return (unit.magic && unit.ilvl >= recipe.Level && ntipResult === Pickit.Result.UNWANTED); - } - - if (recipe.Index === Recipe.Reroll.Rare) { - return (unit.rare && ntipResult === Pickit.Result.UNWANTED); - } - - if (recipe.Index === Recipe.Reroll.HighRare) { - if (recipe.Ingredients[0] === unit.classid && unit.rare && ntipResult === Pickit.Result.UNWANTED) { - recipe.Enabled = true; - - return true; - } - - if (recipe.Enabled && recipe.Ingredients[2] === unit.classid && unit.itemType === sdk.items.type.Ring - && unit.getStat(sdk.stats.MaxManaPercent) && !Storage.Inventory.IsLocked(unit, Config.Inventory)) { - return true; - } - - return false; - } - - if (recipe.Index === Recipe.LowToNorm.Armor || recipe.Index === Recipe.LowToNorm.Weapon) { - return (unit.lowquality && ntipResult === Pickit.Result.UNWANTED); - } - - return false; - }, - - doCubing: function () { - if (!Config.Cubing) return false; - if (!me.getItem(sdk.quest.item.Cube) && !this.getCube()) return false; - - this.update(); - // Randomize the recipe array to prevent recipe blocking (multiple caster items etc.) - let tempArray = this.recipes.slice().shuffle(); - - for (let i = 0; i < tempArray.length; i += 1) { - let string = "Transmuting: "; - let items = this.checkRecipe(tempArray[i]); - - if (items) { - // If cube isn't open, attempt to open stash (the function returns true if stash is already open) - if ((!getUIFlag(sdk.uiflags.Cube) && !Town.openStash()) || !this.emptyCube()) return false; - - this.cursorCheck(); - - i = -1; - - while (items.length) { - string += (items[0].name.trim() + (items.length > 1 ? " + " : "")); - Storage.Cube.MoveTo(items[0]); - items.shift(); - } - - if (!this.openCube()) return false; - - transmute(); - delay(700 + me.ping); - print("ÿc4Cubing: " + string); - Config.ShowCubingInfo && D2Bot.printToConsole(string, sdk.colors.D2Bot.Green); - this.update(); - - let cubeItems = me.findItems(-1, -1, sdk.storage.Cube); - - if (items) { - for (let j = 0; j < cubeItems.length; j += 1) { - let result = Pickit.checkItem(cubeItems[j]); - - switch (result.result) { - case Pickit.Result.UNWANTED: - Misc.itemLogger("Dropped", cubeItems[j], "doCubing"); - cubeItems[j].drop(); - - break; - case Pickit.Result.WANTED: - Misc.itemLogger("Cubing Kept", cubeItems[j]); - Misc.logItem("Cubing Kept", cubeItems[j], result.line); - - break; - case Pickit.Result.CRAFTING: - CraftingSystem.update(cubeItems[j]); - - break; - } - } - } - - if (!this.emptyCube()) { - break; - } - } - } - - if (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { - delay(1000); - - while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { - me.cancel(); - delay(300); - } - } - - return true; - }, - - cursorCheck: function () { - if (me.itemoncursor) { - let item = Game.getCursorUnit(); - - if (item) { - if (Storage.Inventory.CanFit(item) && Storage.Inventory.MoveTo(item)) return true; - if (Storage.Stash.CanFit(item) && Storage.Stash.MoveTo(item)) return true; - - if (item.drop()) { - Misc.itemLogger("Dropped", item, "cursorCheck"); - return true; - } - } - - return false; - } - - return true; - }, - - openCube: function () { - let cube = me.getItem(sdk.quest.item.Cube); - - if (!cube) return false; - if (getUIFlag(sdk.uiflags.Cube)) return true; - if (cube.isInStash && !Town.openStash()) return false; - - for (let i = 0; i < 3; i += 1) { - cube.interact(); - let tick = getTickCount(); - - while (getTickCount() - tick < 5000) { - if (getUIFlag(sdk.uiflags.Cube)) { - delay(100 + me.ping * 2); // allow UI to initialize - - return true; - } - - delay(100); - } - } - - return false; - }, - - closeCube: function () { - if (!getUIFlag(sdk.uiflags.Cube)) return true; - - for (let i = 0; i < 5; i++) { - me.cancel(); - let tick = getTickCount(); - - while (getTickCount() - tick < 3000) { - if (!getUIFlag(sdk.uiflags.Cube)) { - delay(250 + me.ping * 2); // allow UI to initialize - return true; - } - - delay(100); - } - } - - return false; - }, - - emptyCube: function () { - let cube = me.getItem(sdk.quest.item.Cube); - let items = me.findItems(-1, -1, sdk.storage.Cube); - - if (!cube) return false; - if (!items) return true; - - while (items.length) { - if (!Storage.Stash.MoveTo(items[0]) && !Storage.Inventory.MoveTo(items[0])) { - return false; - } - - items.shift(); - } - - return true; - }, - - makeRevPots: function () { - let locations = { - Belt: 2, - Inventory: 3, - Cube: 6, - Stash: 7, - }; - let origin = [], cube = me.getItem(sdk.quest.item.Cube), cubeInStash; - - // Get a list of all items - Filter out all those rev pots - let revpots = me.getItemsEx().filter(item => item.classid === sdk.items.RejuvenationPotion); - - // Stop if less as 3 pots - if (revpots.length < 3) { - return; - } - - // Go to town and open stash - Town.goToTown() && Town.moveToSpot("stash"); - Town.openStash(); - - // For reasons unclear, cubing goes wrong in stash in my test, so for ease, i put cube in inventory - (cubeInStash = cube.location !== locations.Inventory) && Storage.Inventory.MoveTo(cube); - me.cancel(); - me.cancel(); - - // clear the cube, otherwise we cant transmute - Cubing.emptyCube(); - - // Remove excessive pots from the list. (only groups of 3) - revpots.length -= revpots.length % 3; - - // Call this function for each pot - revpots.forEach(function (pot, index) { - - // Add this to the original location array - origin.push({location: pot.location, x: pot.x, y: pot.y}); - - Town.openStash(); - - // Move to inventory first (to avoid bugs) - Storage.Inventory.MoveTo(pot); - me.cancel(); // remove inventory/cube window - me.cancel(); // remove inventory window (if it was cube) - - // Move the current pot to the cube - Storage.Cube.MoveTo(pot); - // For every third pot, excluding the first - if (!index || (1 + index) % 3 !== 0) { - me.cancel(); // remove cube window - me.cancel(); // remove stash window - } else { - // press the transmute button - Cubing.openCube() && transmute(); - - // high delay here to avoid issues with ping spikes - delay(me.ping * 5 + 1000); // <-- probably can be less - - // Find all items in the cube. (the full rev pot) - let fullrev = me.findItem(-1, -1, sdk.storage.Cube); - - // Sort the original locations of the pots. Put a low location first (belt = 2, rest is higher). - origin.sort((a, b) => a.location - b.location).some(function (orgin) { // Loop over all the original spots. - - // Loop trough all possible locations - for (let i in locations) { - // If location is matched with its orgin, we know the name of the spot - locations[i] === orgin.location && (orgin.location = i); // Store the name of the location - } - - Storage.Inventory.MoveTo(fullrev); // First put to inventory; - me.cancel(); // cube - me.cancel(); // inventory - - // If the storage location is known, put the pot to this location - Storage[orgin.location] && Storage[orgin.location].MoveTo(fullrev); - - // If returned true, the prototype some stops looping. - return fullrev.location !== locations.Cube; - }); - - // empty the array - origin.length = 0; - - // Cube should be empty, but lets be sure - Cubing.emptyCube(); - } - }); - // Put cube back in stash, if it was when we started - cubeInStash && Storage.Stash.MoveTo(cube); - - me.cancel(); - me.cancel(); - }, -}; diff --git a/d2bs/kolbot/libs/common/Item.js b/d2bs/kolbot/libs/common/Item.js deleted file mode 100644 index efa80e75f..000000000 --- a/d2bs/kolbot/libs/common/Item.js +++ /dev/null @@ -1,240 +0,0 @@ -/** -* @filename Item.js -* @author kolton, theBGuy -* @desc handle item and autoequip related things -* -*/ - -// torn on if this should be broken up in two classes Item and AutoEquip, for now leaving as is -const Item = { - hasTier: function (item) { - return Config.AutoEquip && NTIP.GetTier(item) > 0; - }, - - canEquip: function (item) { - // Not an item or unid - if (!item || item.type !== sdk.unittype.Item || !item.identified) return false; - // Higher requirements - if (item.getStat(sdk.stats.LevelReq) > me.getStat(sdk.stats.Level) || item.dexreq > me.getStat(sdk.stats.Dexterity) || item.strreq > me.getStat(sdk.stats.Strength)) return false; - - return true; - }, - - // Equips an item and throws away the old equipped item - equip: function (item, bodyLoc) { - if (!this.canEquip(item)) return false; - - // Already equipped in the right slot - if (item.mode === sdk.items.mode.Equipped && item.bodylocation === bodyLoc) return true; - if (item.isInStash && !Town.openStash()) return false; - - for (let i = 0; i < 3; i += 1) { - if (item.toCursor()) { - clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc); - - if (item.bodylocation === bodyLoc) { - if (getCursorType() === 3) { - let cursorItem = Game.getCursorUnit(); - - if (cursorItem) { - if (!Storage.Inventory.CanFit(cursorItem) || !Storage.Inventory.MoveTo(cursorItem)) { - cursorItem.drop(); - } - } - } - - return true; - } - } - } - - return false; - }, - - getEquippedItem: function (bodyLoc) { - let item = me.getItem(); - - if (item) { - do { - if (item.bodylocation === bodyLoc) { - return { - classid: item.classid, - tier: NTIP.GetTier(item) - }; - } - } while (item.getNext()); - } - - // Don't have anything equipped in there - return { - classid: -1, - tier: -1 - }; - }, - - getBodyLoc: function (item) { - let bodyLoc; - - switch (item.itemType) { - case sdk.items.type.Shield: - case sdk.items.type.AuricShields: - case sdk.items.type.VoodooHeads: - case sdk.items.type.BowQuiver: - case sdk.items.type.CrossbowQuiver: - bodyLoc = sdk.body.LeftArm; - - break; - case sdk.items.type.Armor: - bodyLoc = sdk.body.Armor; - - break; - case sdk.items.type.Ring: - bodyLoc = [sdk.body.RingRight, sdk.body.RingLeft]; - - break; - case sdk.items.type.Amulet: - bodyLoc = sdk.body.Neck; - - break; - case sdk.items.type.Boots: - bodyLoc = sdk.body.Feet; - - break; - case sdk.items.type.Gloves: - bodyLoc = sdk.body.Gloves; - - break; - case sdk.items.type.Belt: - bodyLoc = sdk.body.Belt; - - break; - case sdk.items.type.Helm: - case sdk.items.type.PrimalHelm: - case sdk.items.type.Circlet: - case sdk.items.type.Pelt: - bodyLoc = sdk.body.Head; - - break; - case sdk.items.type.Scepter: - case sdk.items.type.Wand: - case sdk.items.type.Staff: - case sdk.items.type.Bow: - case sdk.items.type.Axe: - case sdk.items.type.Club: - case sdk.items.type.Sword: - case sdk.items.type.Hammer: - case sdk.items.type.Knife: - case sdk.items.type.Spear: - case sdk.items.type.Polearm: - case sdk.items.type.Crossbow: - case sdk.items.type.Mace: - case sdk.items.type.ThrowingKnife: - case sdk.items.type.ThrowingAxe: - case sdk.items.type.Javelin: - case sdk.items.type.Orb: - case sdk.items.type.AmazonBow: - case sdk.items.type.AmazonSpear: - case sdk.items.type.AmazonJavelin: - case sdk.items.type.MissilePotion: - bodyLoc = me.barbarian ? [sdk.body.RightArm, sdk.body.LeftArm] : sdk.body.RightArm; - - break; - case sdk.items.type.HandtoHand: - case sdk.items.type.AssassinClaw: - bodyLoc = me.assassin ? [sdk.body.RightArm, sdk.body.LeftArm] : sdk.body.RightArm; - - break; - default: - return false; - } - - !Array.isArray(bodyLoc) && (bodyLoc = [bodyLoc]); - - return bodyLoc; - }, - - autoEquipCheck: function (item) { - if (!Config.AutoEquip) return true; - - let tier = NTIP.GetTier(item); - let bodyLoc = this.getBodyLoc(item); - - if (tier > 0 && bodyLoc) { - for (let i = 0; i < bodyLoc.length; i += 1) { - // Low tier items shouldn't be kept if they can't be equipped - if (tier > this.getEquippedItem(bodyLoc[i]).tier && (this.canEquip(item) || !item.getFlag(sdk.items.flags.Identified))) { - return true; - } - } - } - - // Sell/ignore low tier items, keep high tier - if (tier > 0 && tier < 100) return false; - - return true; - }, - - // returns true if the item should be kept+logged, false if not - autoEquip: function () { - if (!Config.AutoEquip) return true; - - let items = me.findItems(-1, sdk.items.mode.inStorage); - - if (!items) return false; - - function sortEq(a, b) { - if (Item.canEquip(a)) return -1; - if (Item.canEquip(b)) return 1; - - return 0; - } - - me.cancel(); - - // Remove items without tier - for (let i = 0; i < items.length; i += 1) { - if (NTIP.GetTier(items[i]) === 0) { - items.splice(i, 1); - - i -= 1; - } - } - - while (items.length > 0) { - items.sort(sortEq); - - let tier = NTIP.GetTier(items[0]); - let bodyLoc = this.getBodyLoc(items[0]); - - if (tier > 0 && bodyLoc) { - for (let j = 0; j < bodyLoc.length; j += 1) { - // khalim's will adjustment - const equippedItem = this.getEquippedItem(bodyLoc[j]); - if (items[0].isInStorage && tier > equippedItem.tier && equippedItem.classid !== sdk.items.quest.KhalimsWill) { - if (!items[0].identified) { - let tome = me.findItem(sdk.items.TomeofIdentify, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (tome && tome.getStat(sdk.stats.Quantity) > 0) { - items[0].isInStash && Town.openStash(); - Town.identifyItem(items[0], tome); - } - } - - let gid = items[0].gid; - console.log(items[0].name); - - if (this.equip(items[0], bodyLoc[j])) { - Misc.logItem("Equipped", me.getItem(-1, -1, gid)); - } - - break; - } - } - } - - items.shift(); - } - - return true; - } -}; diff --git a/d2bs/kolbot/libs/common/Loader.js b/d2bs/kolbot/libs/common/Loader.js deleted file mode 100644 index e33806972..000000000 --- a/d2bs/kolbot/libs/common/Loader.js +++ /dev/null @@ -1,257 +0,0 @@ -/** -* @filename Loader.js -* @author kolton, theBGuy -* @desc script loader, based on mBot's Sequencer.js -* -*/ - -let global = this; - -const Loader = { - fileList: [], - scriptList: [], - scriptIndex: -1, - skipTown: ["Test", "Follower"], - - init: function () { - this.getScripts(); - this.loadScripts(); - }, - - getScripts: function () { - let fileList = dopen("libs/bots/").getFiles(); - - for (let i = 0; i < fileList.length; i += 1) { - if (fileList[i].indexOf(".js") > -1) { - this.fileList.push(fileList[i].substring(0, fileList[i].indexOf(".js"))); - } - } - }, - - // see http://stackoverflow.com/questions/728360/copying-an-object-in-javascript#answer-728694 - clone: function (obj) { - let copy; - - // Handle the 3 simple types, and null or undefined - if (null === obj || "object" !== typeof obj) { - return obj; - } - - // Handle Date - if (obj instanceof Date) { - copy = new Date(); - copy.setTime(obj.getTime()); - - return copy; - } - - // Handle Array - if (obj instanceof Array) { - copy = []; - - for (let i = 0; i < obj.length; i += 1) { - copy[i] = this.clone(obj[i]); - } - - return copy; - } - - // Handle Object - if (obj instanceof Object) { - copy = {}; - - for (let attr in obj) { - if (obj.hasOwnProperty(attr)) { - copy[attr] = this.clone(obj[attr]); - } - } - - return copy; - } - - throw new Error("Unable to copy obj! Its type isn't supported."); - }, - - copy: function (from, to) { - for (let i in from) { - if (from.hasOwnProperty(i)) { - to[i] = this.clone(from[i]); - } - } - }, - - loadScripts: function () { - let reconfiguration, unmodifiedConfig = {}; - - this.copy(Config, unmodifiedConfig); - - if (!this.fileList.length) { - showConsole(); - - throw new Error("You don't have any valid scripts in bots folder."); - } - - for (let s in Scripts) { - if (Scripts.hasOwnProperty(s) && Scripts[s]) { - this.scriptList.push(s); - } - } - - for (this.scriptIndex = 0; this.scriptIndex < this.scriptList.length; this.scriptIndex++) { - let script = this.scriptList[this.scriptIndex]; - - if (this.fileList.indexOf(script) === -1) { - if (FileTools.exists("bots/" + script + ".js")) { - console.warn("ÿc1Something went wrong in loader, file exists in folder but didn't get included during init process. Lets ignore the error and continue to include the script by name instead"); - } else { - Misc.errorReport("ÿc1Script " + script + " doesn't exist."); - - continue; - } - } - - if (!include("bots/" + script + ".js")) { - Misc.errorReport("Failed to include script: " + script); - continue; - } - - if (isIncluded("bots/" + script + ".js")) { - try { - if (typeof (global[script]) !== "function") { - throw new Error("Invalid script function name"); - } - - if (this.skipTown.includes(script) || Town.goToTown()) { - print("ÿc2Starting script: ÿc9" + script); - Messaging.sendToScript("tools/toolsthread.js", JSON.stringify({currScript: script})); - reconfiguration = typeof Scripts[script] === "object"; - - if (reconfiguration) { - print("ÿc2Copying Config properties from " + script + " object."); - this.copy(Scripts[script], Config); - } - - let tick = getTickCount(); - - if (me.inTown) { - Config.StackThawingPots.enabled && Town.buyPots(Config.StackThawingPots.quantity, sdk.items.ThawingPotion, true); - Config.StackAntidotePots.enabled && Town.buyPots(Config.StackAntidotePots.quantity, sdk.items.AntidotePotion, true); - Config.StackStaminaPots.enabled && Town.buyPots(Config.StackStaminaPots.quantity, sdk.items.StaminaPotion, true); - } - - // kinda hacky, but faster for mfhelpers to stop - if (Config.MFLeader && Config.PublicMode && ["Diablo", "Baal"].includes(script)) { - say("nextup " + script); - } - - if (global[script]()) { - console.log("ÿc7" + script + " :: ÿc0Complete ÿc0- ÿc7Duration: ÿc0" + (Time.format(getTickCount() - tick))); - } - } - } catch (error) { - Misc.errorReport(error, script); - } finally { - // Dont run for last script as that will clear everything anyway - if (this.scriptIndex < this.scriptList.length) { - // remove script function from global scope, so it can be cleared by GC - delete global[script]; - } - - if (reconfiguration) { - print("ÿc2Reverting back unmodified config properties."); - this.copy(unmodifiedConfig, Config); - } - } - } - } - }, - - tempList: [], - - runScript: function (script, configOverride) { - let reconfiguration, unmodifiedConfig = {}; - let failed = false; - let mainScript = this.scriptName(); - - function buildScriptMsg () { - let str = "ÿc9" + mainScript + " ÿc0:: "; - - if (Loader.tempList.length && Loader.tempList[0] !== mainScript) { - Loader.tempList.forEach(s => str += "ÿc9" + s + " ÿc0:: "); - } - - return str; - } - - this.copy(Config, unmodifiedConfig); - - if (!include("bots/" + script + ".js")) { - Misc.errorReport("Failed to include script: " + script); - - return false; - } - - if (isIncluded("bots/" + script + ".js")) { - try { - if (typeof (global[script]) !== "function") { - throw new Error("Invalid script function name"); - } - - if (this.skipTown.includes(script) || Town.goToTown()) { - let mainScriptStr = (mainScript !== script ? buildScriptMsg() : ""); - this.tempList.push(script); - print(mainScriptStr + "ÿc2Starting script: ÿc9" + script); - Messaging.sendToScript("tools/toolsthread.js", JSON.stringify({currScript: script})); - - reconfiguration = typeof Scripts[script] === "object"; - - if (reconfiguration) { - print("ÿc2Copying Config properties from " + script + " object."); - this.copy(Scripts[script], Config); - } - - if (typeof configOverride === "function") { - reconfiguration = true; - configOverride(); - } - - let tick = getTickCount(); - - if (global[script]()) { - console.log(mainScriptStr + "ÿc7" + script + " :: ÿc0Complete ÿc0- ÿc7Duration: ÿc0" + (Time.format(getTickCount() - tick))); - } - } - } catch (error) { - Misc.errorReport(error, script); - failed = true; - } finally { - // Dont run for last script as that will clear everything anyway - if (this.scriptIndex < this.scriptList.length) { - // remove script function from global scope, so it can be cleared by GC - delete global[script]; - } else if (this.tempList.length) { - delete global[script]; - } - - this.tempList.pop(); - - if (reconfiguration) { - print("ÿc2Reverting back unmodified config properties."); - this.copy(unmodifiedConfig, Config); - } - } - } - - return !failed; - }, - - scriptName: function (offset = 0) { - let index = this.scriptIndex + offset; - - if (index >= 0 && index < this.scriptList.length) { - return this.scriptList[index]; - } - - return null; - } -}; diff --git a/d2bs/kolbot/libs/common/Misc.js b/d2bs/kolbot/libs/common/Misc.js deleted file mode 100644 index 8fe699b20..000000000 --- a/d2bs/kolbot/libs/common/Misc.js +++ /dev/null @@ -1,2742 +0,0 @@ -/** -* @filename Misc.js -* @author kolton, theBGuy -* @desc misc library containing Skill, Misc and Sort classes -* -*/ - -includeIfNotIncluded("common/Item.js"); - -const Skill = { - usePvpRange: false, - skills: { - initialized: false, - all: [], - addSkills: function (skill, condition = () => true) { - this.all[skill] = { - hardpoints: false, - checked: false, - condition: condition, - have: function () { - if (!this.condition()) return false; - if (skill === undefined) return false; - if (this.hardpoints) return true; - if (!this.checked) { - this.hardpoints = !!me.getSkill(skill, sdk.skills.subindex.HardPoints); - this.checked = true; - } - return (this.hardpoints || me.getSkill(skill, sdk.skills.subindex.SoftPoints)); - } - }; - }, - init: function () { - this.addSkills(sdk.skills.MagicArrow); - this.addSkills(sdk.skills.FireArrow); - this.addSkills(sdk.skills.InnerSight, () => Config.UseInnerSight); - this.addSkills(sdk.skills.Jab); - this.addSkills(sdk.skills.ColdArrow); - this.addSkills(sdk.skills.MultipleShot); - this.addSkills(sdk.skills.PowerStrike); - this.addSkills(sdk.skills.PoisonJavelin); - this.addSkills(sdk.skills.ExplodingArrow); - this.addSkills(sdk.skills.SlowMissiles, () => Config.UseSlowMissiles); - this.addSkills(sdk.skills.LightningBolt); - this.addSkills(sdk.skills.IceArrow); - this.addSkills(sdk.skills.GuidedArrow); - this.addSkills(sdk.skills.ChargedStrike); - this.addSkills(sdk.skills.Strafe); - this.addSkills(sdk.skills.ImmolationArrow); - this.addSkills(sdk.skills.Decoy, () => Config.UseDecoy); - this.addSkills(sdk.skills.Fend); - this.addSkills(sdk.skills.FreezingArrow); - this.addSkills(sdk.skills.Valkyrie, () => Config.SummonValkyrie); - this.addSkills(sdk.skills.LightningStrike); - this.addSkills(sdk.skills.LightningFury); - // sorceress skills start - this.addSkills(sdk.skills.FireBolt); - this.addSkills(sdk.skills.ChargedBolt); - this.addSkills(sdk.skills.IceBolt); - this.addSkills(sdk.skills.FrozenArmor); - this.addSkills(sdk.skills.Inferno); - this.addSkills(sdk.skills.StaticField); - this.addSkills(sdk.skills.Telekinesis, () => Config.UseTelekinesis); - this.addSkills(sdk.skills.FrostNova); - this.addSkills(sdk.skills.IceBlast); - this.addSkills(sdk.skills.Blaze); - this.addSkills(sdk.skills.FireBall); - this.addSkills(sdk.skills.Nova); - this.addSkills(sdk.skills.Lightning); - this.addSkills(sdk.skills.ShiverArmor); - this.addSkills(sdk.skills.FireWall); - this.addSkills(sdk.skills.Enchant); - this.addSkills(sdk.skills.ChainLightning); - this.addSkills(sdk.skills.Teleport); - this.addSkills(sdk.skills.GlacialSpike); - this.addSkills(sdk.skills.Meteor); - this.addSkills(sdk.skills.ThunderStorm); - this.addSkills(sdk.skills.EnergyShield, () => Config.UseEnergyShield); - this.addSkills(sdk.skills.Blizzard); - this.addSkills(sdk.skills.ChillingArmor); - this.addSkills(sdk.skills.Hydra); - this.addSkills(sdk.skills.FrozenOrb); - // necromancer skills start - this.addSkills(sdk.skills.AmplifyDamage); - this.addSkills(sdk.skills.Teeth); - this.addSkills(sdk.skills.BoneArmor); - this.addSkills(sdk.skills.RaiseSkeleton); - this.addSkills(sdk.skills.DimVision); - this.addSkills(sdk.skills.Weaken); - this.addSkills(sdk.skills.PoisonDagger); - this.addSkills(sdk.skills.CorpseExplosion); - this.addSkills(sdk.skills.ClayGolem); - this.addSkills(sdk.skills.IronMaiden); - this.addSkills(sdk.skills.Terror); - this.addSkills(sdk.skills.BoneWall); - this.addSkills(sdk.skills.RaiseSkeletalMage); - this.addSkills(sdk.skills.Confuse); - this.addSkills(sdk.skills.LifeTap); - this.addSkills(sdk.skills.PoisonExplosion); - this.addSkills(sdk.skills.BoneSpear); - this.addSkills(sdk.skills.BloodGolem); - this.addSkills(sdk.skills.Attract); - this.addSkills(sdk.skills.Decrepify); - this.addSkills(sdk.skills.BonePrison); - this.addSkills(sdk.skills.IronGolem); - this.addSkills(sdk.skills.LowerResist); - this.addSkills(sdk.skills.PoisonNova); - this.addSkills(sdk.skills.BoneSpirit); - this.addSkills(sdk.skills.FireGolem); - this.addSkills(sdk.skills.Revive); - // paladin skills start - this.addSkills(sdk.skills.Sacrifice); - this.addSkills(sdk.skills.Smite); - this.addSkills(sdk.skills.Might); - this.addSkills(sdk.skills.Prayer); - this.addSkills(sdk.skills.ResistFire); - this.addSkills(sdk.skills.HolyBolt); - this.addSkills(sdk.skills.HolyFire); - this.addSkills(sdk.skills.Thorns); - this.addSkills(sdk.skills.Defiance); - this.addSkills(sdk.skills.ResistCold); - this.addSkills(sdk.skills.Zeal); - this.addSkills(sdk.skills.Charge, () => Config.Charge); - this.addSkills(sdk.skills.BlessedAim); - this.addSkills(sdk.skills.Cleansing); - this.addSkills(sdk.skills.ResistLightning); - this.addSkills(sdk.skills.Vengeance); - this.addSkills(sdk.skills.BlessedHammer); - this.addSkills(sdk.skills.Concentration); - this.addSkills(sdk.skills.HolyFreeze); - this.addSkills(sdk.skills.Vigor, () => Config.Vigor || me.inTown); - this.addSkills(sdk.skills.Conversion); - this.addSkills(sdk.skills.HolyShield); - this.addSkills(sdk.skills.HolyShock); - this.addSkills(sdk.skills.Sanctuary); - this.addSkills(sdk.skills.Meditation); - this.addSkills(sdk.skills.FistoftheHeavens); - this.addSkills(sdk.skills.Fanaticism); - this.addSkills(sdk.skills.Conviction); - this.addSkills(sdk.skills.Redemption); - this.addSkills(sdk.skills.Salvation); - // barbarian skills start - this.addSkills(sdk.skills.Bash); - this.addSkills(sdk.skills.Howl); - this.addSkills(sdk.skills.FindPotion); - this.addSkills(sdk.skills.Leap); - this.addSkills(sdk.skills.DoubleSwing); - this.addSkills(sdk.skills.Taunt); - this.addSkills(sdk.skills.Shout); - this.addSkills(sdk.skills.Stun); - this.addSkills(sdk.skills.DoubleThrow); - this.addSkills(sdk.skills.FindItem, () => Config.FindItem); - this.addSkills(sdk.skills.LeapAttack); - this.addSkills(sdk.skills.BattleCry); - this.addSkills(sdk.skills.Frenzy); - this.addSkills(sdk.skills.BattleOrders); - this.addSkills(sdk.skills.GrimWard); - this.addSkills(sdk.skills.Whirlwind); - this.addSkills(sdk.skills.Berserk); - this.addSkills(sdk.skills.WarCry); - this.addSkills(sdk.skills.BattleCommand); - // druid skills start - this.addSkills(sdk.skills.Raven, () => Config.SummonRaven); - this.addSkills(sdk.skills.PoisonCreeper); - this.addSkills(sdk.skills.Werewolf); - this.addSkills(sdk.skills.Firestorm); - this.addSkills(sdk.skills.OakSage); - this.addSkills(sdk.skills.SpiritWolf); - this.addSkills(sdk.skills.Werebear); - this.addSkills(sdk.skills.MoltenBoulder); - this.addSkills(sdk.skills.ArcticBlast); - this.addSkills(sdk.skills.CarrionVine); - this.addSkills(sdk.skills.FeralRage); - this.addSkills(sdk.skills.Maul); - this.addSkills(sdk.skills.Fissure); - this.addSkills(sdk.skills.CycloneArmor); - this.addSkills(sdk.skills.HeartofWolverine); - this.addSkills(sdk.skills.SummonDireWolf); - this.addSkills(sdk.skills.Rabies); - this.addSkills(sdk.skills.FireClaws); - this.addSkills(sdk.skills.Twister); - this.addSkills(sdk.skills.SolarCreeper); - this.addSkills(sdk.skills.Hunger); - this.addSkills(sdk.skills.ShockWave); - this.addSkills(sdk.skills.Volcano); - this.addSkills(sdk.skills.Tornado); - this.addSkills(sdk.skills.SpiritofBarbs); - this.addSkills(sdk.skills.Grizzly); - this.addSkills(sdk.skills.Fury); - this.addSkills(sdk.skills.Armageddon); - this.addSkills(sdk.skills.Hurricane); - // assassin skills start - this.addSkills(sdk.skills.FireBlast); - this.addSkills(sdk.skills.PsychicHammer); - this.addSkills(sdk.skills.TigerStrike); - this.addSkills(sdk.skills.DragonTalon); - this.addSkills(sdk.skills.ShockWeb); - this.addSkills(sdk.skills.BladeSentinel); - this.addSkills(sdk.skills.BurstofSpeed, () => !Config.UseBoS && !me.inTown); - this.addSkills(sdk.skills.FistsofFire); - this.addSkills(sdk.skills.DragonClaw); - this.addSkills(sdk.skills.ChargedBoltSentry); - this.addSkills(sdk.skills.WakeofFire); - this.addSkills(sdk.skills.CloakofShadows); - this.addSkills(sdk.skills.CobraStrike); - this.addSkills(sdk.skills.BladeFury); - this.addSkills(sdk.skills.Fade, () => Config.UseFade); - this.addSkills(sdk.skills.ShadowWarrior); - this.addSkills(sdk.skills.ClawsofThunder); - this.addSkills(sdk.skills.DragonTail); - this.addSkills(sdk.skills.LightningSentry); - this.addSkills(sdk.skills.WakeofInferno); - this.addSkills(sdk.skills.MindBlast); - this.addSkills(sdk.skills.BladesofIce); - this.addSkills(sdk.skills.DragonFlight); - this.addSkills(sdk.skills.DeathSentry); - this.addSkills(sdk.skills.BladeShield, () => Config.UseBladeShield); - this.addSkills(sdk.skills.Venom, () => Config.UseVenom); - this.addSkills(sdk.skills.ShadowMaster); - this.addSkills(sdk.skills.PhoenixStrike); - this.addSkills(sdk.skills.WakeofDestructionSentry); - this.initialized = true; - }, - have: function (skill = 0) { - // ensure the values have been initialized - !this.initialized && this.init(); - return typeof this.all[skill] !== "undefined" && this.all[skill].have(); - }, - reset: function () { - let [min, max] = (() => { - switch (me.classid) { - case sdk.player.class.Amazon: - return [sdk.skills.MagicArrow, sdk.skills.LightningFury]; - case sdk.player.class.Sorceress: - return [sdk.skills.FireBolt, sdk.skills.ColdMastery]; - case sdk.player.class.Necromancer: - return [sdk.skills.AmplifyDamage, sdk.skills.Revive]; - case sdk.player.class.Paladin: - return [sdk.skills.Sacrifice, sdk.skills.Salvation]; - case sdk.player.class.Barbarian: - return [sdk.skills.Bash, sdk.skills.BattleCommand]; - case sdk.player.class.Druid: - return [sdk.skills.Raven, sdk.skills.Hurricane]; - case sdk.player.class.Assassin: - return [sdk.skills.FireBlast, sdk.skills.PhoenixStrike]; - default: - return [0, 0]; - } - })(); - - for (let i = min; i <= max; i++) { - if (typeof this.all[i] !== "undefined" && !this.all[i].hardpoints) { - this.all[i].checked = false; - } - } - } - }, - - // initialize our skill data - init: function () { - // reset check values - !Skill.skills.initialized ? Skill.skills.init() : Skill.skills.reset(); - // reset mana values - Skill.manaCostList = {}; - - switch (me.classid) { - case sdk.player.class.Amazon: - break; - case sdk.player.class.Sorceress: - if (Config.UseColdArmor === true) { - Precast.skills.coldArmor.best = (function () { - let coldArmor = [ - {skillId: sdk.skills.ShiverArmor, level: me.getSkill(sdk.skills.ShiverArmor, sdk.skills.subindex.SoftPoints)}, - {skillId: sdk.skills.ChillingArmor, level: me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.SoftPoints)}, - {skillId: sdk.skills.FrozenArmor, level: me.getSkill(sdk.skills.FrozenArmor, sdk.skills.subindex.SoftPoints)}, - ].filter(skill => !!skill.level && skill.level > 0).sort((a, b) => b.level - a.level).first(); - return coldArmor !== undefined ? coldArmor.skillId : false; - })(); - Precast.skills.coldArmor.duration = this.getDuration(Precast.skills.coldArmor.best); - } else { - Precast.skills.coldArmor.duration = this.getDuration(Config.UseColdArmor); - } - - break; - case sdk.player.class.Necromancer: - { - let bMax = me.getStat(sdk.stats.SkillBoneArmorMax); - bMax > 0 && (Precast.skills.boneArmor.max = bMax); - } - if (!!Config.Golem && Config.Golem !== "None") { - // todo: change Config.Golem to use skillid instead of 0, 1, 2, and 3 - } - break; - case sdk.player.class.Paladin: - // how to handle if someone manually equips a shield during game play, don't want to build entire item list if we don't need to - // maybe store gid of shield, would still require doing me.getItem(-1, 1, gid) everytime we wanted to cast but that's still less involved - // than getting every item we have and finding shield, for now keeping this. Checks during init if we have a shield or not - Precast.skills.holyShield.canUse = me.usingShield(); - - break; - case sdk.player.class.Barbarian: - Skill.canUse(sdk.skills.Shout) && (Precast.skills.shout.duration = this.getDuration(sdk.skills.Shout)); - Skill.canUse(sdk.skills.BattleOrders) && (Precast.skills.battleOrders.duration = this.getDuration(sdk.skills.BattleOrders)); - Skill.canUse(sdk.skills.BattleCommand) && (Precast.skills.battleCommand.duration = this.getDuration(sdk.skills.BattleCommand)); - - break; - case sdk.player.class.Druid: - if (!!Config.SummonAnimal && Config.SummonAnimal !== "None") { - // todo: change Config.SummonAnimal to use skillid instead of 0, 1, 2, and 3 - } - if (!!Config.SummonVine && Config.SummonVine !== "None") { - // todo: change Config.SummonVine to use skillid instead of 0, 1, 2, and 3 - } - if (!!Config.SummonSpirit && Config.SummonSpirit !== "None") { - // todo: change Config.SummonSpirit to use skillid instead of 0, 1, 2, and 3 - } - break; - case sdk.player.class.Assassin: - if (!!Config.SummonShadow) { - // todo: change Config.SummonShadow to use skillid instead of 0, 1, 2, and 3 - } - break; - } - }, - - canUse: function (skillId = -1) { - try { - if (skillId === -1) return false; - if (skillId >= sdk.skills.Attack && skillId <= sdk.skills.LeftHandSwing) return true; - let valid = Skill.skills.have(skillId); - - return valid; - } catch (e) { - return false; - } - }, - - getDuration: function (skillId = -1) { - switch (skillId) { - case sdk.skills.Decoy: - return ((10 + me.getSkill(sdk.skills.Decoy, sdk.skills.subindex.SoftPoints) * 5) * 1000); - case sdk.skills.FrozenArmor: - return (((12 * me.getSkill(sdk.skills.FrozenArmor, sdk.skills.subindex.SoftPoints) + 108) + ((me.getSkill(sdk.skills.ShiverArmor, sdk.skills.subindex.HardPoints) + me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.HardPoints)) * 10)) * 1000); - case sdk.skills.ShiverArmor: - return (((12 * me.getSkill(sdk.skills.ShiverArmor, sdk.skills.subindex.SoftPoints) + 108) + ((me.getSkill(sdk.skills.FrozenArmor, sdk.skills.subindex.HardPoints) + me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.HardPoints)) * 10)) * 1000); - case sdk.skills.ChillingArmor: - return (((6 * me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.SoftPoints) + 138) + ((me.getSkill(sdk.skills.FrozenArmor, sdk.skills.subindex.HardPoints) + me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.HardPoints)) * 10)) * 1000); - case sdk.skills.EnergyShield: - return (84 + (60 * me.getSkill(sdk.skills.EnergyShield, sdk.skills.subindex.SoftPoints)) * 1000); - case sdk.skills.ThunderStorm: - return (24 + (8 * me.getSkill(sdk.skills.ThunderStorm, sdk.skills.subindex.SoftPoints))) * 1000; - case sdk.skills.Shout: - return (((10 + me.getSkill(sdk.skills.Shout, sdk.skills.subindex.SoftPoints) * 10) + ((me.getSkill(sdk.skills.BattleOrders, sdk.skills.subindex.HardPoints) + me.getSkill(sdk.skills.BattleCommand, sdk.skills.subindex.HardPoints)) * 5)) * 1000); - case sdk.skills.BattleOrders: - return (((20 + me.getSkill(sdk.skills.BattleOrders, sdk.skills.subindex.SoftPoints) * 10) + ((me.getSkill(sdk.skills.Shout, sdk.skills.subindex.HardPoints) + me.getSkill(sdk.skills.BattleCommand, sdk.skills.subindex.HardPoints)) * 5)) * 1000); - case sdk.skills.BattleCommand: - return (((10 * me.getSkill(sdk.skills.BattleCommand, sdk.skills.subindex.SoftPoints) - 5) + ((me.getSkill(sdk.skills.Shout, sdk.skills.subindex.HardPoints) + me.getSkill(sdk.skills.BattleOrders, sdk.skills.subindex.HardPoints)) * 5)) * 1000); - case sdk.skills.HolyShield: - return (5 + (25 * me.getSkill(sdk.skills.HolyShield, sdk.skills.subindex.SoftPoints)) * 1000); - case sdk.skills.Hurricane: - return (10 + (2 * me.getSkill(sdk.skills.CycloneArmor, sdk.skills.subindex.HardPoints)) * 1000); - case sdk.skills.Werewolf: - case sdk.skills.Werebear: - return (40 + (20 * me.getSkill(sdk.skills.Lycanthropy, sdk.skills.subindex.SoftPoints) + 20) * 1000); - case sdk.skills.BurstofSpeed: - return (108 + (12 * me.getSkill(sdk.skills.BurstofSpeed, sdk.skills.subindex.SoftPoints)) * 1000); - case sdk.skills.Fade: - return (108 + (12 * me.getSkill(sdk.skills.Fade, sdk.skills.subindex.SoftPoints)) * 1000); - case sdk.skills.Venom: - return (116 + (4 * me.getSkill(sdk.skills.Venom, sdk.skills.subindex.SoftPoints)) * 1000); - case sdk.skills.BladeShield: - return (15 + (5 * me.getSkill(sdk.skills.BladeShield, sdk.skills.subindex.SoftPoints)) * 1000); - default: - return 0; - } - }, - - getMaxSummonCount: function (skillId) { - let skillNum = 0; - - switch (skillId) { - case sdk.skills.Raven: - return Math.min(me.getSkill(skillId, sdk.skills.subindex.SoftPoints), 5); - case sdk.skills.SummonSpiritWolf: - return Math.min(me.getSkill(skillId, sdk.skills.subindex.SoftPoints), 5); - case sdk.skills.SummonDireWolf: - return Math.min(me.getSkill(skillId, sdk.skills.subindex.SoftPoints), 3); - case sdk.skills.RaiseSkeleton: - case sdk.skills.RaiseSkeletalMage: - skillNum = me.getSkill(skillId, sdk.skills.subindex.SoftPoints); - return skillNum < 4 ? skillNum : (Math.floor(skillNum / 3) + 2); - case sdk.skills.Revive: - return me.getSkill(sdk.skills.Revive, sdk.skills.subindex.SoftPoints); - case sdk.skills.ShadowWarrior: - case sdk.skills.ShadowMaster: - case sdk.skills.PoisonCreeper: - case sdk.skills.CarrionVine: - case sdk.skills.SolarCreeper: - case sdk.skills.OakSage: - case sdk.skills.HeartofWolverine: - case sdk.skills.SpiritofBarbs: - case sdk.skills.SummonGrizzly: - case sdk.skills.ClayGolem: - case sdk.skills.BloodGolem: - case sdk.skills.FireGolem: - case sdk.skills.Valkyrie: - return 1; - } - - return 0; - }, - - getRange: function (skillId) { - switch (skillId) { - case sdk.skills.Attack: - return Attack.usingBow() ? 20 : 3; - case sdk.skills.Kick: - case sdk.skills.LeftHandSwing: - case sdk.skills.Jab: - case sdk.skills.PowerStrike: - case sdk.skills.ChargedStrike: - case sdk.skills.LightningStrike: - case sdk.skills.Impale: - case sdk.skills.Fend: - case sdk.skills.Blaze: - case sdk.skills.PoisonDagger: - case sdk.skills.Sacrifice: - case sdk.skills.Smite: - case sdk.skills.Zeal: - case sdk.skills.Vengeance: - case sdk.skills.Conversion: - case sdk.skills.BlessedHammer: - case sdk.skills.FindPotion: - case sdk.skills.FindItem: - case sdk.skills.GrimWard: - case sdk.skills.Bash: - case sdk.skills.DoubleSwing: - case sdk.skills.Stun: - case sdk.skills.Concentrate: - case sdk.skills.Frenzy: - case sdk.skills.Berserk: - case sdk.skills.FeralRage: - case sdk.skills.Maul: - case sdk.skills.Rabies: - case sdk.skills.FireClaws: - case sdk.skills.Hunger: - case sdk.skills.Fury: - case sdk.skills.DragonTalon: - case sdk.skills.DragonClaw: - case sdk.skills.DragonTail: - return 3; - case sdk.skills.BattleCry: - case sdk.skills.WarCry: - return 4; - case sdk.skills.FrostNova: - case sdk.skills.Twister: - case sdk.skills.Tornado: - case sdk.skills.Summoner: - return 5; - case sdk.skills.ChargedBolt: - return 6; - case sdk.skills.Nova: - case sdk.skills.Whirlwind: - return 7; - case sdk.skills.PoisonNova: - return 8; - case sdk.skills.Armageddon: - return 9; - case sdk.skills.PoisonJavelin: - case sdk.skills.PlagueJavelin: - case sdk.skills.HolyBolt: - case sdk.skills.Charge: - case sdk.skills.Howl: - case sdk.skills.Firestorm: - case sdk.skills.MoltenBoulder: - case sdk.skills.ShockWave: - return 10; - case sdk.skills.InnerSight: - case sdk.skills.SlowMissiles: - return 13; - case sdk.skills.LightningFury: - case sdk.skills.FrozenOrb: - case sdk.skills.Teeth: - case sdk.skills.Fissure: - case sdk.skills.Volcano: - case sdk.skills.FireBlast: - case sdk.skills.ShockWeb: - case sdk.skills.BladeSentinel: - case sdk.skills.BladeFury: - return 15; - case sdk.skills.FireArrow: - case sdk.skills.MultipleShot: - case sdk.skills.ExplodingArrow: - case sdk.skills.GuidedArrow: - case sdk.skills.ImmolationArrow: - case sdk.skills.FreezingArrow: - case sdk.skills.IceBolt: - case sdk.skills.IceBlast: - case sdk.skills.FireBolt: - case sdk.skills.Revive: - case sdk.skills.FistoftheHeavens: - case sdk.skills.DoubleThrow: - case sdk.skills.PsychicHammer: - case sdk.skills.DragonFlight: - return 20; - case sdk.skills.LowerResist: - return 50; - // Variable range - case sdk.skills.StaticField: - return Math.floor((me.getSkill(sdk.skills.StaticField, sdk.skills.subindex.SoftPoints) + 4) * 2 / 3); - case sdk.skills.Leap: - { - let skLvl = me.getSkill(sdk.skills.Leap, sdk.skills.subindex.SoftPoints); - return Math.floor(Math.min(4 + (26 * ((110 * skLvl / (skLvl + 6)) / 100)), 30) * (2 / 3)); - } - case sdk.skills.ArcticBlast: - { - let skLvl = me.getSkill(sdk.skills.ArcticBlast, sdk.skills.subindex.SoftPoints); - let range = Math.floor(((33 + (2 * skLvl)) / 4) * (2 / 3)); - // Druid using this on physical immunes needs the monsters to be within range of hurricane - range > 6 && Config.AttackSkill[5] === sdk.skills.ArcticBlast && (range = 6); - - return range; - } - case sdk.skills.Lightning: - case sdk.skills.BoneSpear: - case sdk.skills.BoneSpirit: - return !!this.usePvpRange ? 35 : 15; - case sdk.skills.FireBall: - case sdk.skills.FireWall: - case sdk.skills.ChainLightning: - case sdk.skills.Meteor: - case sdk.skills.Blizzard: - case sdk.skills.MindBlast: - return !!this.usePvpRange ? 35 : 20; - } - - // Every other skill - return !!this.usePvpRange ? 30 : 20; - }, - - needFloor: [ - sdk.skills.Blizzard, sdk.skills.Meteor, sdk.skills.Fissure, sdk.skills.Volcano, sdk.skills.ShockWeb, sdk.skills.LeapAttack, sdk.skills.Hydra - ], - - missileSkills: [ - sdk.skills.MagicArrow, sdk.skills.FireArrow, sdk.skills.ColdArrow, sdk.skills.MultipleShot, sdk.skills.PoisonJavelin, sdk.skills.ExplodingArrow, - sdk.skills.LightningBolt, sdk.skills.IceArrow, sdk.skills.GuidedArrow, sdk.skills.PlagueJavelin, sdk.skills.Strafe, sdk.skills.ImmolationArrow, - sdk.skills.FreezingArrow, sdk.skills.LightningFury, sdk.skills.ChargedBolt, sdk.skills.IceBolt, sdk.skills.FireBolt, sdk.skills.Inferno, - sdk.skills.IceBlast, sdk.skills.FireBall, sdk.skills.Lightning, sdk.skills.ChainLightning, sdk.skills.GlacialSpike, sdk.skills.FrozenOrb, - sdk.skills.Teeth, sdk.skills.BoneSpear, sdk.skills.BoneSpirit, sdk.skills.HolyBolt, sdk.skills.FistoftheHeavens, sdk.skills.DoubleThrow, - sdk.skills.Firestorm, sdk.skills.MoltenBoulder, sdk.skills.ArcticBlast, sdk.skills.Twister, sdk.skills.Tornado, sdk.skills.FireBlast - ], - - getHand: function (skillId) { - switch (skillId) { - case sdk.skills.MagicArrow: - case sdk.skills.FireArrow: - case sdk.skills.ColdArrow: - case sdk.skills.MultipleShot: - case sdk.skills.PoisonJavelin: - case sdk.skills.ExplodingArrow: - case sdk.skills.Impale: - case sdk.skills.LightningBolt: - case sdk.skills.IceArrow: - case sdk.skills.GuidedArrow: - case sdk.skills.PlagueJavelin: - case sdk.skills.Strafe: - case sdk.skills.ImmolationArrow: - case sdk.skills.Fend: - case sdk.skills.FreezingArrow: - case sdk.skills.LightningFury: - case sdk.skills.FireBolt: - case sdk.skills.ChargedBolt: - case sdk.skills.IceBolt: - case sdk.skills.Inferno: - case sdk.skills.IceBlast: - case sdk.skills.FireBall: - case sdk.skills.Lightning: - case sdk.skills.ChainLightning: - case sdk.skills.GlacialSpike: - case sdk.skills.FrozenOrb: - case sdk.skills.Teeth: - case sdk.skills.PoisonDagger: - case sdk.skills.BoneSpear: - case sdk.skills.BoneSpirit: - case sdk.skills.HolyBolt: - case sdk.skills.Charge: - case sdk.skills.BlessedHammer: - case sdk.skills.FistoftheHeavens: - case sdk.skills.Leap: - case sdk.skills.DoubleThrow: - case sdk.skills.LeapAttack: - case sdk.skills.Whirlwind: - case sdk.skills.Firestorm: - case sdk.skills.MoltenBoulder: - case sdk.skills.ArcticBlast: - case sdk.skills.Twister: - case sdk.skills.ShockWave: - case sdk.skills.Tornado: - case sdk.skills.FireBlast: - case sdk.skills.TigerStrike: - case sdk.skills.ShockWeb: - case sdk.skills.BladeSentinel: - case sdk.skills.FistsofFire: - case sdk.skills.CobraStrike: - case sdk.skills.BladeFury: - case sdk.skills.ClawsofThunder: - case sdk.skills.BladesofIce: - case sdk.skills.DragonFlight: - return sdk.skills.hand.Left; - case sdk.skills.Attack: - case sdk.skills.Jab: - case sdk.skills.PowerStrike: - case sdk.skills.ChargedStrike: - case sdk.skills.LightningStrike: - case sdk.skills.Sacrifice: - case sdk.skills.Smite: - case sdk.skills.Zeal: - case sdk.skills.Vengeance: - case sdk.skills.Conversion: - case sdk.skills.Bash: - case sdk.skills.DoubleSwing: - case sdk.skills.Stun: - case sdk.skills.Concentrate: - case sdk.skills.Frenzy: - case sdk.skills.Berserk: - case sdk.skills.FeralRage: - case sdk.skills.Maul: - case sdk.skills.Rabies: - case sdk.skills.FireClaws: - case sdk.skills.Hunger: - case sdk.skills.Fury: - case sdk.skills.DragonTalon: - case sdk.skills.DragonClaw: - case sdk.skills.DragonTail: - return sdk.skills.hand.LeftNoShift; // Shift bypass - } - - // Every other skill - return sdk.skills.hand.Right; - }, - - charges: [], - - // Cast a skill on self, Unit or coords - cast: function (skillId, hand, x, y, item) { - switch (true) { - case me.inTown && !this.townSkill(skillId): - case !item && (this.getManaCost(skillId) > me.mp || !this.canUse(skillId)): - case !this.wereFormCheck(skillId): - return false; - case skillId === undefined: - throw new Error("Unit.cast: Must supply a skill ID"); - } - - if (skillId === sdk.skills.Telekinesis && typeof x === "object" && Packet.telekinesis(x)) { - delay(250); - return true; - } - - hand === undefined && (hand = this.getHand(skillId)); - x === undefined && (x = me.x); - y === undefined && (y = me.y); - - // Check mana cost, charged skills don't use mana - if (!item && this.getManaCost(skillId) > me.mp) { - // Maybe delay on ALL skills that we don't have enough mana for? - if (Config.AttackSkill.concat([sdk.skills.StaticField, sdk.skills.Teleport]).concat(Config.LowManaSkill).includes(skillId)) { - delay(300); - } - - return false; - } - - if (skillId === sdk.skills.Teleport && typeof x === "number" && Packet.teleport(x, y)) { - delay(250); - return true; - } - - if (!this.setSkill(skillId, hand, item)) return false; - - if (Config.PacketCasting > 1) { - switch (typeof x) { - case "number": - Packet.castSkill(hand, x, y); - delay(250); - - break; - case "object": - Packet.unitCast(hand, x); - delay(250); - - break; - } - } else { - let [clickType, shift] = (() => { - switch (hand) { - case sdk.skills.hand.Left: // Left hand + Shift - return [sdk.clicktypes.click.map.LeftDown, sdk.clicktypes.shift.Shift]; - case sdk.skills.hand.LeftNoShift: // Left hand + No Shift - return [sdk.clicktypes.click.map.LeftDown, sdk.clicktypes.shift.NoShift]; - case sdk.skills.hand.RightShift: // Right hand + Shift - return [sdk.clicktypes.click.map.RightDown, sdk.clicktypes.shift.Shift]; - case sdk.skills.hand.Right: // Right hand + No Shift - default: - return [sdk.clicktypes.click.map.RightDown, sdk.clicktypes.shift.NoShift]; - } - })(); - - MainLoop: - for (let n = 0; n < 3; n += 1) { - typeof x === "object" ? clickMap(clickType, shift, x) : clickMap(clickType, shift, x, y); - delay(20); - typeof x === "object" ? clickMap(clickType + 2, shift, x) : clickMap(clickType + 2, shift, x, y); - - for (let i = 0; i < 8; i += 1) { - if (me.attacking) { - break MainLoop; - } - - delay(20); - } - } - - while (me.attacking) { - delay(10); - } - } - - // account for lag, state 121 doesn't kick in immediately - if (this.isTimed(skillId)) { - for (let i = 0; i < 10; i += 1) { - if ([sdk.player.mode.GettingHit, sdk.player.mode.Blocking].includes(me.mode) || me.skillDelay) { - break; - } - - delay(10); - } - } - - return true; - }, - - // Put a skill on desired slot - setSkill: function (skillId, hand, item) { - // Check if the skill is already set - if (me.getSkill(hand === sdk.skills.hand.Right ? sdk.skills.get.RightId : sdk.skills.get.LeftId) === skillId) return true; - if (!item && !Skill.canUse(skillId)) return false; - - // Charged skills must be cast from right hand - if (hand === undefined || hand === sdk.skills.hand.RightShift || item) { - item && hand !== sdk.skills.hand.Right && console.warn("[ÿc9Warningÿc0] charged skills must be cast from right hand"); - hand = sdk.skills.hand.Right; - } - - return (me.setSkill(skillId, hand, item)); - }, - - // Timed skills - isTimed: function (skillId) { - return [ - sdk.skills.PoisonJavelin, sdk.skills.PlagueJavelin, sdk.skills.ImmolationArrow, sdk.skills.FireWall, sdk.skills.Meteor, sdk.skills.Blizzard, - sdk.skills.Hydra, sdk.skills.FrozenOrb, sdk.skills.FistoftheHeavens, sdk.skills.Firestorm, sdk.skills.Werewolf, sdk.skills.Werebear, sdk.skills.MoltenBoulder, - sdk.skills.Fissure, sdk.skills.Volcano, sdk.skills.Grizzly, sdk.skills.Armageddon, sdk.skills.Hurricane, sdk.skills.ShockWeb, sdk.skills.ShadowWarrior, - sdk.skills.DragonFlight, sdk.skills.BladeShield, sdk.skills.ShadowMaster - ].includes(skillId); - }, - - // Wereform skill check - wereFormCheck: function (skillId) { - // we don't even have the skills to transform or we aren't transformed - add handler for wereform given by an item that is on switch - if (!Skill.canUse(sdk.skills.Werewolf) && !Skill.canUse(sdk.skills.Werebear)) return true; - if (!me.getState(sdk.states.Wearwolf) && !me.getState(sdk.states.Wearbear)) return true; - - // Can be cast by both - if ([sdk.skills.Attack, sdk.skills.Kick, sdk.skills.Raven, sdk.skills.PoisonCreeper, sdk.skills.OakSage, sdk.skills.SpiritWolf, sdk.skills.CarrionVine, - sdk.skills.HeartofWolverine, sdk.skills.SummonDireWolf, sdk.skills.FireClaws, sdk.skills.SolarCreeper, sdk.skills.Hunger, sdk.skills.SpiritofBarbs, sdk.skills.SummonGrizzly, sdk.skills.Armageddon].includes(skillId)) { - return true; - } - - // Can be cast by werewolf only - if (me.getState(sdk.states.Wearwolf) && [sdk.skills.Werewolf, sdk.skills.FeralRage, sdk.skills.Rabies, sdk.skills.Fury].includes(skillId)) return true; - // Can be cast by werebear only - if (me.getState(sdk.states.Wearbear) && [sdk.skills.Werebear, sdk.skills.Maul, sdk.skills.ShockWave].includes(skillId)) return true; - - return false; - }, - - // Skills that cn be cast in town - townSkill: function (skillId = -1) { - return [ - sdk.skills.Valkyrie, sdk.skills.FrozenArmor, sdk.skills.Telekinesis, sdk.skills.ShiverArmor, sdk.skills.Enchant, sdk.skills.ThunderStorm, sdk.skills.EnergyShield, sdk.skills.ChillingArmor, - sdk.skills.BoneArmor, sdk.skills.ClayGolem, sdk.skills.BloodGolem, sdk.skills.FireGolem, sdk.skills.HolyShield, sdk.skills.Raven, sdk.skills.PoisonCreeper, sdk.skills.Werewolf, sdk.skills.Werebear, - sdk.skills.OakSage, sdk.skills.SpiritWolf, sdk.skills.CarrionVine, sdk.skills.CycloneArmor, sdk.skills.HeartofWolverine, sdk.skills.SummonDireWolf, sdk.skills.SolarCreeper, - sdk.skills.SpiritofBarbs, sdk.skills.SummonGrizzly, sdk.skills.BurstofSpeed, sdk.skills.Fade, sdk.skills.ShadowWarrior, sdk.skills.BladeShield, sdk.skills.Venom, sdk.skills.ShadowMaster - ].includes(skillId); - }, - - manaCostList: {}, - - // Get mana cost of the skill (mBot) - getManaCost: function (skillId) { - if (skillId < sdk.skills.MagicArrow) return 0; - if (this.manaCostList.hasOwnProperty(skillId)) return this.manaCostList[skillId]; - - let skillLvl = me.getSkill(skillId, sdk.skills.subindex.SoftPoints); - let effectiveShift = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]; - let lvlmana = getBaseStat("skills", skillId, "lvlmana") === 65535 ? -1 : getBaseStat("skills", skillId, "lvlmana"); // Correction for skills that need less mana with levels (kolton) - let ret = Math.max((getBaseStat("skills", skillId, "mana") + lvlmana * (skillLvl - 1)) * (effectiveShift[getBaseStat("skills", skillId, "manashift")] / 256), getBaseStat("skills", skillId, "minmana")); - - if (!this.manaCostList.hasOwnProperty(skillId)) { - this.manaCostList[skillId] = ret; - } - - return ret; - }, - - useTK: function (unit = undefined) { - try { - if (!unit || !Skill.canUse(sdk.skills.Telekinesis) - || typeof unit !== "object" || unit.type !== sdk.unittype.Object - || unit.name.toLowerCase() === "dummy" - || (unit.name.toLowerCase() === "portal" && !me.inTown && unit.classid !== sdk.objects.ArcaneSanctuaryPortal) - || [sdk.objects.RedPortalToAct4, sdk.objects.WorldstonePortal, sdk.objects.RedPortal, sdk.objects.RedPortalToAct5].includes(unit.classid)) { - return false; - } - - return me.inTown || (me.mpPercent > 25); - } catch (e) { - return false; - } - } -}; - -Object.defineProperties(Skill, { - haveTK: { - get: function () { - return Skill.canUse(sdk.skills.Telekinesis); - }, - }, -}); - -const Misc = { - // Click something - click: function (button, shift, x, y) { - if (arguments.length < 2) throw new Error("Misc.click: Needs at least 2 arguments."); - - while (!me.gameReady) { - delay(100); - } - - switch (arguments.length) { - case 2: - me.blockMouse = true; - clickMap(button, shift, me.x, me.y); - delay(20); - clickMap(button + 2, shift, me.x, me.y); - me.blockMouse = false; - - break; - case 3: - if (typeof (x) !== "object") throw new Error("Misc.click: Third arg must be a Unit."); - - me.blockMouse = true; - clickMap(button, shift, x); - delay(20); - clickMap(button + 2, shift, x); - me.blockMouse = false; - - break; - case 4: - me.blockMouse = true; - clickMap(button, shift, x, y); - delay(20); - clickMap(button + 2, shift, x, y); - me.blockMouse = false; - - break; - } - - return true; - }, - - // Check if a player is in your party - inMyParty: function (name) { - if (me.name === name) return true; - - while (!me.gameReady) { - delay(100); - } - - let player, myPartyId; - - try { - player = getParty(); - if (!player) return false; - - myPartyId = player.partyid; - player = getParty(name); // May throw an error - - if (player && player.partyid !== sdk.party.NoParty && player.partyid === myPartyId) { - return true; - } - } catch (e) { - player = getParty(); - - if (player) { - myPartyId = player.partyid; - - while (player.getNext()) { - if (player.partyid !== sdk.party.NoParty && player.partyid === myPartyId) { - return true; - } - } - } - } - - return false; - }, - - // Find a player - findPlayer: function (name) { - let player = getParty(); - - if (player) { - do { - if (player.name !== me.name && player.name === name) { - return player; - } - } while (player.getNext()); - } - - return false; - }, - - // Get player unit - getPlayerUnit: function (name) { - let player = Game.getPlayer(name); - - if (player) { - do { - if (!player.dead) { - return player; - } - } while (player.getNext()); - } - - return false; - }, - - // Get the player act, accepts party unit or name - getPlayerAct: function (player) { - if (!player) return false; - - let unit = (typeof player === "object" ? player : this.findPlayer(player)); - - if (!unit) { - return false; - } else { - return sdk.areas.actOf(unit.area); - } - }, - - // Get number of players within getUnit distance - getNearbyPlayerCount: function () { - let count = 0; - let player = Game.getPlayer(); - - if (player) { - do { - if (player.name !== me.name && !player.dead) { - count += 1; - } - } while (player.getNext()); - } - - return count; - }, - - // Get total number of players in game - getPlayerCount: function () { - let count = 0; - let party = getParty(); - - if (party) { - do { - count += 1; - } while (party.getNext()); - } - - return count; - }, - - // Get total number of players in game and in my party - getPartyCount: function () { - let count = 0; - let party = getParty(); - - if (party) { - let myPartyId = party.partyid; - - do { - if (party.partyid !== sdk.party.NoParty && party.partyid === myPartyId && party.name !== me.name) { - print(party.name); - count += 1; - } - } while (party.getNext()); - } - - return count; - }, - - // check if any member of our party meets a certain level req - checkPartyLevel: function (levelCheck = 1, exclude = []) { - !Array.isArray(exclude) && (exclude = [exclude]); - let party = getParty(); - - if (party) { - let myPartyId = party.partyid; - - do { - if (party.partyid !== sdk.party.NoParty && party.partyid === myPartyId && party.name !== me.name && !exclude.includes(party.name)) { - if (party.level >= levelCheck) { - return true; - } - } - } while (party.getNext()); - } - - return false; - }, - - getPlayerArea: function (player) { - if (!player) return false; - - let unit = (typeof player === "object" ? player : this.findPlayer(player)); - - return !!unit ? unit.area : 0; - }, - - // autoleader by Ethic - refactored by theBGuy - autoLeaderDetect: function (givenSettings = {}) { - const settings = Object.assign({}, { - destination: -1, - quitIf: false, - timeout: Infinity - }, givenSettings); - - let leader; - let startTick = getTickCount(); - let check = typeof settings.quitIf === "function"; - do { - let solofail = 0; - let suspect = getParty(); // get party object (players in game) - - do { - // player isn't alone - suspect.name !== me.name && (solofail += 1); - - if (check && settings.quitIf(suspect.area)) return false; - - // first player not hostile found in destination area... - if (suspect.area === settings.destination && !getPlayerFlag(me.gid, suspect.gid, 8)) { - leader = suspect.name; // ... is our leader - console.log("ÿc4Autodetected " + leader); - - return leader; - } - } while (suspect.getNext()); - - // empty game, nothing left to do. Or we exceeded our wait time - if (solofail === 0 || (getTickCount() - startTick > settings.timeout)) { - return false; - } - - delay(500); - } while (!leader); // repeat until leader is found (or until game is empty) - - return false; - }, - - // Open a chest Unit (takes chestID or unit) - openChest: function (unit) { - typeof unit === "number" && (unit = Game.getObject(unit)); - - // Skip invalid/open and Countess chests - if (!unit || unit.x === 12526 || unit.x === 12565 || unit.mode) return false; - // locked chest, no keys - if (!me.assassin && unit.islocked && !me.findItem(sdk.items.Key, sdk.items.mode.inStorage, sdk.storage.Inventory)) return false; - - let specialChest = sdk.quest.chests.includes(unit.classid); - - for (let i = 0; i < 7; i++) { - // don't use tk if we are right next to it - let useTK = (unit.distance > 5 && Skill.useTK(unit) && i < 3); - if (useTK) { - unit.distance > 13 && Attack.getIntoPosition(unit, 13, sdk.collision.WallOrRanged); - if (!Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit)) { - console.debug("Failed to tk: attempt: " + i); - continue; - } - } else { - [(unit.x + 1), (unit.y + 2)].distance > 5 && Pather.moveTo(unit.x + 1, unit.y + 2, 3); - (specialChest || i > 2) ? Misc.click(0, 0, unit) : Packet.entityInteract(unit); - } - - if (Misc.poll(() => unit.mode, 1000, 50)) { - return true; - } else { - Packet.flash(me.gid); - } - } - - // Click to stop walking in case we got stuck - !me.idle && Misc.click(0, 0, me.x, me.y); - - return false; - }, - - // Open all chests that have preset units in an area - openChestsInArea: function (area, chestIds = []) { - !area && (area = me.area); - area !== me.area && Pather.journeyTo(area); - - let presetUnits = Game.getPresetObjects(area); - if (!presetUnits) return false; - - if (!chestIds.length) { - chestIds = [ - 5, 6, 87, 104, 105, 106, 107, 143, 140, 141, 144, 146, 147, 148, 176, 177, 181, 183, 198, 240, 241, - 242, 243, 329, 330, 331, 332, 333, 334, 335, 336, 354, 355, 356, 371, 387, 389, 390, 391, 397, 405, - 406, 407, 413, 420, 424, 425, 430, 431, 432, 433, 454, 455, 501, 502, 504, 505, 580, 581 - ]; - } - - let coords = []; - - while (presetUnits.length > 0) { - if (chestIds.includes(presetUnits[0].id)) { - coords.push({ - x: presetUnits[0].roomx * 5 + presetUnits[0].x, - y: presetUnits[0].roomy * 5 + presetUnits[0].y - }); - } - - presetUnits.shift(); - } - - while (coords.length) { - coords.sort(Sort.units); - Pather.moveToUnit(coords[0], 1, 2); - this.openChests(20); - - for (let i = 0; i < coords.length; i += 1) { - if (getDistance(coords[i].x, coords[i].y, coords[0].x, coords[0].y) < 20) { - coords.shift(); - } - } - } - - return true; - }, - - openChests: function (range = 15) { - if (!Config.OpenChests.Enabled) return true; - - let unitList = []; - let containers = []; - - // Testing all container code - if (Config.OpenChests.Types.some((el) => el.toLowerCase() === "all")) { - containers = [ - "chest", "loose rock", "hidden stash", "loose boulder", "corpseonstick", "casket", "armorstand", "weaponrack", "barrel", "holeanim", "tomb2", - "tomb3", "roguecorpse", "ratnest", "corpse", "goo pile", "largeurn", "urn", "chest3", "jug", "skeleton", "guardcorpse", "sarcophagus", "object2", - "cocoon", "basket", "stash", "hollow log", "hungskeleton", "pillar", "skullpile", "skull pile", "jar3", "jar2", "jar1", "bonechest", "woodchestl", - "woodchestr", "barrel wilderness", "burialchestr", "burialchestl", "explodingchest", "chestl", "chestr", "groundtomb", "icecavejar1", "icecavejar2", - "icecavejar3", "icecavejar4", "deadperson", "deadperson2", "evilurn", "tomb1l", "tomb3l", "groundtombl" - ]; - } else { - containers = Config.OpenChests.Types; - } - - let unit = Game.getObject(); - - if (unit) { - do { - if (unit.name && unit.mode === sdk.objects.mode.Inactive && getDistance(me.x, me.y, unit.x, unit.y) <= range && containers.includes(unit.name.toLowerCase())) { - unitList.push(copyUnit(unit)); - } - } while (unit.getNext()); - } - - while (unitList.length > 0) { - unitList.sort(Sort.units); - unit = unitList.shift(); - - if (unit && (Pather.useTeleport() || !checkCollision(me, unit, sdk.collision.WallOrRanged)) && this.openChest(unit)) { - Pickit.pickItems(); - } - } - - return true; - }, - - shrineStates: false, - - scanShrines: function (range, ignore = []) { - if (!Config.ScanShrines.length) return false; - - !range && (range = Pather.useTeleport() ? 25 : 15); - !Array.isArray(ignore) && (ignore = [ignore]); - - let shrineList = []; - - // Initiate shrine states - if (!this.shrineStates) { - this.shrineStates = []; - - for (let i = 0; i < Config.ScanShrines.length; i += 1) { - switch (Config.ScanShrines[i]) { - case sdk.shrines.None: - case sdk.shrines.Refilling: - case sdk.shrines.Health: - case sdk.shrines.Mana: - case sdk.shrines.HealthExchange: // (doesn't exist) - case sdk.shrines.ManaExchange: // (doesn't exist) - case sdk.shrines.Enirhs: // (doesn't exist) - case sdk.shrines.Portal: - case sdk.shrines.Gem: - case sdk.shrines.Fire: - case sdk.shrines.Monster: - case sdk.shrines.Exploding: - case sdk.shrines.Poison: - this.shrineStates[i] = 0; // no state - - break; - case sdk.shrines.Armor: - case sdk.shrines.Combat: - case sdk.shrines.ResistFire: - case sdk.shrines.ResistCold: - case sdk.shrines.ResistLightning: - case sdk.shrines.ResistPoison: - case sdk.shrines.Skill: - case sdk.shrines.ManaRecharge: - case sdk.shrines.Stamina: - case sdk.shrines.Experience: - // Both states and shrines are arranged in same order with armor shrine starting at 128 - this.shrineStates[i] = Config.ScanShrines[i] + 122; - - break; - } - } - } - - let shrine = Game.getObject("shrine"); - - if (shrine) { - let index = -1; - // Build a list of nearby shrines - do { - if (shrine.mode === sdk.objects.mode.Inactive && !ignore.includes(shrine.objtype) && getDistance(me.x, me.y, shrine.x, shrine.y) <= range) { - shrineList.push(copyUnit(shrine)); - } - } while (shrine.getNext()); - - // Check if we have a shrine state, store its index if yes - for (let i = 0; i < this.shrineStates.length; i += 1) { - if (me.getState(this.shrineStates[i])) { - index = i; - - break; - } - } - - for (let i = 0; i < Config.ScanShrines.length; i += 1) { - for (let j = 0; j < shrineList.length; j += 1) { - // Get the shrine if we have no active state or to refresh current state or if the shrine has no state - // Don't override shrine state with a lesser priority shrine - // todo - check to make sure we can actually get the shrine for ones without states - // can't grab a health shrine if we are in perfect health, can't grab mana shrine if our mana is maxed - if (index === -1 || i <= index || this.shrineStates[i] === 0) { - if (shrineList[j].objtype === Config.ScanShrines[i] && (Pather.useTeleport() || !checkCollision(me, shrineList[j], sdk.collision.WallOrRanged))) { - this.getShrine(shrineList[j]); - - // Gem shrine - pick gem - if (Config.ScanShrines[i] === sdk.shrines.Gem) { - Pickit.pickItems(); - } - } - } - } - } - } - - return true; - }, - - // Use a shrine Unit - getShrine: function (unit) { - if (unit.mode === sdk.objects.mode.Active) return false; - - for (let i = 0; i < 3; i++) { - if (Skill.useTK(unit) && i < 2) { - unit.distance > 21 && Pather.moveNearUnit(unit, 20); - !Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit) && Attack.getIntoPosition(unit, 20, sdk.collision.WallOrRanged); - } else { - if (getDistance(me, unit) < 4 || Pather.moveToUnit(unit, 3, 0)) { - Misc.click(0, 0, unit); - } - } - - if (Misc.poll(() => unit.mode, 1000, 40)) { - return true; - } - } - - return false; - }, - - // Check all shrines in area and get the first one of specified type - getShrinesInArea: function (area, type, use) { - let shrineLocs = []; - let shrineIds = [2, 81, 83]; - let unit = Game.getPresetObjects(area); - let result = false; - - if (unit) { - for (let i = 0; i < unit.length; i += 1) { - if (shrineIds.includes(unit[i].id)) { - shrineLocs.push([unit[i].roomx * 5 + unit[i].x, unit[i].roomy * 5 + unit[i].y]); - } - } - } - - try { - NodeAction.shrinesToIgnore.push(type); - - while (shrineLocs.length > 0) { - shrineLocs.sort(Sort.points); - let coords = shrineLocs.shift(); - - Skill.haveTK ? Pather.moveNear(coords[0], coords[1], 20) : Pather.moveTo(coords[0], coords[1], 2); - - let shrine = Game.getObject("shrine"); - - if (shrine) { - do { - if (shrine.objtype === type && shrine.mode === sdk.objects.mode.Inactive) { - (!Skill.haveTK || !use) && Pather.moveTo(shrine.x - 2, shrine.y - 2); - - if (!use || this.getShrine(shrine)) { - result = true; - return true; - } - } - } while (shrine.getNext()); - } - } - } finally { - NodeAction.shrinesToIgnore.remove(type); - } - - return result; - }, - - getItemDesc: function (unit, logILvl = true) { - let stringColor = ""; - let desc = unit.description; - - if (!desc) return ""; - desc = desc.split("\n"); - - // Lines are normally in reverse. Add color tags if needed and reverse order. - for (let i = 0; i < desc.length; i += 1) { - // Remove sell value - if (desc[i].includes(getLocaleString(sdk.locale.text.SellValue))) { - desc.splice(i, 1); - - i -= 1; - } else { - // Add color info - if (!desc[i].match(/^(y|ÿ)c/)) { - desc[i] = stringColor + desc[i]; - } - - // Find and store new color info - let index = desc[i].lastIndexOf("ÿc"); - - if (index > -1) { - stringColor = desc[i].substring(index, index + "ÿ".length + 2); - } - } - - desc[i] = desc[i].replace(/(y|ÿ)c([0-9!"+<:;.*])/g, "\\xffc$2"); - } - - if (logILvl && desc[desc.length - 1]) { - desc[desc.length - 1] = desc[desc.length - 1].trim() + " (" + unit.ilvl + ")"; - } - - desc = desc.reverse().join("\n"); - - return desc; - }, - - getItemCode: function (unit) { - if (unit === undefined) return ""; - - let code = (() => { - switch (unit.quality) { - case sdk.items.quality.Set: - switch (unit.classid) { - case sdk.items.Sabre: - return "inv9sbu"; - case sdk.items.ShortWarBow: - return "invswbu"; - case sdk.items.Helm: - return "invhlmu"; - case sdk.items.LargeShield: - return "invlrgu"; - case sdk.items.LongSword: - case sdk.items.CrypticSword: - return "invlsdu"; - case sdk.items.SmallShield: - return "invsmlu"; - case sdk.items.Buckler: - return "invbucu"; - case sdk.items.Cap: - return "invcapu"; - case sdk.items.BroadSword: - return "invbsdu"; - case sdk.items.FullHelm: - return "invfhlu"; - case sdk.items.GothicShield: - return "invgtsu"; - case sdk.items.AncientArmor: - case sdk.items.SacredArmor: - return "invaaru"; - case sdk.items.KiteShield: - return "invkitu"; - case sdk.items.TowerShield: - return "invtowu"; - case sdk.items.FullPlateMail: - return "invfulu"; - case sdk.items.MilitaryPick: - return "invmpiu"; - case sdk.items.JaggedStar: - return "invmstu"; - case sdk.items.ColossusBlade: - return "invgsdu"; - case sdk.items.OrnatePlate: - return "invxaru"; - case sdk.items.Cuirass: - case sdk.items.ReinforcedMace: - case sdk.items.Ward: - case sdk.items.SpiredHelm: - return "inv" + unit.code + "s"; - case sdk.items.GrandCrown: - return "invxrnu"; - case sdk.items.ScissorsSuwayyah: - return "invskru"; - case sdk.items.GrimHelm: - case sdk.items.BoneVisage: - return "invbhmu"; - case sdk.items.ElderStaff: - return "invcstu"; - case sdk.items.RoundShield: - return "invxmlu"; - case sdk.items.BoneWand: - return "invbwnu"; - default: - return ""; - } - case sdk.items.quality.Unique: - for (let i = 0; i < 401; i += 1) { - if (unit.code === getBaseStat("uniqueitems", i, 4).trim() - && unit.fname.split("\n").reverse()[0].includes(getLocaleString(getBaseStat("uniqueitems", i, 2)))) { - return getBaseStat("uniqueitems", i, "invfile"); - } - } - return ""; - default: - return ""; - } - })(); - - if (!code) { - // Tiara/Diadem - code = ["ci2", "ci3"].includes(unit.code) ? unit.code : (getBaseStat("items", unit.classid, "normcode") || unit.code); - code = code.replace(" ", ""); - [sdk.items.type.Ring, sdk.items.type.Amulet, sdk.items.type.Jewel, sdk.items.type.SmallCharm, sdk.items.type.LargeCharm, sdk.items.type.GrandCharm].includes(unit.itemType) && (code += (unit.gfx + 1)); - } - - return code; - }, - - getItemSockets: function (unit) { - let code; - let sockets = unit.sockets; - let subItems = unit.getItemsEx(); - let tempArray = []; - - if (subItems.length) { - switch (unit.sizex) { - case 2: - switch (unit.sizey) { - case 3: // 2 x 3 - switch (sockets) { - case 4: - tempArray = [subItems[0], subItems[3], subItems[2], subItems[1]]; - - break; - case 5: - tempArray = [subItems[1], subItems[4], subItems[0], subItems[3], subItems[2]]; - - break; - case 6: - tempArray = [subItems[0], subItems[3], subItems[1], subItems[4], subItems[2], subItems[5]]; - - break; - } - - break; - case 4: // 2 x 4 - switch (sockets) { - case 5: - tempArray = [subItems[1], subItems[4], subItems[0], subItems[3], subItems[2]]; - - break; - case 6: - tempArray = [subItems[0], subItems[3], subItems[1], subItems[4], subItems[2], subItems[5]]; - - break; - } - - break; - } - - break; - } - - if (tempArray.length === 0 && subItems.length > 0) { - tempArray = subItems.slice(0); - } - } - - for (let i = 0; i < sockets; i += 1) { - if (tempArray[i]) { - code = tempArray[i].code; - - if ([sdk.items.type.Ring, sdk.items.type.Amulet, sdk.items.type.Jewel, sdk.items.type.SmallCharm, sdk.items.type.LargeCharm, sdk.items.type.GrandCharm].includes(tempArray[i].itemType)) { - code += (tempArray[i].gfx + 1); - } - } else { - code = "gemsocket"; - } - - tempArray[i] = code; - } - - return tempArray; - }, - - useItemLog: true, // Might be a bit dirty - - itemLogger: function (action, unit, text) { - if (!Config.ItemInfo || !this.useItemLog) return false; - - let desc; - let date = new Date(); - let dateString = "[" + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, -5).replace(/-/g, "/").replace("T", " ") + "]"; - - switch (action) { - case "Sold": - if (Config.ItemInfoQuality.indexOf(unit.quality) === -1) { - return false; - } - - desc = this.getItemDesc(unit).split("\n").join(" | ").replace(/(\\xff|ÿ)c[0-9!"+<:;.*]/gi, "").trim(); - - break; - case "Kept": - case "Field Kept": - case "Runeword Kept": - case "Cubing Kept": - case "Shopped": - case "Gambled": - case "Dropped": - desc = this.getItemDesc(unit).split("\n").join(" | ").replace(/(\\xff|ÿ)c[0-9!"+<:;.*]|\/|\\/gi, "").trim(); - - break; - case "No room for": - desc = unit.name; - - break; - default: - desc = unit.fname.split("\n").reverse().join(" ").replace(/(\\xff|ÿ)c[0-9!"+<:;.*]|\/|\\/gi, "").trim(); - - break; - } - - return this.fileAction("logs/ItemLog.txt", 2, dateString + " <" + me.profile + "> <" + action + "> (" + Pickit.itemQualityToName(unit.quality) + ") " + desc + (text ? " {" + text + "}" : "") + "\n"); - }, - - // Log kept item stats in the manager. - logItem: function (action, unit, keptLine) { - if (!this.useItemLog) return false; - if (!Config.LogKeys && ["pk1", "pk2", "pk3"].includes(unit.code)) return false; - if (!Config.LogOrgans && ["dhn", "bey", "mbr"].includes(unit.code)) return false; - if (!Config.LogLowRunes && ["r01", "r02", "r03", "r04", "r05", "r06", "r07", "r08", "r09", "r10", "r11", "r12", "r13", "r14"].includes(unit.code)) return false; - if (!Config.LogMiddleRunes && ["r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23"].includes(unit.code)) return false; - if (!Config.LogHighRunes && ["r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", "r32", "r33"].includes(unit.code)) return false; - if (!Config.LogLowGems && ["gcv", "gcy", "gcb", "gcg", "gcr", "gcw", "skc", "gfv", "gfy", "gfb", "gfg", "gfr", "gfw", "skf", "gsv", "gsy", "gsb", "gsg", "gsr", "gsw", "sku"].includes(unit.code)) return false; - if (!Config.LogHighGems && ["gzv", "gly", "glb", "glg", "glr", "glw", "skl", "gpv", "gpy", "gpb", "gpg", "gpr", "gpw", "skz"].includes(unit.code)) return false; - - for (let i = 0; i < Config.SkipLogging.length; i++) { - if (Config.SkipLogging[i] === unit.classid || Config.SkipLogging[i] === unit.code) return false; - } - - let lastArea; - let name = unit.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<:;.*]|\/|\\/g, "").trim(); - let desc = this.getItemDesc(unit); - let color = (unit.getColor() || -1); - - if (action.match("kept", "i")) { - lastArea = DataFile.getStats().lastArea; - lastArea && (desc += ("\n\\xffc0Area: " + lastArea)); - } - - let code = this.getItemCode(unit); - let sock = unit.getItem(); - - if (sock) { - do { - if (sock.itemType === sdk.items.type.Jewel) { - desc += "\n\n"; - desc += this.getItemDesc(sock); - } - } while (sock.getNext()); - } - - keptLine && (desc += ("\n\\xffc0Line: " + keptLine)); - desc += "$" + (unit.ethereal ? ":eth" : ""); - - let itemObj = { - title: action + " " + name, - description: desc, - image: code, - textColor: unit.quality, - itemColor: color, - header: "", - sockets: this.getItemSockets(unit) - }; - - D2Bot.printToItemLog(itemObj); - - return true; - }, - - // skip low items: MuleLogger - skipItem: function (id) { - return [ - sdk.items.HandAxe, sdk.items.Wand, sdk.items.Club, sdk.items.ShortSword, sdk.items.Javelin, sdk.items.ShortStaff, sdk.items.Katar, - sdk.items.Buckler, sdk.items.StaminaPotion, sdk.items.AntidotePotion, sdk.items.RejuvenationPotion, sdk.items.FullRejuvenationPotion, - sdk.items.ThawingPotion, sdk.items.TomeofTownPortal, sdk.items.TomeofIdentify, sdk.items.ScrollofIdentify, sdk.items.ScrollofTownPortal, - sdk.items.Key, sdk.items.MinorHealingPotion, sdk.items.LightHealingPotion, sdk.items.HealingPotion, sdk.items.GreaterHealingPotion, - sdk.items.SuperHealingPotion, sdk.items.MinorManaPotion, sdk.items.LightManaPotion, sdk.items.ManaPotion, sdk.items.GreaterManaPotion, - sdk.items.SuperManaPotion - ].includes(id); - }, - - // Change into werewolf or werebear - shapeShift: function (mode) { - let [skill, state] = (() => { - switch (mode.toString().toLowerCase()) { - case "0": - return [-1, -1]; - case "1": - case "werewolf": - return [sdk.skills.Werewolf, sdk.states.Wearwolf]; - case "2": - case "werebear": - return [sdk.skills.Werebear, sdk.states.Wearbear]; - default: - throw new Error("shapeShift: Invalid parameter"); - } - })(); - - // don't have wanted skill - if (!Skill.canUse(skill)) return false; - // already in wanted state - if (me.getState(state)) return true; - - let slot = Attack.getPrimarySlot(); - me.switchWeapons(Precast.getBetterSlot(skill)); - - for (let i = 0; i < 3; i += 1) { - Skill.cast(skill, sdk.skills.hand.Right); - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (me.getState(state)) { - delay(250); - me.weaponswitch !== slot && me.switchWeapons(slot); - - return true; - } - - delay(10); - } - } - - me.weaponswitch !== slot && me.switchWeapons(slot); - - return false; - }, - - // Change back to human shape - unShift: function () { - if (me.getState(sdk.states.Wearwolf) || me.getState(sdk.states.Wearbear)) { - for (let i = 0; i < 3; i += 1) { - Skill.cast(me.getState(sdk.states.Wearwolf) ? sdk.skills.Werewolf : sdk.skills.Werebear); - - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (!me.getState(sdk.states.Wearwolf) && !me.getState(sdk.states.Wearbear)) { - delay(250); - - return true; - } - - delay(10); - } - } - } else { - return true; - } - - return false; - }, - - // Go to town when low on hp/mp or when out of potions. can be upgraded to check for curses etc. - townCheck: function () { - if (!Town.canTpToTown()) return false; - - let tTick = getTickCount(); - let check = false; - - if (Config.TownCheck && !me.inTown) { - try { - if (Town.needPotions() || (Config.OpenChests.Enabled && Town.needKeys())) { - check = true; - } - } catch (e) { - return false; - } - - if (check) { - // check that townchicken is running - so we don't spam needing potions if it isn't - let townChick = getScript("tools/TownChicken.js"); - if (!townChick || townChick && !townChick.running) { - return false; - } - - townChick.send("townCheck"); - console.log("townCheck check Duration: " + (getTickCount() - tTick)); - - return true; - } - } - - return false; - }, - - // Log someone's gear - spy: function (name) { - includeIfNotIncluded("oog.js"); - includeIfNotIncluded("common/prototypes.js"); - - let unit = getUnit(-1, name); - - if (!unit) { - console.warn("player not found"); - return false; - } - - let item = unit.getItem(); - - if (item) { - do { - this.logItem(unit.name, item); - } while (item.getNext()); - } - - return true; - }, - - fileAction: function (path, mode, msg) { - let contents = ""; - - MainLoop: - for (let i = 0; i < 30; i += 1) { - try { - switch (mode) { - case 0: // read - contents = FileTools.readText(path); - - break MainLoop; - case 1: // write - FileTools.writeText(path, msg); - - break MainLoop; - case 2: // append - FileTools.appendText(path, msg); - - break MainLoop; - } - } catch (e) { - continue; - } - - delay(100); - } - - return mode === 0 ? contents : true; - }, - - errorConsolePrint: true, - screenshotErrors: true, - - // Report script errors to logs/ScriptErrorLog.txt - errorReport: function (error, script) { - let msg, oogmsg, filemsg, source, stack; - let stackLog = ""; - - let date = new Date(); - let dateString = "[" + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, -5).replace(/-/g, "/").replace("T", " ") + "]"; - - if (typeof error === "string") { - msg = error; - oogmsg = error.replace(/ÿc[0-9!"+<:;.*]/gi, ""); - filemsg = dateString + " <" + me.profile + "> " + error.replace(/ÿc[0-9!"+<:;.*]/gi, "") + "\n"; - } else { - source = error.fileName.substring(error.fileName.lastIndexOf("\\") + 1, error.fileName.length); - msg = "ÿc1Error in ÿc0" + script + " ÿc1(" + source + " line ÿc1" + error.lineNumber + "): ÿc1" + error.message; - oogmsg = " Error in " + script + " (" + source + " #" + error.lineNumber + ") " + error.message + " (Area: " + me.area + ", Ping:" + me.ping + ", Game: " + me.gamename + ")"; - filemsg = dateString + " <" + me.profile + "> " + msg.replace(/ÿc[0-9!"+<:;.*]/gi, "") + "\n"; - - if (error.hasOwnProperty("stack")) { - stack = error.stack; - - if (stack) { - stack = stack.split("\n"); - - if (stack && typeof stack === "object") { - stack.reverse(); - } - - for (let i = 0; i < stack.length; i += 1) { - if (stack[i]) { - stackLog += stack[i].substr(0, stack[i].indexOf("@") + 1) + stack[i].substr(stack[i].lastIndexOf("\\") + 1, stack[i].length - 1); - - if (i < stack.length - 1) { - stackLog += ", "; - } - } - } - } - } - - stackLog && (filemsg += "Stack: " + stackLog + "\n"); - } - - this.errorConsolePrint && D2Bot.printToConsole(oogmsg, sdk.colors.D2Bot.Gray); - showConsole(); - console.log(msg); - this.fileAction("logs/ScriptErrorLog.txt", 2, filemsg); - - if (this.screenshotErrors) { - takeScreenshot(); - delay(500); - } - }, - - debugLog: function (msg) { - if (!Config.Debug) return; - debugLog(me.profile + ": " + msg); - }, - - // Use a NPC menu. Experimental function, subject to change - // id = string number (with exception of Ressurect merc). - useMenu: function (id) { - //print("useMenu " + getLocaleString(id)); - - let npc; - - switch (id) { - case sdk.menu.RessurectMerc: // (non-English dialog) - case sdk.menu.Trade: // (crash dialog) - npc = getInteractedNPC(); - - if (npc) { - npc.useMenu(id); - delay(750); - - return true; - } - - break; - } - - let lines = getDialogLines(); - if (!lines) return false; - - for (let i = 0; i < lines.length; i += 1) { - if (lines[i].selectable && lines[i].text.includes(getLocaleString(id))) { - getDialogLines()[i].handler(); - delay(750); - - return true; - } - } - - return false; - }, - - clone: function (obj) { - let copy; - - // Handle the 3 simple types, and null or undefined - if (null === obj || "object" !== typeof obj) { - return obj; - } - - // Handle Date - if (obj instanceof Date) { - copy = new Date(); - copy.setTime(obj.getTime()); - - return copy; - } - - // Handle Array - if (obj instanceof Array) { - copy = []; - - for (let i = 0; i < obj.length; i += 1) { - copy[i] = this.clone(obj[i]); - } - - return copy; - } - - // Handle Object - if (obj instanceof Object) { - copy = {}; - - for (let attr in obj) { - if (obj.hasOwnProperty(attr)) { - copy[attr] = this.clone(obj[attr]); - } - } - - return copy; - } - - throw new Error("Unable to copy obj! Its type isn't supported."); - }, - - copy: function (from) { - let obj = {}; - - for (let i in from) { - if (from.hasOwnProperty(i)) { - obj[i] = this.clone(from[i]); - } - } - - return obj; - }, - - poll: function (check, timeout = 6000, sleep = 40) { - let ret, start = getTickCount(); - - while (getTickCount() - start <= timeout) { - if ((ret = check())) { - return ret; - } - - delay(sleep); - } - - return false; - }, - - // returns array of UI flags that are set, or null if none are set - getUIFlags: function (excluded = []) { - if (!me.gameReady) return null; - - const MAX_FLAG = 37; // anything over 37 crashes - let flags = []; - - if (typeof excluded !== "object" || excluded.length === undefined) { - // not an array-like object, make it an array - excluded = [excluded]; - } - - for (let c = 1; c <= MAX_FLAG; c++) { - // 0x23 is always set in-game - if (c !== 0x23 && excluded.indexOf(c) === -1 && getUIFlag(c)) { - flags.push(c); - } - } - - return flags.length ? flags : null; - }, - - checkQuest: function (id, state) { - Packet.questRefresh(); - delay(500); - return me.getQuest(id, state); - }, - - getQuestStates: function (questID) { - if (!me.gameReady) return []; - Packet.questRefresh(); - delay(500); - const MAX_STATE = 16; - let questStates = []; - - for (let i = 0; i < MAX_STATE; i++) { - if (me.getQuest(questID, i)) { - questStates.push(i); - } - - delay(50); - } - - return questStates; - } -}; - -const Sort = { - // Sort units by comparing distance between the player - units: function (a, b) { - return Math.round(getDistance(me.x, me.y, a.x, a.y)) - Math.round(getDistance(me.x, me.y, b.x, b.y)); - }, - - // Sort preset units by comparing distance between the player (using preset x/y calculations) - presetUnits: function (a, b) { - return getDistance(me, a.roomx * 5 + a.x, a.roomy * 5 + a.y) - getDistance(me, b.roomx * 5 + b.x, b.roomy * 5 + b.y); - }, - - // Sort arrays of x,y coords by comparing distance between the player - points: function (a, b) { - return getDistance(me, a[0], a[1]) - getDistance(me, b[0], b[1]); - }, - - numbers: function (a, b) { - return a - b; - } -}; - -const Experience = { - totalExp: [0, 0, 500, 1500, 3750, 7875, 14175, 22680, 32886, 44396, 57715, 72144, 90180, 112725, 140906, 176132, 220165, 275207, 344008, 430010, 537513, 671891, 839864, 1049830, 1312287, 1640359, 2050449, 2563061, 3203826, 3902260, 4663553, 5493363, 6397855, 7383752, 8458379, 9629723, 10906488, 12298162, 13815086, 15468534, 17270791, 19235252, 21376515, 23710491, 26254525, 29027522, 32050088, 35344686, 38935798, 42850109, 47116709, 51767302, 56836449, 62361819, 68384473, 74949165, 82104680, 89904191, 98405658, 107672256, 117772849, 128782495, 140783010, 153863570, 168121381, 183662396, 200602101, 219066380, 239192444, 261129853, 285041630, 311105466, 339515048, 370481492, 404234916, 441026148, 481128591, 524840254, 572485967, 624419793, 681027665, 742730244, 809986056, 883294891, 963201521, 1050299747, 1145236814, 1248718217, 1361512946, 1484459201, 1618470619, 1764543065, 1923762030, 2097310703, 2286478756, 2492671933, 2717422497, 2962400612, 3229426756, 3520485254, 0, 0], - nextExp: [0, 500, 1000, 2250, 4125, 6300, 8505, 10206, 11510, 13319, 14429, 18036, 22545, 28181, 35226, 44033, 55042, 68801, 86002, 107503, 134378, 167973, 209966, 262457, 328072, 410090, 512612, 640765, 698434, 761293, 829810, 904492, 985897, 1074627, 1171344, 1276765, 1391674, 1516924, 1653448, 1802257, 1964461, 2141263, 2333976, 2544034, 2772997, 3022566, 3294598, 3591112, 3914311, 4266600, 4650593, 5069147, 5525370, 6022654, 6564692, 7155515, 7799511, 8501467, 9266598, 10100593, 11009646, 12000515, 13080560, 14257811, 15541015, 16939705, 18464279, 20126064, 21937409, 23911777, 26063836, 28409582, 30966444, 33753424, 36791232, 40102443, 43711663, 47645713, 51933826, 56607872, 61702579, 67255812, 73308835, 79906630, 87098226, 94937067, 103481403, 112794729, 122946255, 134011418, 146072446, 159218965, 173548673, 189168053, 206193177, 224750564, 244978115, 267026144, 291058498, 0, 0], - expCurve: [13, 16, 110, 159, 207, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 174, 92, 38, 5], - expPenalty: [1024, 976, 928, 880, 832, 784, 736, 688, 640, 592, 544, 496, 448, 400, 352, 304, 256, 192, 144, 108, 81, 61, 46, 35, 26, 20, 15, 11, 8, 6, 5], - monsterExp: [ - [1, 1, 1], [30, 78, 117], [40, 104, 156], [50, 131, 197], [60, 156, 234], [70, 182, 273], [80, 207, 311], [90, 234, 351], [100, 260, 390], [110, 285, 428], [120, 312, 468], - [130, 338, 507], [140, 363, 545], [154, 401, 602], [169, 440, 660], [186, 482, 723], [205, 533, 800], [225, 584, 876], [248, 644, 966], [273, 708, 1062], [300, 779, 1169], - [330, 857, 1286], [363, 942, 1413], [399, 1035, 1553], [439, 1139, 1709], [470, 1220, 1830], [503, 1305, 1958], [538, 1397, 2096], [576, 1494, 2241], [616, 1598, 2397], - [659, 1709, 2564], [706, 1832, 2748], [755, 1958, 2937], [808, 2097, 3146], [864, 2241, 3362], [925, 2399, 3599], [990, 2568, 3852], [1059, 2745, 4118], [1133, 2939, 4409], - [1212, 3144, 4716], [1297, 3365, 5048], [1388, 3600, 5400], [1485, 3852, 5778], [1589, 4121, 6182], [1693, 4409, 6614], [1797, 4718, 7077], [1901, 5051, 7577], - [2005, 5402, 8103], [2109, 5783, 8675], [2213, 6186, 9279], [2317, 6618, 9927], [2421, 7080, 10620], [2525, 7506, 11259], [2629, 7956, 11934], [2733, 8435, 12653], - [2837, 8942, 13413], [2941, 9477, 14216], [3045, 10044, 15066], [3149, 10647, 15971], [3253, 11286, 16929], [3357, 11964, 17946], [3461, 12680, 19020], - [3565, 13442, 20163], [3669, 14249, 21374], [3773, 15104, 22656], [3877, 16010, 24015], [3981, 16916, 25374], [4085, 17822, 26733], [4189, 18728, 28092], - [4293, 19634, 29451], [4397, 20540, 30810], [4501, 21446, 32169], [4605, 22352, 33528], [4709, 23258, 34887], [4813, 24164, 36246], [4917, 25070, 37605], - [5021, 25976, 38964], [5125, 26882, 40323], [5229, 27788, 41682], [5333, 28694, 43041], [5437, 29600, 44400], [5541, 30506, 45759], [5645, 31412, 47118], - [5749, 32318, 48477], [5853, 33224, 49836], [5957, 34130, 51195], [6061, 35036, 52554], [6165, 35942, 53913], [6269, 36848, 55272], [6373, 37754, 56631], - [6477, 38660, 57990], [6581, 39566, 59349], [6685, 40472, 60708], [6789, 41378, 62067], [6893, 42284, 63426], [6997, 43190, 64785], [7101, 44096, 66144], - [7205, 45002, 67503], [7309, 45908, 68862], [7413, 46814, 70221], [7517, 47720, 71580], [7621, 48626, 72939], [7725, 49532, 74298], [7829, 50438, 75657], - [7933, 51344, 77016], [8037, 52250, 78375], [8141, 53156, 79734], [8245, 54062, 81093], [8349, 54968, 82452], [8453, 55874, 83811], [160000, 160000, 160000] - ], - // Percent progress into the current level. Format: xx.xx% - progress: function () { - return me.getStat(sdk.stats.Level) === 99 ? 0 : (((me.getStat(sdk.stats.Experience) - this.totalExp[me.getStat(sdk.stats.Level)]) / this.nextExp[me.getStat(sdk.stats.Level)]) * 100).toFixed(2); - }, - - // Total experience gained in current run - gain: function () { - return (me.getStat(sdk.stats.Experience) - DataFile.getStats().experience); - }, - - // Percent experience gained in current run - gainPercent: function () { - return me.getStat(sdk.stats.Level) === 99 ? 0 : (this.gain() * 100 / this.nextExp[me.getStat(sdk.stats.Level)]).toFixed(6); - }, - - // Runs until next level - runsToLevel: function () { - return Math.round(((100 - this.progress()) / 100) * this.nextExp[me.getStat(sdk.stats.Level)] / this.gain()); - }, - - // Total runs needed for next level (not counting current progress) - totalRunsToLevel: function () { - return Math.round(this.nextExp[me.getStat(sdk.stats.Level)] / this.gain()); - }, - - // Total time till next level - timeToLevel: function () { - let tTLrawSeconds = (Math.floor((getTickCount() - me.gamestarttime) / 1000)).toString(); - let tTLrawtimeToLevel = this.runsToLevel() * tTLrawSeconds; - let tTLDays = Math.floor(tTLrawtimeToLevel / 86400); - let tTLHours = Math.floor((tTLrawtimeToLevel % 86400) / 3600); - let tTLMinutes = Math.floor(((tTLrawtimeToLevel % 86400) % 3600) / 60); - //let tTLSeconds = ((tTLrawtimeToLevel % 86400) % 3600) % 60; - - //return tDays + "d " + tTLHours + "h " + tTLMinutes + "m " + tTLSeconds + "s"; - //return tTLDays + "d " + tTLHours + "h " + tTLMinutes + "m"; - return (tTLDays ? tTLDays + " d " : "") + (tTLHours ? tTLHours + " h " : "") + (tTLMinutes ? tTLMinutes + " m" : ""); - }, - - // Get Game Time - getGameTime: function () { - let rawMinutes = Math.floor((getTickCount() - me.gamestarttime) / 60000).toString(); - let rawSeconds = (Math.floor((getTickCount() - me.gamestarttime) / 1000) % 60).toString(); - - rawMinutes <= 9 && (rawMinutes = "0" + rawMinutes); - rawSeconds <= 9 && (rawSeconds = "0" + rawSeconds); - - return " (" + rawMinutes + ":" + rawSeconds + ")"; - }, - - // Log to manager - log: function () { - let gain = this.gain(); - let progress = this.progress(); - let runsToLevel = this.runsToLevel(); - let getGameTime = this.getGameTime(); - let string = "[Game: " + me.gamename + (me.gamepassword ? "//" + me.gamepassword : "") + getGameTime + "] [Level: " + me.getStat(sdk.stats.Level) + " (" + progress + "%)] [XP: " + gain + "] [Games ETA: " + runsToLevel + "]"; - - if (gain) { - D2Bot.printToConsole(string, sdk.colors.D2Bot.Blue); - - if (me.getStat(sdk.stats.Level) > DataFile.getStats().level) { - D2Bot.printToConsole("Congrats! You gained a level. Current level:" + me.getStat(sdk.stats.Level), sdk.colors.D2Bot.Green); - } - } - } -}; - -const Packet = { - openMenu: function (unit) { - if (unit.type !== sdk.unittype.NPC) throw new Error("openMenu: Must be used on NPCs."); - if (getUIFlag(sdk.uiflags.NPCMenu)) return true; - let pingDelay = (me.gameReady ? me.ping : 125); - - for (let i = 0; i < 5; i += 1) { - unit.distance > 4 && Pather.moveToUnit(unit); - Packet.entityInteract(unit); - let tick = getTickCount(); - - while (getTickCount() - tick < 5000) { - if (getUIFlag(sdk.uiflags.NPCMenu)) { - delay(Math.max(500, pingDelay * 2)); - - return true; - } - - if (getInteractedNPC() && getTickCount() - tick > 1000) { - me.cancel(); - } - - delay(100); - } - - sendPacket(1, sdk.packets.send.NPCInit, 4, 1, 4, unit.gid); - delay(pingDelay + 1 * 2); - Packet.cancelNPC(unit); - delay(pingDelay + 1 * 2); - this.flash(me.gid); - } - - return false; - }, - - startTrade: function (unit, mode) { - if (unit.type !== sdk.unittype.NPC) throw new Error("Unit.startTrade: Must be used on NPCs."); - if (getUIFlag(sdk.uiflags.Shop)) return true; - - const gamble = mode === "Gamble"; - console.info(true, mode + " at " + unit.name); - - if (this.openMenu(unit)) { - for (let i = 0; i < 10; i += 1) { - delay(200); - - i % 2 === 0 && sendPacket(1, sdk.packets.send.EntityAction, 4, gamble ? 2 : 1, 4, unit.gid, 4, 0); - - if (unit.itemcount > 0) { - delay(200); - console.info(false, "Successfully started " + mode + " at " + unit.name); - return true; - } - } - } - - return false; - }, - - buyItem: function (unit, shiftBuy, gamble) { - let oldGold = me.gold; - let itemCount = me.itemcount; - let npc = getInteractedNPC(); - - try { - if (!npc) throw new Error("buyItem: No NPC menu open."); - - // Can we afford the item? - if (oldGold < unit.getItemCost(sdk.items.cost.ToBuy)) return false; - - for (let i = 0; i < 3; i += 1) { - sendPacket(1, sdk.packets.send.NPCBuy, 4, npc.gid, 4, unit.gid, 4, shiftBuy ? 0x80000000 : gamble ? 0x2 : 0x0, 4, 0); - - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(2000, me.ping * 2 + 500)) { - if (shiftBuy && me.gold < oldGold) return true; - if (itemCount !== me.itemcount) return true; - - delay(10); - } - } - } catch (e) { - console.error(e); - } - - return false; - }, - - buyScroll: function (unit, tome, shiftBuy) { - let oldGold = me.gold; - let itemCount = me.itemcount; - let npc = getInteractedNPC(); - tome === undefined && (tome = me.findItem( - (unit.classid === sdk.items.ScrollofTownPortal ? sdk.items.TomeofTownPortal : sdk.items.TomeofIdentify), - sdk.items.mode.inStorage, sdk.storage.Inventory - )); - let preCount = !!tome ? tome.getStat(sdk.stats.Quantity) : 0; - - try { - if (!npc) throw new Error("buyItem: No NPC menu open."); - - // Can we afford the item? - if (oldGold < unit.getItemCost(sdk.items.cost.ToBuy)) return false; - - for (let i = 0; i < 3; i += 1) { - sendPacket(1, sdk.packets.send.NPCBuy, 4, npc.gid, 4, unit.gid, 4, shiftBuy ? 0x80000000 : 0x0, 4, 0); - - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(2000, me.ping * 2 + 500)) { - if (shiftBuy && me.gold < oldGold) return true; - if (itemCount !== me.itemcount) return true; - if (tome && tome.getStat(sdk.stats.Quantity) > preCount) return true; - delay(10); - } - } - } catch (e) { - console.error(e); - } - - return false; - }, - - sellItem: function (unit) { - // Check if it's an item we want to buy - if (unit.type !== sdk.unittype.Item) throw new Error("Unit.sell: Must be used on items."); - if (!unit.sellable) { - console.error((new Error("Item is unsellable"))); - return false; - } - - let itemCount = me.itemcount; - let npc = getInteractedNPC(); - - if (!npc) return false; - - for (let i = 0; i < 5; i += 1) { - sendPacket(1, sdk.packets.send.NPCSell, 4, npc.gid, 4, unit.gid, 4, 0, 4, 0); - - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (me.itemcount !== itemCount) return true; - delay(10); - } - } - - return false; - }, - - identifyItem: function (unit, tome) { - if (!unit || unit.identified) return false; - - CursorLoop: - for (let i = 0; i < 3; i += 1) { - sendPacket(1, sdk.packets.send.IndentifyItem, 4, unit.gid, 4, tome.gid); - - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (getCursorType() === sdk.cursortype.Identify) { - break CursorLoop; - } - - delay(10); - } - } - - if (getCursorType() !== sdk.cursortype.Identify) { - return false; - } - - for (let i = 0; i < 3; i += 1) { - getCursorType() === sdk.cursortype.Identify && sendPacket(1, sdk.packets.send.IndentifyItem, 4, unit.gid, 4, tome.gid); - - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (unit.identified) { - delay(50); - return true; - } - - delay(10); - } - } - - return false; - }, - - itemToCursor: function (item) { - // Something already on cursor - if (me.itemoncursor) { - let cursorItem = Game.getCursorUnit(); - // Return true if the item is already on cursor - if (cursorItem.gid === item.gid) { - return true; - } - this.dropItem(cursorItem); // If another item is on cursor, drop it - } - - for (let i = 0; i < 15; i += 1) { - // equipped - item.isEquipped ? sendPacket(1, sdk.packets.send.PickupBodyItem, 2, item.bodylocation) : sendPacket(1, sdk.packets.send.PickupBufferItem, 4, item.gid); - - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(500, me.ping * 2 + 200)) { - if (me.itemoncursor) return true; - delay(10); - } - } - - return false; - }, - - dropItem: function (item) { - if (!this.itemToCursor(item)) return false; - - for (let i = 0; i < 15; i += 1) { - sendPacket(1, sdk.packets.send.DropItem, 4, item.gid); - - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(500, me.ping * 2 + 200)) { - if (!me.itemoncursor) return true; - delay(10); - } - } - - return false; - }, - - givePotToMerc: function (item) { - if (!!item - && [sdk.items.type.HealingPotion, sdk.items.type.RejuvPotion, sdk.items.type.ThawingPotion, sdk.items.type.AntidotePotion].includes(item.itemType)) { - switch (item.location) { - case sdk.storage.Belt: - return this.useBeltItemForMerc(item); - case sdk.storage.Inventory: - if (this.itemToCursor(item)) { - sendPacket(1, sdk.packets.send.MercItem, 2, 0); - - return true; - } - - break; - default: - break; - } - } - - return false; - }, - - placeInBelt: function (item, xLoc) { - item.toCursor(true) && new PacketBuilder().byte(sdk.packets.send.ItemToBelt).dword(item.gid).dword(xLoc).send(); - return Misc.poll(() => item.isInBelt, 500, 100); - }, - - click: function (who, toCursor = false) { - if (!who || !copyUnit(who).x) return false; - new PacketBuilder().byte(sdk.packets.send.PickupItem).dword(sdk.unittype.Item).dword(who.gid).dword(toCursor ? 1 : 0).send(); - return true; - }, - - entityInteract: function (who) { - if (!who || !copyUnit(who).x) return false; - sendPacket(1, sdk.packets.send.InteractWithEntity, 4, who.type, 4, who.gid); - return true; - }, - - cancelNPC: function (who) { - if (!who || !copyUnit(who).x) return false; - sendPacket(1, sdk.packets.send.NPCCancel, 4, who.type, 4, who.gid); - return true; - }, - - useBeltItemForMerc: function (who) { - if (!who) return false; - sendPacket(1, sdk.packets.send.UseBeltItem, 4, who.gid, 4, 1, 4, 0); - return true; - }, - - castSkill: function (hand, wX, wY) { - hand = (hand === sdk.skills.hand.Right) ? sdk.packets.send.RightSkillOnLocation : sdk.packets.send.LeftSkillOnLocation; - sendPacket(1, hand, 2, wX, 2, wY); - }, - - unitCast: function (hand, who) { - hand = (hand === sdk.skills.hand.Right) ? sdk.packets.send.RightSkillOnEntityEx3 : sdk.packets.send.LeftSkillOnEntityEx3; - sendPacket(1, hand, 4, who.type, 4, who.gid); - }, - - telekinesis: function (who) { - if (!who || !Skill.setSkill(sdk.skills.Telekinesis, sdk.skills.hand.Right)) return false; - sendPacket(1, sdk.packets.send.RightSkillOnEntityEx3, 4, who.type, 4, who.gid); - return true; - }, - - enchant: function (who) { - if (!who || !Skill.setSkill(sdk.skills.Enchant, sdk.skills.hand.Right)) return false; - sendPacket(1, sdk.packets.send.RightSkillOnEntityEx3, 4, who.type, 4, who.gid); - return true; - }, - - teleport: function (wX, wY) { - if (![wX, wY].every(n => typeof n === "number") || !Skill.setSkill(sdk.skills.Teleport, sdk.skills.hand.Right)) return false; - sendPacket(1, sdk.packets.send.RightSkillOnLocation, 2, wX, 2, wY); - return true; - }, - - // moveNPC: function (npc, dwX, dwY) { // commented the patched packet - // //sendPacket(1, sdk.packets.send.MakeEntityMove, 4, npc.type, 4, npc.gid, 4, dwX, 4, dwY); - // }, - - teleWalk: function (x, y, maxDist = 5) { - !this.telewalkTick && (this.telewalkTick = 0); - - if (getDistance(me, x, y) > 10 && getTickCount() - this.telewalkTick > 3000 && Attack.validSpot(x, y)) { - for (let i = 0; i < 5; i += 1) { - sendPacket(1, sdk.packets.send.UpdatePlayerPos, 2, x + rand(-1, 1), 2, y + rand(-1, 1)); - delay(me.ping + 1); - sendPacket(1, sdk.packets.send.RequestEntityUpdate, 4, me.type, 4, me.gid); - delay(me.ping + 1); - - if (getDistance(me, x, y) < maxDist) { - delay(200); - - return true; - } - } - - this.telewalkTick = getTickCount(); - } - - return false; - }, - - questRefresh: function () { - sendPacket(1, sdk.packets.send.UpdateQuests); - }, - - flash: function (gid, wait = 0) { - wait === 0 && (wait = 300 + (me.gameReady ? 2 * me.ping : 300)); - sendPacket(1, sdk.packets.send.RequestEntityUpdate, 4, 0, 4, gid); - - if (wait > 0) { - delay(wait); - } - }, - - changeStat: function (stat, value) { - if (value > 0) { - getPacket(1, 0x1d, 1, stat, 1, value); - } - }, - - // specialized wrapper for addEventListener - addListener: function (packetType, callback) { - if (typeof packetType === "number") { - packetType = [packetType]; - } - - if (typeof packetType === "object" && packetType.length) { - addEventListener("gamepacket", packet => (packetType.indexOf(packet[0]) > -1 ? callback(packet) : false)); - - return callback; - } - - return null; - }, - - removeListener: callback => removeEventListener("gamepacket", callback), // just a wrapper -}; - -/* - -new PacketBuilder() - create new packet object - -Example (Spoof 'reassign player' packet to client): - new PacketBuilder().byte(sdk.packets.recv.ReassignPlayer).byte(0).dword(me.gid).word(x).word(y).byte(1).get(); - -Example (Spoof 'player move' packet to server): - new PacketBuilder().byte(sdk.packets.send.RunToLocation).word(x).word(y).send(); -*/ - -function PacketBuilder () { - // globals DataView ArrayBuffer - if (this.__proto__.constructor !== PacketBuilder) throw new Error("PacketBuilder must be called with 'new' operator!"); - - let queue = [], count = 0; - - // accepts any number of arguments - let enqueue = (type, size) => (...args) => { - args.forEach(arg => { - if (type === "String") { - arg = stringToEUC(arg); - size = arg.length + 1; - } - - queue.push({type: type, size: size, data: arg}); - count += size; - }); - - return this; - }; - - this.float = enqueue("Float32", 4); - this.dword = enqueue("Uint32", 4); - this.word = enqueue("Uint16", 2); - this.byte = enqueue("Uint8", 1); - this.string = enqueue("String"); - - this.buildDataView = () => { - let dv = new DataView(new ArrayBuffer(count)), i = 0; - queue.forEach(field => { - if (field.type === "String") { - for (let l = 0; l < field.data.length; l++) { - dv.setUint8(i++, field.data.charCodeAt(l), true); - } - - i += field.size - field.data.length; // fix index for field.size !== field.data.length - } else { - dv["set" + field.type](i, field.data, true); - i += field.size; - } - }); - - return dv; - }; - - this.send = () => (sendPacket(this.buildDataView().buffer), this); - this.spoof = () => (getPacket(this.buildDataView().buffer), this); - this.get = this.spoof; // same thing but spoof has clearer intent than get -} - -const LocalChat = new function () { - const LOCAL_CHAT_ID = 0xD2BAAAA; - let toggle, proxy = say; - - let relay = (msg) => D2Bot.shoutGlobal(JSON.stringify({ msg: msg, realm: me.realm, charname: me.charname, gamename: me.gamename }), LOCAL_CHAT_ID); - - let onChatInput = (speaker, msg) => { - relay(msg); - return true; - }; - - let onChatRecv = (mode, msg) => { - if (mode !== LOCAL_CHAT_ID) { - return; - } - - msg = JSON.parse(msg); - - if (me.gamename === msg.gamename && me.realm === msg.realm) { - new PacketBuilder().byte(38).byte(1, me.locale).word(2, 0, 0).byte(90).string(msg.charname, msg.msg).get(); - } - }; - - let onKeyEvent = (key) => { - if (toggle === key) { - this.init(true); - } - }; - - this.init = (cycle = false) => { - if (!Config.LocalChat.Enabled) return; - - Config.LocalChat.Mode = (Config.LocalChat.Mode + cycle) % 3; - print("ÿc2LocalChat enabled. Mode: " + Config.LocalChat.Mode); - - switch (Config.LocalChat.Mode) { - case 2: - removeEventListener("chatinputblocker", onChatInput); - addEventListener("chatinputblocker", onChatInput); - // eslint-disable-next-line no-fallthrough - case 1: - removeEventListener("copydata", onChatRecv); - addEventListener("copydata", onChatRecv); - say = (msg, force = false) => force ? proxy(msg) : relay(msg); - break; - case 0: - removeEventListener("chatinputblocker", onChatInput); - removeEventListener("copydata", onChatRecv); - say = proxy; - break; - } - - if (Config.LocalChat.Toggle) { - toggle = typeof Config.LocalChat.Toggle === "string" ? Config.LocalChat.Toggle.charCodeAt(0) : Config.LocalChat.Toggle; - Config.LocalChat.Toggle = false; - addEventListener("keyup", onKeyEvent); - } - }; -}; - -const Messaging = { - sendToScript: function (name, msg) { - let script = getScript(name); - - if (script && script.running) { - script.send(msg); - - return true; - } - - return false; - }, - - sendToProfile: function (profileName, mode, message, getResponse = false) { - let response; - - function copyDataEvent(mode2, msg) { - if (mode2 === mode) { - let obj; - - try { - obj = JSON.parse(msg); - } catch (e) { - return false; - } - - if (obj.hasOwnProperty("sender") && obj.sender === profileName) { - response = Misc.copy(obj); - } - - return true; - } - - return false; - } - - getResponse && addEventListener("copydata", copyDataEvent); - - if (!sendCopyData(null, profileName, mode, JSON.stringify({message: message, sender: me.profile}))) { - //print("sendToProfile: failed to get response from " + profileName); - getResponse && removeEventListener("copydata", copyDataEvent); - - return false; - } - - if (getResponse) { - delay(200); - removeEventListener("copydata", copyDataEvent); - - if (!!response) { - return response; - } - - return false; - } - - return true; - } -}; - -// Unused anywhere -// var Events = { -// // gamepacket -// gamePacket: function (bytes) { -// let temp; - -// switch (bytes[0]) { -// // Block movement after using TP/WP/Exit -// case 0x0D: // Player Stop -// // This can mess up death screen so disable for characters that are allowed to die -// if (Config.LifeChicken > 0) { -// return true; -// } - -// break; -// // Block poison skills that might crash the client -// case 0x4C: // Cast skill on target -// case 0x4D: // Cast skill on coords -// temp = Number("0x" + bytes[7].toString(16) + bytes[6].toString(16)); - -// // Match Poison Javelin, Plague Javelin or Poison Nova -// if (temp && [15, 25, 92].indexOf(temp) > -1) { -// return true; -// } - -// break; -// } - -// return false; -// } -// }; diff --git a/d2bs/kolbot/libs/common/Pather.js b/d2bs/kolbot/libs/common/Pather.js deleted file mode 100644 index fed93409e..000000000 --- a/d2bs/kolbot/libs/common/Pather.js +++ /dev/null @@ -1,2057 +0,0 @@ -/** -* @filename Pather.js -* @author kolton, theBGuy -* @desc handle player movement -* -*/ - -// TODO: this needs to be re-worked -// Perform certain actions after moving to each node -const NodeAction = { - shrinesToIgnore: [], - - // Run all the functions within NodeAction (except for itself) - go: function (arg) { - for (let i in this) { - if (this.hasOwnProperty(i) && typeof this[i] === "function" && i !== "go") { - this[i](arg); - } - } - }, - - // Kill monsters while pathing - killMonsters: function (arg = {}) { - const settings = Object.assign({}, { - clearPath: false, - specType: sdk.monsters.spectype.All, - range: 10, - overrideConfig: false, - }, arg); - - if (Config.Countess.KillGhosts && [sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TowerCellarLvl5].includes(me.area)) { - let monList = (Attack.getMob(sdk.monsters.Ghost1, sdk.monsters.spectype.All, 30) || []); - monList.length > 0 && Attack.clearList(monList); - } - - if ((typeof Config.ClearPath === "number" || typeof Config.ClearPath === "object") && settings.clearPath === false && !settings.overrideConfig) { - switch (typeof Config.ClearPath) { - case "number": - Attack.clear(30, Config.ClearPath); - - break; - case "object": - if (!Config.ClearPath.hasOwnProperty("Areas") || !Config.ClearPath.Areas.length || Config.ClearPath.Areas.includes(me.area)) { - Attack.clear(Config.ClearPath.Range, Config.ClearPath.Spectype); - } - - break; - } - - return; - } - - if (settings.clearPath !== false) { - Attack.clear(settings.range, settings.specType); - } - }, - - // Open chests while pathing - popChests: function () { - // fastPick check? should only open chests if surrounding monsters have been cleared or if fastPick is active - // note: clear of surrounding monsters of the spectype we are set to clear - Config.OpenChests.Enabled && Misc.openChests(Config.OpenChests.Range); - }, - - // Scan shrines while pathing - getShrines: function () { - Config.ScanShrines.length > 0 && Misc.scanShrines(null, this.shrinesToIgnore); - } -}; - -const PathDebug = { - hooks: [], - enableHooks: false, - - drawPath: function (path) { - if (!this.enableHooks) return; - - this.removeHooks(); - - if (path.length < 2) return; - - for (let i = 0; i < path.length - 1; i += 1) { - this.hooks.push(new Line(path[i].x, path[i].y, path[i + 1].x, path[i + 1].y, 0x84, true)); - } - }, - - removeHooks: function () { - for (let i = 0; i < this.hooks.length; i += 1) { - this.hooks[i].remove(); - } - - this.hooks = []; - }, - - coordsInPath: function (path, x, y) { - for (let i = 0; i < path.length; i += 1) { - if (getDistance(x, y, path[i].x, path[i].y) < 5) { - return true; - } - } - - return false; - } -}; - -// todo - test path generating in a dedicated thread to prevent lagging main thread -const Pather = { - initialized: false, - teleport: true, - recursion: true, - walkDistance: 5, - teleDistance: 40, - lastPortalTick: 0, - cancelFlags: [ - sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.SkillWindow, sdk.uiflags.NPCMenu, sdk.uiflags.Waypoint, - sdk.uiflags.Party, sdk.uiflags.Shop, sdk.uiflags.Quest, sdk.uiflags.TradePrompt, sdk.uiflags.Stash, sdk.uiflags.Cube - ], - wpAreas: [ - sdk.areas.RogueEncampment, sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.DarkWood, sdk.areas.BlackMarsh, sdk.areas.OuterCloister, - sdk.areas.JailLvl1, sdk.areas.InnerCloister, sdk.areas.CatacombsLvl2, sdk.areas.LutGholein, sdk.areas.A2SewersLvl2, sdk.areas.DryHills, - sdk.areas.HallsoftheDeadLvl2, sdk.areas.FarOasis, sdk.areas.LostCity, sdk.areas.PalaceCellarLvl1, sdk.areas.ArcaneSanctuary, sdk.areas.CanyonofMagic, - sdk.areas.KurastDocktown, sdk.areas.SpiderForest, sdk.areas.GreatMarsh, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, sdk.areas.KurastBazaar, - sdk.areas.UpperKurast, sdk.areas.Travincal, sdk.areas.DuranceofHateLvl2, sdk.areas.PandemoniumFortress, sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, - sdk.areas.Harrogath, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, sdk.areas.HallsofPain, - sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.WorldstoneLvl2 - ], - nonTownWpAreas: [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.DarkWood, sdk.areas.BlackMarsh, sdk.areas.OuterCloister, - sdk.areas.JailLvl1, sdk.areas.InnerCloister, sdk.areas.CatacombsLvl2, sdk.areas.A2SewersLvl2, sdk.areas.DryHills, - sdk.areas.HallsoftheDeadLvl2, sdk.areas.FarOasis, sdk.areas.LostCity, sdk.areas.PalaceCellarLvl1, sdk.areas.ArcaneSanctuary, sdk.areas.CanyonofMagic, - sdk.areas.SpiderForest, sdk.areas.GreatMarsh, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, sdk.areas.KurastBazaar, - sdk.areas.UpperKurast, sdk.areas.Travincal, sdk.areas.DuranceofHateLvl2, sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, - sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, sdk.areas.HallsofPain, - sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.WorldstoneLvl2 - ], - nextAreas: {}, - - init: function () { - if (!this.initialized) { - me.classic && (this.nonTownWpAreas = this.nonTownWpAreas.filter((wp) => wp < sdk.areas.Harrogath)); - if (!Config.WaypointMenu) { - !getWaypoint(1) && this.getWP(me.area); - me.cancelUIFlags(); - this.initialized = true; - } - } - }, - - canTeleport: function () { - return this.teleport && (Skill.canUse(sdk.skills.Teleport) || me.getStat(sdk.stats.OSkill, sdk.skills.Teleport)); - }, - - useTeleport: function () { - let manaTP = Skill.getManaCost(sdk.skills.Teleport); - let numberOfTeleport = ~~(me.mpmax / manaTP); - return !me.inTown && !Config.NoTele && !me.shapeshifted && this.canTeleport() && numberOfTeleport > 2; - }, - - spotOnDistance: function (spot, distance, givenSettings = {}) { - const settings = Object.assign({}, { - area: me.area, - reductionType: 2, - coll: (sdk.collision.BlockWalk), - returnSpotOnError: true - }, givenSettings); - - let nodes = (getPath(settings.area, me.x, me.y, spot.x, spot.y, settings.reductionType, 4) || []); - - if (!nodes.length) { - if (settings.reductionType === 2) { - // try again with walking reduction - nodes = getPath(settings.area, me.x, me.y, spot.x, spot.y, 0, 4); - } - if (!nodes.length) return (settings.returnSpotOnError ? spot : { x: me.x, y: me.y }); - } - - return (nodes.find((node) => getDistance(spot.x, spot.y, node.x, node.y) < distance && Pather.checkSpot(node.x, node.y, settings.coll)) - || (settings.returnSpotOnError ? spot : { x: me.x, y: me.y })); - }, - - move: function (target, givenSettings = {}) { - // Abort if dead - if (me.dead) return false; - // assign settings - const settings = Object.assign({}, { - clearSettings: { - }, - allowTeleport: true, - allowClearing: true, - allowTown: true, - minDist: 3, - retry: 5, - pop: false, - returnSpotOnError: true, - callback: () => {}, - }, givenSettings); - // assign clear settings becasue object.assign was removing the default properties of settings.clearSettings - const clearSettings = Object.assign({ - clearPath: false, - range: 10, - specType: 0, - sort: Attack.sortMonsters, - }, settings.clearSettings); - // set settings.clearSettings equal to the now properly asssigned clearSettings - settings.clearSettings = clearSettings; - - !settings.allowClearing && (settings.clearSettings.allowClearing = false); - (target instanceof PresetUnit) && (target = { x: target.roomx * 5 + target.x, y: target.roomy * 5 + target.y }); - - if (settings.minDist > 3) { - target = this.spotOnDistance(target, settings.minDist, {returnSpotOnError: settings.returnSpotOnError, reductionType: (me.inTown ? 0 : 2)}); - } - - let fail = 0; - let node = {x: target.x, y: target.y}; - let [cleared, leaped, invalidCheck] = [false, false, false]; - - for (let i = 0; i < this.cancelFlags.length; i += 1) { - getUIFlag(this.cancelFlags[i]) && me.cancel(); - } - - if (typeof target.x !== "number" || typeof target.y !== "number") throw new Error("move: Coords must be numbers"); - if (getDistance(me, target) < 2 && !CollMap.checkColl(me, target, sdk.collision.BlockMissile, 5)) return true; - - let useTeleport = settings.allowTeleport && this.useTeleport(); - const tpMana = useTeleport ? Skill.getManaCost(sdk.skills.Teleport) : Infinity; - const annoyingArea = [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3].includes(me.area); - let path = getPath(me.area, target.x, target.y, me.x, me.y, useTeleport ? 1 : 0, useTeleport ? (annoyingArea ? 30 : this.teleDistance) : this.walkDistance); - if (!path) throw new Error("move: Failed to generate path."); - - path.reverse(); - settings.pop && path.pop(); - PathDebug.drawPath(path); - useTeleport && Config.TeleSwitch && path.length > 5 && me.switchWeapons(Attack.getPrimarySlot() ^ 1); - - while (path.length > 0) { - // Abort if dead - if (me.dead) return false; - // main path - this.recursion && (this.currentWalkingPath = path); - - for (let i = 0; i < this.cancelFlags.length; i += 1) { - if (getUIFlag(this.cancelFlags[i])) me.cancel(); - } - - node = path.shift(); - - if (getDistance(me, node) > 2) { - // Make life in Maggot Lair easier - fail >= 3 && fail % 3 === 0 && !Attack.validSpot(node.x, node.y) && (invalidCheck = true); - // Make life in Maggot Lair easier - should this include arcane as well? - if (annoyingArea || invalidCheck) { - let adjustedNode = this.getNearestWalkable(node.x, node.y, 15, 3, sdk.collision.BlockWalk); - - if (adjustedNode) { - [node.x, node.y] = [adjustedNode[0], adjustedNode[1]]; - invalidCheck && (invalidCheck = false); - } - - annoyingArea && ([settings.clearSettings.overrideConfig, settings.clearSettings.range] = [true, 5]); - settings.retry <= 3 && !useTeleport && (settings.retry = 15); - } - - if (useTeleport && tpMana <= me.mp ? this.teleportTo(node.x, node.y) : this.walkTo(node.x, node.y, (fail > 0 || me.inTown) ? 2 : 4)) { - if (!me.inTown) { - if (this.recursion) { - this.recursion = false; - try { - NodeAction.go(settings.clearSettings); - node.distance > 5 && this.moveTo(node.x, node.y); - } finally { - this.recursion = true; - } - } - - settings.allowTown && Misc.townCheck(); - } - } else { - if (!me.inTown) { - if (!useTeleport && settings.allowClearing && me.checkForMobs({range: 10}) && Attack.clear(10)) { - console.debug("Cleared Node"); - continue; - } - if (!useTeleport && (this.kickBarrels(node.x, node.y) || this.openDoors(node.x, node.y))) { - continue; - } - - if (fail > 0 && (!useTeleport || tpMana > me.mp)) { - // Don't go berserk on longer paths - if (settings.allowClearing && !cleared && me.checkForMobs({range: 10}) && Attack.clear(10)) { - console.debug("Cleared Node"); - cleared = true; - } - - // Only do this once - if (!leaped && Skill.canUse(sdk.skills.LeapAttack) && Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, node.x, node.y)) { - leaped = true; - } - } - } - - // Reduce node distance in new path - path = getPath(me.area, target.x, target.y, me.x, me.y, useTeleport ? 1 : 0, useTeleport ? rand(25, 35) : rand(10, 15)); - if (!path) throw new Error("move: Failed to generate path."); - - path.reverse(); - PathDebug.drawPath(path); - settings.pop && path.pop(); - - if (fail > 0) { - console.debug("move retry " + fail); - Packet.flash(me.gid); - - if (fail >= settings.retry) { - console.log("Failed move: Retry = " + settings.retry); - break; - } - } - fail++; - } - } - - delay(5); - } - - useTeleport && Config.TeleSwitch && me.switchWeapons(Attack.getPrimarySlot() ^ 1); - PathDebug.removeHooks(); - - return getDistance(me, node.x, node.y) < 5; - }, - - moveNear: function (x, y, minDist, givenSettings = {}) { - return Pather.move({x: x, y: y}, Object.assign({minDist: minDist}, givenSettings)); - }, - - /* - Pather.moveTo(x, y, retry, clearPath, pop); - x - the x coord to move to - y - the y coord to move to - retry - number of attempts before aborting - clearPath - kill monsters while moving - pop - remove last node - */ - moveTo: function (x, y, retry, clearPath = false, pop = false) { - return Pather.move({x: x, y: y}, {retry: retry, pop: pop, allowClearing: clearPath}); - }, - - moveToEx: function (x, y, givenSettings = {}) { - return Pather.move({x: x, y: y}, givenSettings); - }, - - /* - Pather.teleportTo(x, y); - x - the x coord to teleport to - y - the y coord to teleport to - */ - // does this need a validLocation check? - maybe if we fail once check the spot - teleportTo: function (x, y, maxRange = 5) { - for (let i = 0; i < 3; i += 1) { - Config.PacketCasting > 0 ? Packet.teleport(x, y) : Skill.cast(sdk.skills.Teleport, sdk.skills.hand.Right, x, y); - let tick = getTickCount(); - let pingDelay = i === 0 ? 150 : me.getPingDelay(); - - while (getTickCount() - tick < Math.max(500, pingDelay * 2 + 200)) { - if ([x, y].distance < maxRange) { - return true; - } - - delay(10); - } - } - - return false; - }, - - /* - Pather.walkTo(x, y); - x - the x coord to walk to - y - the y coord to walk to - minDist - minimal distance from x/y before returning true - */ - walkTo: function (x, y, minDist) { - while (!me.gameReady) { - delay(100); - } - - if (x === undefined || y === undefined || me.dead) return false; - minDist === undefined && (minDist = me.inTown ? 2 : 4); - - let nTimer; - let [nFail, attemptCount] = [0, 0]; - - // credit @Jaenster - // Stamina handler and Charge - if (!me.inTown) { - // Check if I have a stamina potion and use it if I do - if (me.staminaPercent <= 20) { - let stam = me.getItemsEx(-1, sdk.items.mode.inStorage).filter((i) => i.classid === sdk.items.StaminaPotion && i.isInInventory).first(); - !!stam && !me.deadOrInSequence && stam.use(); - } - (me.running && me.staminaPercent <= 15) && me.walk(); - // the less stamina you have, the more you wait to recover - let recover = me.staminaMaxDuration < 30 ? 80 : 50; - (me.walking && me.staminaPercent >= recover) && me.run(); - if (Skill.canUse(sdk.skills.Charge) && me.paladin && me.mp >= 9 && getDistance(me.x, me.y, x, y) > 8 && Skill.setSkill(sdk.skills.Charge, sdk.skills.hand.Left)) { - if (Skill.canUse(sdk.skills.Vigor)) { - Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right); - } else if (!Config.Vigor && !Attack.auradin && Skill.canUse(sdk.skills.HolyFreeze)) { - // Useful in classic to keep mobs cold while you rush them - Skill.setSkill(sdk.skills.HolyFreeze, sdk.skills.hand.Right); - } - Misc.click(0, 1, x, y); - while (!me.idle) { - delay(40); - } - } - } else { - me.walking && me.run(); - Skill.canUse(sdk.skills.Vigor) && Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right); - } - - MainLoop: - while (getDistance(me.x, me.y, x, y) > minDist && !me.dead) { - if (me.paladin && !me.inTown) { - Skill.canUse(sdk.skills.Vigor) ? Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right) : Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); - } - - if (this.openDoors(x, y) && getDistance(me.x, me.y, x, y) <= minDist) { - return true; - } - - if (attemptCount > 1 && CollMap.checkColl(me, {x: x, y: y}, sdk.collision.BlockWall | sdk.collision.ClosedDoor)) { - this.openDoors(me.x, me.y); - } - - Misc.click(0, 0, x, y); - - attemptCount += 1; - nTimer = getTickCount(); - - while (!me.moving) { - if (me.dead) return false; - - if ((getTickCount() - nTimer) > 500) { - if (nFail >= 3) { - break MainLoop; - } - - nFail += 1; - let angle = Math.atan2(me.y - y, me.x - x); - let angles = [Math.PI / 2, -Math.PI / 2]; - - for (let i = 0; i < angles.length; i += 1) { - // TODO: might need rework into getnearestwalkable - let whereToClick = { - x: Math.round(Math.cos(angle + angles[i]) * 5 + me.x), - y: Math.round(Math.sin(angle + angles[i]) * 5 + me.y) - }; - - if (Attack.validSpot(whereToClick.x, whereToClick.y)) { - Misc.click(0, 0, whereToClick.x, whereToClick.y); - - let tick = getTickCount(); - - while (getDistance(me, whereToClick) > 2 && getTickCount() - tick < 1000) { - delay(40); - } - - break; - } - } - - break; - } - - delay(10); - } - - attemptCount > 1 && this.kickBarrels(x, y); - - // Wait until we're done walking - idle or dead - while (getDistance(me.x, me.y, x, y) > minDist && !me.idle) { - delay(10); - } - - if (attemptCount >= 3) { - break; - } - } - return (!me.dead && getDistance(me.x, me.y, x, y) <= minDist); - }, - - /* - Pather.openDoors(x, y); - x - the x coord of the node close to the door - y - the y coord of the node close to the door - */ - openDoors: function (x, y) { - if (me.inTown && me.act !== 5) return false; - - (typeof x !== "number" || typeof y !== "number") && ({x, y} = me); - - // Regular doors - let door = Game.getObject("door", sdk.objects.mode.Inactive); - - if (door) { - do { - if ((getDistance(door, x, y) < 4 && door.distance < 9) || door.distance < 4) { - for (let i = 0; i < 3; i++) { - Misc.click(0, 0, door); - - if (Misc.poll(() => door.mode === sdk.objects.mode.Active, 1000, 30)) { - return true; - } - - i === 2 && Packet.flash(me.gid); - } - } - } while (door.getNext()); - } - - // handle act 5 gate - if ([sdk.areas.Harrogath, sdk.areas.BloodyFoothills].includes(me.area)) { - let gate = Game.getObject("gate", sdk.objects.mode.Inactive); - - if (gate) { - if ((getDistance(gate, x, y) < 4 && gate.distance < 9) || gate.distance < 4) { - for (let i = 0; i < 3; i++) { - Misc.click(0, 0, gate); - - if (Misc.poll(() => gate.mode, 1000, 50)) { - return true; - } - - i === 2 && Packet.flash(me.gid); - } - } - } - } - - // Monsta doors (Barricaded) - not sure if this is really needed anymore - let monstadoor = Game.getMonster("barricaded door"); - - if (monstadoor) { - do { - if (monstadoor.hp > 0 && (getDistance(monstadoor, x, y) < 4 && monstadoor.distance < 9) || monstadoor.distance < 4) { - for (let p = 0; p < 20 && monstadoor.hp; p++) { - Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), monstadoor); - } - } - } while (monstadoor.getNext()); - } - - let monstawall = Game.getMonster("barricade"); - - if (monstawall) { - do { - if (monstawall.hp > 0 && (getDistance(monstawall, x, y) < 4 && monstawall.distance < 9) || monstawall.distance < 4) { - for (let p = 0; p < 20 && monstawall.hp; p++) { - Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), monstawall); - } - } - } while (monstawall.getNext()); - } - - return false; - }, - - /* - Pather.kickBarrels(x, y); - x - the x coord of the node close to the barrel - y - the y coord of the node close to the barrel - */ - kickBarrels: function (x, y) { - if (me.inTown) return false; - - (typeof x !== "number" || typeof y !== "number") && ({x, y} = me); - - // anything small and annoying really - let barrels = getUnits(sdk.unittype.Object) - .filter(function (el) { - return (el.name && el.mode === sdk.objects.mode.Inactive - && ["ratnest", "goo pile", "barrel", "basket", "largeurn", "jar3", "jar2", "jar1", "urn", "jug", "barrel wilderness", "cocoon"].includes(el.name.toLowerCase()) - && ((getDistance(el, x, y) < 4 && el.distance < 9) || el.distance < 4)); - }); - let brokeABarrel = false; - - while (barrels.length > 0) { - barrels.sort(Sort.units); - let unit = barrels.shift(); - - if (unit && !checkCollision(me, unit, sdk.collision.WallOrRanged)) { - try { - for (let i = 0; i < 5; i++) { - i < 3 ? Packet.entityInteract(unit) : Misc.click(0, 0, unit); - - if (unit.mode) { - brokeABarrel = true; - break; - } - } - } catch (e) { - continue; - } - } - } - - return brokeABarrel; - }, - - /* - Pather.moveToUnit(unit, offX, offY, clearPath, pop); - unit - a valid Unit or PresetUnit object - offX - offset from unit's x coord - offY - offset from unit's x coord - clearPath - kill monsters while moving - pop - remove last node - */ - moveToUnit: function (unit, offX, offY, clearPath, pop) { - const useTeleport = this.useTeleport(); - - offX === undefined && (offX = 0); - offY === undefined && (offY = 0); - clearPath === undefined && (clearPath = false); - pop === undefined && (pop = false); - - if (!unit || !unit.hasOwnProperty("x") || !unit.hasOwnProperty("y")) throw new Error("moveToUnit: Invalid unit."); - - (unit instanceof PresetUnit) && (unit = { x: unit.roomx * 5 + unit.x, y: unit.roomy * 5 + unit.y }); - - if (!useTeleport) { - // The unit will most likely be moving so call the first walk with 'pop' parameter - this.moveTo(unit.x + offX, unit.y + offY, 0, clearPath, true); - } - - return this.moveTo(unit.x + offX, unit.y + offY, useTeleport && unit.type && unit.isMonster ? 3 : 0, clearPath, pop); - }, - - moveNearUnit: function (unit, minDist, clearPath, pop = false) { - const useTeleport = this.useTeleport(); - minDist === undefined && (minDist = me.inTown ? 2 : 5); - - if (!unit || !unit.hasOwnProperty("x") || !unit.hasOwnProperty("y")) throw new Error("moveNearUnit: Invalid unit."); - - (unit instanceof PresetUnit) && (unit = { x: unit.roomx * 5 + unit.x, y: unit.roomy * 5 + unit.y }); - - if (!useTeleport) { - // The unit will most likely be moving so call the first walk with 'pop' parameter - this.moveNear(unit.x, unit.y, minDist, { clearSettings: { clearPath: clearPath }, pop: true }); - } - - return this.moveNear(unit.x, unit.y, minDist, { clearSettings: { clearPath: clearPath }, pop: pop }); - }, - - moveNearPreset: function (area, unitType, unitId, minDist, clearPath = false, pop = false) { - if (area === undefined || unitType === undefined || unitId === undefined) { - throw new Error("moveNearPreset: Invalid parameters."); - } - - me.area !== area && Pather.journeyTo(area); - let presetUnit = getPresetUnit(area, unitType, unitId); - - if (!presetUnit) { - throw new Error("moveNearPreset: Couldn't find preset unit - id: " + unitId + " unitType: " + unitType + " in area: " + this.getAreaName(area)); - } - - delay(40); - Misc.poll(() => me.gameReady, 500, 100); - - let unit = presetUnit.realCoords(); - - return this.moveNear(unit.x, unit.y, minDist, { clearSettings: { clearPath: clearPath }, pop: pop }); - }, - - /* - Pather.moveToPreset(area, unitType, unitId, offX, offY, clearPath, pop); - area - area of the preset unit - unitType - type of the preset unit - unitId - preset unit id - offX - offset from unit's x coord - offY - offset from unit's x coord - clearPath - kill monsters while moving - pop - remove last node - */ - moveToPreset: function (area, unitType, unitId, offX, offY, clearPath, pop) { - if (area === undefined || unitType === undefined || unitId === undefined) { - throw new Error("moveToPreset: Invalid parameters."); - } - - offX === undefined && (offX = 0); - offY === undefined && (offY = 0); - clearPath === undefined && (clearPath = false); - pop === undefined && (pop = false); - - me.area !== area && Pather.journeyTo(area); - let presetUnit = getPresetUnit(area, unitType, unitId); - - if (!presetUnit) { - throw new Error("moveToPreset: Couldn't find preset unit - id: " + unitId + " unitType: " + unitType + " in area: " + this.getAreaName(area)); - } - - delay(40); - Misc.poll(() => me.gameReady, 500, 100); - - return this.moveTo(presetUnit.roomx * 5 + presetUnit.x + offX, presetUnit.roomy * 5 + presetUnit.y + offY, 3, clearPath, pop); - }, - - /* - Pather.moveToExit(targetArea, use, clearPath); - targetArea - area id or array of area ids to move to - use - enter target area or last area in the array - clearPath - kill monsters while moving - */ - moveToExit: function (targetArea, use = false, clearPath = false) { - if (targetArea === undefined) return false; - - console.time("moveToExit"); - console.info(true, "ÿc7MyArea: ÿc0" + Pather.getAreaName(me.area) + " ÿc7TargetArea: ÿc0" + Pather.getAreaName(targetArea)); - let areas = Array.isArray(targetArea) ? targetArea : [targetArea]; - let finalDest = areas.last(); - - for (let i = 0; i < areas.length; i += 1) { - if (me.area === areas[i]) { - console.debug("Already in: " + Pather.getAreaName(areas[i])); - continue; - } - - console.info(null, "ÿc0Moving from: " + Pather.getAreaName(me.area) + " to " + Pather.getAreaName(areas[i])); - - Config.DebugMode && console.time("getArea"); - let area = Misc.poll(() => getArea(me.area)); - Config.DebugMode && console.timeEnd("getArea"); - - if (!area) throw new Error("moveToExit: error in getArea()"); - - let t2 = getTickCount(); - let currTarget = areas[i]; - let exits = (area.exits || []); - let checkExits = []; - - if (!exits.length) return false; - Config.DebugMode && console.log("Took: " + (getTickCount() - t2) + " to assign vars"); - - let t3 = getTickCount(); - for (let j = 0; j < exits.length; j += 1) { - if (!exits[j].hasOwnProperty("target") || exits[j].target !== currTarget) continue; - Config.DebugMode && console.debug(exits[j]); - let currCheckExit = { - x: exits[j].x, - y: exits[j].y, - type: exits[j].type, - target: exits[j].target, - tileid: exits[j].tileid - }; - - currCheckExit.target === currTarget && checkExits.push(currCheckExit); - } - Config.DebugMode && console.log("Took: " + (getTickCount() - t3) + " to find all exits"); - - if (checkExits.length > 0) { - Config.DebugMode && console.debug(checkExits); - let t4 = getTickCount(); - // if there are multiple exits to the same location find the closest one - let currExit = checkExits.length > 1 - ? (() => { - let useExit = checkExits.shift(); // assign the first exit as a possible result - let dist = getDistance(me.x, me.y, useExit.x, useExit.y); - while (checkExits.length > 0) { - let exitDist = getDistance(me.x, me.y, checkExits[0].x, checkExits[0].y); - if (exitDist < dist) { - useExit = checkExits[0]; - dist = exitDist; - } - checkExits.shift(); - } - return useExit; - //checkExits.sort((a, b) => getDistance(me.x, me.y, a.x, a.y) - getDistance(me.x, me.y, b.x, b.y)).first() - })() - : checkExits[0]; - Config.DebugMode && console.log("Took: " + (getTickCount() - t4) + " to pick exit", currExit); - let t5 = getTickCount(); - let dest = this.getNearestWalkable(currExit.x, currExit.y, 5, 1); - Config.DebugMode && console.log("Took: " + (getTickCount() - t5) + " to find nearest walkable"); - - if (!dest) return false; - - for (let retry = 0; retry < 3; retry++) { - if (this.moveTo(dest[0], dest[1], 3, clearPath)) { - break; - } - - delay(200); - console.log("ÿc7(moveToExit) :: ÿc0Retry: " + (retry + 1)); - Misc.poll(() => me.gameReady, 1000, 200); - } - - /* i < areas.length - 1 is for crossing multiple areas. - In that case we must use the exit before the last area. - */ - if (use || i < areas.length - 1) { - switch (currExit.type) { - case 1: // walk through - let targetRoom = this.getNearestRoom(areas[i]); - - if (targetRoom) { - this.moveTo(targetRoom[0], targetRoom[1]); - } else { - // might need adjustments - return false; - } - - break; - case 2: // stairs - if (!this.openExit(areas[i]) && !this.useUnit(sdk.unittype.Stairs, currExit.tileid, areas[i])) { - return false; - } - - break; - } - } - } else { - // journey there? - console.warn("Something broke"); - } - } - - console.info(false, "ÿc7targetArea: ÿc0" + this.getAreaName(finalDest) + " ÿc7myArea: ÿc0" + this.getAreaName(me.area), "moveToExit"); - delay(300); - - return (use && finalDest ? me.area === finalDest : true); - }, - - getDistanceToExit: function (area, exit) { - area === undefined && (area = me.area); - exit === undefined && (exit = me.area + 1); - let areaToCheck = Misc.poll(() => getArea(area)); - if (!areaToCheck) throw new Error("Couldn't get area info for " + Pather.getAreaName(area)); - let exits = areaToCheck.exits; - if (!exits.length) throw new Error("Failed to find exits"); - let loc = exits.find(a => a.target === exit); - console.debug(area, exit, loc); - return loc ? [loc.x, loc.y].distance : Infinity; - }, - - getExitCoords: function (area, exit) { - area === undefined && (area = me.area); - exit === undefined && (exit = me.area + 1); - let areaToCheck = Misc.poll(() => getArea(area)); - if (!areaToCheck) throw new Error("Couldn't get area info for " + Pather.getAreaName(area)); - let exits = areaToCheck.exits; - if (!exits.length) throw new Error("Failed to find exits"); - let loc = exits.find(a => a.target === exit); - console.debug(area, exit, loc); - return loc ? {x: loc.x, y: loc.y} : false; - }, - - /* - Pather.getNearestRoom(area); - area - the id of area to search for the room nearest to the player character - */ - getNearestRoom: function (area) { - let startTick = getTickCount(); - let x, y, minDist = 10000; - - let room = Misc.poll(() => getRoom(area), 1000, 200); - if (!room) return false; - - do { - let dist = getDistance(me, room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2); - - if (dist < minDist) { - x = room.x * 5 + room.xsize / 2; - y = room.y * 5 + room.ysize / 2; - minDist = dist; - } - } while (room.getNext()); - - room = getRoom(area, x, y); - !!Config.DebugMode && console.log(room); - - if (room) { - CollMap.addRoom(room); - - !!Config.DebugMode && console.log("ÿc7End ÿc8(getNearestOld) ÿc0 - ÿc7Duration: ÿc0" + (getTickCount() - startTick)); - return this.getNearestWalkable(x, y, 20, 4); - } - - !!Config.DebugMode && console.log("ÿc7End ÿc8(getNearestOld) ÿc0 - ÿc7Duration: ÿc0" + (getTickCount() - startTick)); - return [x, y]; - }, - - /* - Pather.openExit(targetArea); - targetArea - area id of where the unit leads to - */ - openExit: function (targetArea) { - switch (true) { - case targetArea === sdk.areas.AncientTunnels: - case targetArea === sdk.areas.A2SewersLvl1 && !(me.inArea(sdk.areas.LutGholein) && [5218, 5180].distance < 20): - return this.useUnit(sdk.unittype.Object, sdk.objects.TrapDoorA2, targetArea); - case targetArea === sdk.areas.A3SewersLvl2: - return this.useUnit(sdk.unittype.Object, sdk.objects.SewerStairsA3, targetArea); - case targetArea === sdk.areas.RuinedTemple: - case targetArea === sdk.areas.DisusedFane: - case targetArea === sdk.areas.ForgottenReliquary: - case targetArea === sdk.areas.ForgottenTemple: - case targetArea === sdk.areas.RuinedFane: - case targetArea === sdk.areas.DisusedReliquary: - return this.useUnit(sdk.unittype.Object, "stair", targetArea); - case targetArea === sdk.areas.DuranceOfHateLvl1 && me.inArea(sdk.areas.Travincal): - return this.useUnit(sdk.unittype.Object, sdk.objects.DuranceEntryStairs, targetArea); - case targetArea === sdk.areas.WorldstoneLvl1 && me.inArea(sdk.areas.ArreatSummit): - return this.useUnit(sdk.unittype.Object, sdk.objects.AncientsDoor, targetArea); - } - - return false; - }, - - /* - Pather.openUnit(id); - type - type of the unit to open - id - id of the unit to open - */ - openUnit: function (type, id) { - let unit = Misc.poll(() => getUnit(type, id), 1000, 200); - if (!unit) throw new Error("openUnit: Unit not found. ID: " + unit); - if (unit.mode !== sdk.objects.mode.Inactive) return true; - - for (let i = 0; i < 3; i += 1) { - unit.distance > 5 && this.moveToUnit(unit); - - delay(300); - Packet.entityInteract(unit); - - if (Misc.poll(() => unit.mode !== sdk.objects.mode.Inactive, 2000, 60)) { - delay(100); - - return true; - } - - let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 3); - !!coord && this.moveTo(coord.x, coord.y); - } - - return false; - }, - - /* - Pather.useUnit(type, id, targetArea); - type - type of the unit to use - id - id of the unit to use - targetArea - area id of where the unit leads to - */ - // should use an object as param, or be changed to able to take an already found unit as a param - useUnit: function (type, id, targetArea) { - let unit = Misc.poll(() => getUnit(type, id), 2000, 200); - let preArea = me.area; - - if (!unit) { - throw new Error("useUnit: Unit not found. TYPE: " + type + " ID: " + id + " MyArea: " + this.getAreaName(me.area) + (!!targetArea ? " TargetArea: " + Pather.getAreaName(targetArea) : "")); - } - - for (let i = 0; i < 5; i += 1) { - let usetk = (i < 2 && Skill.useTK(unit)); - - if (unit.distance > 5) { - usetk ? this.moveNearUnit(unit, 20) : this.moveToUnit(unit); - // try to activate it once - usetk && i === 0 && unit.mode === sdk.objects.mode.Inactive && unit.distance < 21 && Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit); - } - - if (type === sdk.unittype.Object && unit.mode === sdk.objects.mode.Inactive) { - if ((me.inArea(sdk.areas.Travincal) && targetArea === sdk.areas.DuranceofHateLvl1 && me.getQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed) !== 1) - || (me.inArea(sdk.areas.ArreatSummit) && targetArea === sdk.areas.WorldstoneLvl1 && me.getQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed) !== 1)) { - throw new Error("useUnit: Incomplete quest." + (!!targetArea ? " TargetArea: " + Pather.getAreaName(targetArea) : "")); - } - - me.inArea(sdk.areas.A3SewersLvl1) ? this.openUnit(sdk.unittype.Object, sdk.objects.SewerLever) : this.openUnit(sdk.unittype.Object, id); - } - - if (type === sdk.unittype.Object && id === sdk.objects.RedPortalToAct4 && me.inArea(sdk.areas.DuranceofHateLvl3) - && targetArea === sdk.areas.PandemoniumFortress && me.getQuest(sdk.quest.id.TheGuardian, sdk.quest.states.Completed) !== 1) { - throw new Error("useUnit: Incomplete quest." + (!!targetArea ? " TargetArea: " + Pather.getAreaName(targetArea) : "")); - } - - delay(300); - type === sdk.unittype.Stairs - ? Misc.click(0, 0, unit) - : usetk && unit.distance > 5 ? Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit) : Packet.entityInteract(unit); - delay(300); - - let tick = getTickCount(); - - while (getTickCount() - tick < 3000) { - if ((!targetArea && me.area !== preArea) || me.area === targetArea) { - delay(200); - //Packet.flash(me.gid); - - return true; - } - - delay(10); - } - - i > 2 && Packet.flash(me.gid); - let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 3); - !!coord && this.moveTo(coord.x, coord.y); - } - - return targetArea ? me.area === targetArea : me.area !== preArea; - }, - - /* - Pather.useUnitEx(givenSettings = {}); - optional - use either or - givenSettings.unit - defined unit thats been found - or - givenSettings.type - type of the unit to use - givenSettings.id - id of the unit to use - targetArea - area id of where the unit leads to - */ - useUnitEx: function (givenSettings = {}, targetArea = undefined) { - let settings = Object.assign({}, { - unit: undefined, - type: undefined, - id: undefined, - }, givenSettings); - let unit = settings.unit ? settings.unit : (settings.type || settings.id) ? Misc.poll(() => getUnit(settings.type, settings.id), 2000, 200) : null; - - if (!unit) { - throw new Error( - "useUnit: Unit not found. TYPE: " + (settings.type ? settings.type : "") - + " ID: " + (settings.id ? settings.id : "") - + " MyArea: " + this.getAreaName(me.area) + (!!targetArea ? " TargetArea: " + Pather.getAreaName(targetArea) : "") - ); - } - - let preArea = me.area; - let targetAreaCheck = (unit.objtype || 0); - targetArea === undefined && targetAreaCheck > 0 && (targetArea = targetAreaCheck); - let type = unit.type; - let id = unit.classid; - - for (let i = 0; i < 5; i += 1) { - let usetk = (i < 2 && Skill.useTK(unit)); - - if (unit.distance > 5) { - usetk ? this.moveNearUnit(unit, 20) : this.moveToUnit(unit); - // try to activate it once - usetk && i === 0 && unit.mode === sdk.objects.mode.Inactive && unit.distance < 21 && Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit); - } - - if (type === sdk.unittype.Object && unit.mode === sdk.objects.mode.Inactive) { - if ((me.inArea(sdk.areas.Travincal) && targetArea === sdk.areas.DuranceofHateLvl1 && me.getQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed) !== 1) - || (me.inArea(sdk.areas.ArreatSummit) && targetArea === sdk.areas.WorldstoneLvl1 && me.getQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed) !== 1)) { - throw new Error("useUnit: Incomplete quest." + (!!targetArea ? " TargetArea: " + Pather.getAreaName(targetArea) : "")); - } - - me.inArea(sdk.areas.A3SewersLvl1) ? this.openUnit(sdk.unittype.Object, sdk.objects.SewerLever) : this.openUnit(sdk.unittype.Object, id); - } - - if (type === sdk.unittype.Object && id === sdk.objects.RedPortalToAct4 && me.inArea(sdk.areas.DuranceofHateLvl3) - && targetArea === sdk.areas.PandemoniumFortress && me.getQuest(sdk.quest.id.TheGuardian, sdk.quest.states.Completed) !== 1) { - throw new Error("useUnit: Incomplete quest." + (!!targetArea ? " TargetArea: " + Pather.getAreaName(targetArea) : "")); - } - - delay(300); - type === 5 ? Misc.click(0, 0, unit) : usetk && unit.distance > 5 ? Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit) : Packet.entityInteract(unit); - delay(300); - - let tick = getTickCount(); - - while (getTickCount() - tick < 3000) { - if ((!targetArea && me.area !== preArea) || me.area === targetArea) { - delay(200); - - return true; - } - - delay(10); - } - - i > 2 && Packet.flash(me.gid); - let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 3); - !!coord && this.moveTo(coord.x, coord.y); - } - - return targetArea ? me.area === targetArea : me.area !== preArea; - }, - - /* - Pather.broadcastIntent(targetArea); - targetArea - area id - */ - broadcastIntent: function broadcastIntent(targetArea) { - if (Config.MFLeader) { - let targetAct = sdk.areas.actOf(targetArea); - me.act !== targetAct && say("goto A" + targetAct); - } - }, - - /* - Pather.moveTo(targetArea, check); - targetArea - id of the area to enter - check - force the waypoint menu - */ - useWaypoint: function useWaypoint(targetArea, check = false) { - switch (targetArea) { - case undefined: - throw new Error("useWaypoint: Invalid targetArea parameter: " + targetArea); - case null: - case "random": - check = true; - - break; - default: - if (typeof targetArea !== "number") throw new Error("useWaypoint: Invalid targetArea parameter"); - if (this.wpAreas.indexOf(targetArea) < 0) throw new Error("useWaypoint: Invalid area"); - - break; - } - - this.broadcastIntent(targetArea); - console.info(true, "ÿc7targetArea: ÿc0" + this.getAreaName(targetArea) + " ÿc7myArea: ÿc0" + this.getAreaName(me.area)); - let wpTick = getTickCount(); - - for (let i = 0; i < 12; i += 1) { - if (me.area === targetArea || me.dead) { - break; - } - - if (me.inTown) { - if (me.inArea(sdk.areas.LutGholein)) { - let npc = Game.getNPC(NPC.Warriv); - - if (!!npc && npc.distance < 50) { - if (npc && npc.openMenu()) { - Misc.useMenu(sdk.menu.GoWest); - - if (!Misc.poll(() => me.gameReady && me.inArea(sdk.areas.RogueEncampment), 2000, 100)) { - throw new Error("Failed to go to act 1 using Warriv"); - } - } - } - } - - !getUIFlag(sdk.uiflags.Waypoint) && Town.getDistance("waypoint") > (Skill.haveTK ? 20 : 5) && Town.move("waypoint"); - } - - let wp = Game.getObject("waypoint"); - - if (!!wp && wp.area === me.area) { - let useTK = (Skill.useTK(wp) && i < 3); - let pingDelay = me.getPingDelay(); - - if (useTK && !getUIFlag(sdk.uiflags.Waypoint)) { - wp.distance > 21 && Pather.moveNearUnit(wp, 20); - Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, wp); - } else if (!me.inTown && wp.distance > 7) { - this.moveToUnit(wp); - } - - if (check || Config.WaypointMenu || !this.initialized) { - if (!useTK && (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint))) { - this.moveToUnit(wp) && Misc.click(0, 0, wp); - } - - // handle getUnit bug - if (me.inTown && !getUIFlag(sdk.uiflags.Waypoint) && wp.name.toLowerCase() === "dummy") { - Town.getDistance("waypoint") > 5 && Town.move("waypoint"); - Misc.click(0, 0, wp); - } - - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), pingDelay * 2)) { - // Waypoint screen is open - if (getUIFlag(sdk.uiflags.Waypoint)) { - delay(500); - !this.initialized && (this.initialized = true); - - switch (targetArea) { - case "random": - while (true) { - targetArea = this.nonTownWpAreas[rand(0, this.nonTownWpAreas.length - 1)]; - - // get a valid wp, avoid towns - if (getWaypoint(this.wpAreas.indexOf(targetArea))) { - break; - } - - delay(5); - } - - break; - case null: - me.cancel(); - - return true; - } - - if (!getWaypoint(this.wpAreas.indexOf(targetArea))) { - me.cancel(); - console.log("Trying to get the waypoint: " + this.getAreaName(targetArea)); - me.overhead("Trying to get the waypoint"); - - if (Pather.getWP(targetArea)) { - return true; - } - - throw new Error("Pather.useWaypoint: Failed to go to waypoint " + this.getAreaName(targetArea)); - } - - break; - } - - delay(10); - } - - if (!getUIFlag(sdk.uiflags.Waypoint)) { - console.warn("waypoint retry " + (i + 1)); - let retry = Math.min(i + 1, 5); - let coord = CollMap.getRandCoordinate(me.x, -5 * retry, 5 * retry, me.y, -5 * retry, 5 * retry); - !!coord && this.moveTo(coord.x, coord.y); - delay(200); - Packet.flash(me.gid, pingDelay); - - continue; - } - } - - if (!check || getUIFlag(sdk.uiflags.Waypoint)) { - delay(200); - wp.interact(targetArea); - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), pingDelay * 2)) { - if (me.area === targetArea) { - delay((1500 + (pingDelay * i))); - console.info(false, "ÿc7targetArea: ÿc0" + this.getAreaName(targetArea) + " ÿc7myArea: ÿc0" + this.getAreaName(me.area) + "ÿc0 - ÿc7Duration: ÿc0" + (Time.format(getTickCount() - wpTick))); - - return true; - } - - delay(20); - } - - while (!me.gameReady) { - delay(1000); - } - - // In case lag causes the wp menu to stay open - Misc.poll(() => me.gameReady, 2000, 100) && getUIFlag(sdk.uiflags.Waypoint) && me.cancelUIFlags(); - } - - Packet.flash(me.gid, pingDelay); - // Activate check if we fail direct interact twice - i > 1 && (check = true); - } else { - Packet.flash(me.gid); - } - - // We can't seem to get the wp maybe attempt portal to town instead and try to use that wp - i >= 10 && !me.inTown && Town.goToTown(); - delay(200); - } - - if (me.area === targetArea) { - // delay to allow act to init - helps with crashes - delay(500); - console.info(false, "ÿc7targetArea: ÿc0" + this.getAreaName(targetArea) + " ÿc7myArea: ÿc0" + this.getAreaName(me.area) + "ÿc0 - ÿc7Duration: ÿc0" + (Time.format(getTickCount() - wpTick))); - - return true; - } - - throw new Error("useWaypoint: Failed to use waypoint"); - }, - - /* - Pather.makePortal(use); - use - use the portal that was made - */ - makePortal: function (use = false) { - if (me.inTown) return true; - - let oldGid; - - for (let i = 0; i < 5; i += 1) { - if (me.dead) return false; - - let tpTool = Town.getTpTool(); - let pingDelay = i === 0 ? 100 : me.gameReady ? (me.ping + 25) : 350; - if (!tpTool) return false; - - let oldPortal = getUnits(sdk.unittype.Object, "portal") - .filter((p) => p.getParent() === me.name) - .first(); - - !!oldPortal && (oldGid = oldPortal.gid); - - if (tpTool.use() || Game.getObject("portal")) { - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(500 + i * 100, pingDelay * 2 + 100)) { - let portal = getUnits(sdk.unittype.Object, "portal") - .filter((p) => p.getParent() === me.name && p.gid !== oldGid) - .first(); - - if (!!portal) { - if (use) { - if (this.usePortal(null, null, copyUnit(portal))) { - return true; - } - break; // don't spam usePortal - } else { - return copyUnit(portal); - } - } - - delay(10); - } - } else { - console.log("Failed to use tp tool"); - Packet.flash(me.gid, pingDelay); - delay(200); - } - - delay(40); - } - - return false; - }, - - /* - Pather.usePortal(targetArea, owner, unit); - targetArea - id of the area the portal leads to - owner - name of the portal's owner - unit - use existing portal unit - */ - usePortal: function (targetArea, owner, unit) { - // while (!me.gameReady) { - // delay(30); - // } - - if (targetArea && me.area === targetArea) return true; - - me.cancelUIFlags(); - - let preArea = me.area; - - for (let i = 0; i < 10; i += 1) { - if (me.dead) return false; - i > 0 && owner && me.inTown && Town.move("portalspot"); - - let portal = unit ? copyUnit(unit) : this.getPortal(targetArea, owner); - - if (portal) { - let redPortal = portal.classid === sdk.objects.RedPortal; - - if (portal.area === me.area) { - if (Skill.useTK(portal) && i < 3) { - portal.distance > 21 && (me.inArea(sdk.areas.Harrogath) ? Town.move("portalspot") : Pather.moveNearUnit(portal, 20)); - if (Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, portal)) { - if (Misc.poll(() => { - if (me.area !== preArea) { - Pather.lastPortalTick = getTickCount(); - delay(100); - - return true; - } - - return false; - }, 500, 50)) { - return true; - } - } - } else { - portal.distance > 5 && this.moveToUnit(portal); - - if (getTickCount() - this.lastPortalTick > 2500) { - i < 2 ? Packet.entityInteract(portal) : Misc.click(0, 0, portal); - !!redPortal && delay(150); - } else { - let timeTillNextPortal = Math.max(3, Math.round(2500 - (getTickCount() - this.lastPortalTick))); - delay(timeTillNextPortal); - - continue; - } - } - } - - // Portal to/from Arcane - if (portal.classid === sdk.objects.ArcaneSanctuaryPortal && portal.mode !== sdk.objects.mode.Active) { - Misc.click(0, 0, portal); - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (portal.mode === sdk.objects.mode.Active || me.inArea(sdk.areas.ArcaneSanctuary)) { - break; - } - - delay(10); - } - } - - let tick = getTickCount(); - - while (getTickCount() - tick < 500) { - if (me.area !== preArea) { - this.lastPortalTick = getTickCount(); - delay(100); - - return true; - } - - delay(10); - } - - i > 1 && Packet.flash(me.gid); - } else { - Packet.flash(me.gid); - } - - delay(250); - } - - return (targetArea ? me.area === targetArea : me.area !== preArea); - }, - - /* - Pather.getPortal(targetArea, owner, unit); - targetArea - id of the area the portal leads to - owner - name of the portal's owner - */ - getPortal: function (targetArea, owner) { - let portal = Game.getObject("portal"); - - if (portal) { - do { - if (typeof targetArea !== "number" || portal.objtype === targetArea) { - switch (owner) { - case undefined: // Pather.usePortal(area) - red portal - if (!portal.getParent()) { - return copyUnit(portal); - } - - break; - case null: // Pather.usePortal(area, null) - any blue portal leading to area - if (portal.getParent() === me.name || Misc.inMyParty(portal.getParent())) { - return copyUnit(portal); - } - - break; - default: // Pather.usePortal(null, owner) - any blue portal belonging to owner OR Pather.usePortal(area, owner) - blue portal matching area and owner - if (portal.getParent() === owner && (owner === me.name || Misc.inMyParty(owner))) { - return copyUnit(portal); - } - - break; - } - } - } while (portal.getNext()); - } - - return false; - }, - - /* - Pather.getNearestWalkable(x, y, range, step, coll, size); - x - the starting x coord - y - the starting y coord - range - maximum allowed range from the starting coords - step - distance between each checked dot on the grid - coll - collision flag to avoid - */ - getNearestWalkable: function (x, y, range, step, coll, size) { - !step && (step = 1); - coll === undefined && (coll = sdk.collision.BlockWall); - - let distance = 1; - let result = false; - - // Check if the original spot is valid - if (this.checkSpot(x, y, coll, false, size)) { - result = [x, y]; - } - - MainLoop: - while (!result && distance < range) { - for (let i = -distance; i <= distance; i += 1) { - for (let j = -distance; j <= distance; j += 1) { - // Check outer layer only (skip previously checked) - if (Math.abs(i) >= Math.abs(distance) || Math.abs(j) >= Math.abs(distance)) { - if (this.checkSpot(x + i, y + j, coll, false, size)) { - result = [x + i, y + j]; - - break MainLoop; - } - } - } - } - - distance += step; - } - - CollMap.reset(); - - return result; - }, - - /* - Pather.moveTo(x, y, coll, cacheOnly); - x - the x coord to check - y - the y coord to check - coll - collision flag to search for - cacheOnly - use only cached room data - */ - checkSpot: function (x, y, coll, cacheOnly, size) { - coll === undefined && (coll = sdk.collision.BlockWall); - !size && (size = 1); - - for (let dx = -size; dx <= size; dx += 1) { - for (let dy = -size; dy <= size; dy += 1) { - if (Math.abs(dx) !== Math.abs(dy)) { - let value = CollMap.getColl(x + dx, y + dy, cacheOnly); - - if (value & coll) { - return false; - } - } - } - } - - return true; - }, - - /* - Pather.accessToAct(act); - act - the act number to check for access - */ - accessToAct: function (act) { - switch (act) { - // Act 1 is always accessible - case 1: - return true; - // For the other acts, check the "Able to go to Act *" quests - case 2: - return me.getQuest(sdk.quest.id.AbleToGotoActII, sdk.quest.states.Completed) === 1; - case 3: - return me.getQuest(sdk.quest.id.AbleToGotoActIII, sdk.quest.states.Completed) === 1; - case 4: - return me.getQuest(sdk.quest.id.AbleToGotoActIV, sdk.quest.states.Completed) === 1; - case 5: - return me.expansion && me.getQuest(sdk.quest.id.AbleToGotoActV, sdk.quest.states.Completed) === 1; - default: - return false; - } - }, - - /* - Pather.getWP(area); - area - the id of area to get the waypoint in - clearPath - clear path - */ - getWP: function (area, clearPath) { - area !== me.area && this.journeyTo(area); - - for (let i = 0; i < sdk.waypoints.Ids.length; i++) { - let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); - - if (preset) { - Skill.haveTK ? Pather.moveNearUnit(preset, 20, clearPath) : this.moveToUnit(preset, 0, 0, clearPath); - - let wp = Game.getObject("waypoint"); - - if (wp) { - for (let j = 0; j < 10; j++) { - if (!getUIFlag(sdk.uiflags.Waypoint)) { - if (wp.distance > 5 && Skill.useTK(wp) && j < 3) { - wp.distance > 21 && Attack.getIntoPosition(wp, 20, sdk.collision.Ranged); - Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, wp); - } else if (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint)) { - this.moveToUnit(wp) && Misc.click(0, 0, wp); - } - } - - if (Misc.poll(() => me.gameReady && getUIFlag(sdk.uiflags.Waypoint), 1000, 150)) { - delay(500); - !this.initialized && (this.initialized = true); - me.cancelUIFlags(); - - return true; - } - - // handle getUnit bug - if (!getUIFlag(sdk.uiflags.Waypoint) && me.inTown && wp.name.toLowerCase() === "dummy") { - Town.getDistance("waypoint") > 5 && Town.move("waypoint"); - Misc.click(0, 0, wp); - } - - delay(500); - } - } - } - } - - return false; - }, - - /* - Pather.journeyTo(area); - area - the id of area to move to - */ - journeyTo: function (area) { - if (area === undefined) return false; - console.time("journeyTo"); - - let target, retry = 0; - - if (area !== sdk.areas.DurielsLair) { - target = this.plotCourse(area, me.area); - } else { - target = {course: [sdk.areas.CanyonofMagic, sdk.areas.DurielsLair], useWP: false}; - this.wpAreas.indexOf(me.area) === -1 && (target.useWP = true); - } - - console.info(true, "Course :: " + target.course); - area === sdk.areas.PandemoniumFortress && me.inArea(sdk.areas.DuranceofHateLvl3) && (target.useWP = false); - target.useWP && Town.goToTown(); - - // handle variable flayer jungle entrances - if (target.course.includes(sdk.areas.FlayerJungle)) { - Town.goToTown(3); // without initiated act, getArea().exits will crash - let special = getArea(sdk.areas.FlayerJungle); - - if (special) { - special = special.exits; - - for (let i = 0; i < special.length; i += 1) { - if (special[i].target === sdk.areas.GreatMarsh) { - // add great marsh if needed - target.course.splice(target.course.indexOf(sdk.areas.FlayerJungle), 0, sdk.areas.GreatMarsh); - - break; - } - } - } - } - - while (target.course.length) { - const currArea = me.area; - const targetArea = target.course[0]; - let unit; - - if (currArea === targetArea && target.course.shift()) { - continue; - } - - console.info(null, "ÿc0Moving from: " + Pather.getAreaName(currArea) + " to " + Pather.getAreaName(targetArea)); - - if (!me.inTown) { - Precast.doPrecast(false); - - if (this.wpAreas.includes(currArea) && !getWaypoint(this.wpAreas.indexOf(currArea))) { - this.getWP(currArea); - } - } - - if (me.inTown && this.nextAreas[currArea] !== targetArea && this.wpAreas.includes(targetArea) && getWaypoint(this.wpAreas.indexOf(targetArea))) { - this.useWaypoint(targetArea, !this.plotCourse_openedWpMenu); - Precast.doPrecast(false); - } else if (currArea === sdk.areas.StonyField && targetArea === sdk.areas.Tristram) { - // Stony Field -> Tristram - this.moveToPreset(currArea, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 0, 0, false, true); - Misc.poll(() => this.usePortal(sdk.areas.Tristram), 5000, 1000); - } else if (currArea === sdk.areas.LutGholein && targetArea === sdk.areas.A2SewersLvl1) { - // Lut Gholein -> Sewers Level 1 (use Trapdoor) - this.moveToPreset(currArea, sdk.unittype.Stairs, sdk.exits.preset.A2SewersTrapDoor); - this.useUnit(sdk.unittype.Object, sdk.objects.TrapDoorA2, sdk.areas.A2SewersLvl1); - } else if (currArea === sdk.areas.A2SewersLvl2 && targetArea === sdk.areas.A2SewersLvl1) { - // Sewers Level 2 -> Sewers Level 1 - Pather.moveToExit(targetArea, false); - this.useUnit(sdk.unittype.Stairs, sdk.objects.A2UndergroundUpStairs, sdk.areas.A2SewersLvl1); - } else if (currArea === sdk.areas.PalaceCellarLvl3 && targetArea === sdk.areas.ArcaneSanctuary) { - // Palace -> Arcane - this.moveTo(10073, 8670); - this.usePortal(null); - } else if (currArea === sdk.areas.ArcaneSanctuary && targetArea === sdk.areas.PalaceCellarLvl3) { - // Arcane Sanctuary -> Palace Cellar 3 - Skill.haveTK ? this.moveNearPreset(currArea, sdk.unittype.Object, sdk.objects.ArcaneSanctuaryPortal, 20) : this.moveToPreset(currArea, sdk.unittype.Object, sdk.objects.ArcaneSanctuaryPortal); - unit = Misc.poll(() => Game.getObject(sdk.objects.ArcaneSanctuaryPortal)); - unit && Pather.useUnit(sdk.unittype.Object, sdk.objects.ArcaneSanctuaryPortal, sdk.areas.PalaceCellarLvl3); - } else if (currArea === sdk.areas.ArcaneSanctuary && targetArea === sdk.areas.CanyonofMagic) { - // Arcane Sanctuary -> Canyon of the Magic - this.moveToPreset(currArea, sdk.unittype.Object, sdk.objects.Journal); - unit = Game.getObject(sdk.objects.RedPortal); - - if (!unit || !this.usePortal(null, null, unit)) { - for (let i = 0; i < 5; i++) { - unit = Game.getObject(sdk.objects.Journal); - - Misc.click(0, 0, unit); - delay(1000); - me.cancel(); - - if (this.usePortal(sdk.areas.CanyonofMagic)) { - break; - } - } - } - } else if (currArea === sdk.areas.CanyonofMagic && targetArea === sdk.areas.DurielsLair) { - // Canyon -> Duriels Lair - this.moveToExit(getRoom().correcttomb, true); - this.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.HoradricStaffHolder); - unit = Misc.poll(() => Game.getObject(sdk.objects.PortaltoDurielsLair)); - unit && Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair); - } else if (currArea === sdk.areas.Travincal && targetArea === sdk.areas.DuranceofHateLvl1) { - // Trav -> Durance Lvl 1 - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.DuranceEntryStairs); - this.useUnit(sdk.unittype.Object, sdk.objects.DuranceEntryStairs, sdk.areas.DuranceofHateLvl1); - } else if (currArea === sdk.areas.DuranceofHateLvl3 && targetArea === sdk.areas.PandemoniumFortress) { - // Durance Lvl 3 -> Pandemonium Fortress - if (me.getQuest(sdk.quest.id.TheGuardian, sdk.quest.states.Completed) !== 1) { - console.log(sdk.colors.Red + "(journeyTo) :: Incomplete Quest"); - return false; - } - - Pather.moveTo(17581, 8070); - delay(250 + me.ping * 2); - this.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct4, sdk.areas.PandemoniumFortress); - } else if (currArea === sdk.areas.Harrogath && targetArea === sdk.areas.BloodyFoothills) { - // Harrogath -> Bloody Foothills - this.moveTo(5026, 5095); - this.openUnit(sdk.unittype.Object, sdk.objects.Act5Gate); - this.moveToExit(targetArea, true); - } else if (currArea === sdk.areas.Harrogath && targetArea === sdk.areas.NihlathaksTemple) { - // Harrogath -> Nihlathak's Temple - Town.move(NPC.Anya); - if (!Pather.getPortal(sdk.areas.NihlathaksTemple) && Misc.checkQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.ReqComplete)) { - Town.npcInteract("Anya"); - } - this.usePortal(sdk.areas.NihlathaksTemple); - } else if (currArea === sdk.areas.FrigidHighlands && targetArea === sdk.areas.Abaddon) { - // Abaddon - this.moveToPreset(sdk.areas.FrigidHighlands, sdk.unittype.Object, sdk.objects.RedPortal); - this.usePortal(sdk.areas.Abaddon); - } else if (currArea === sdk.areas.ArreatPlateau && targetArea === sdk.areas.PitofAcheron) { - // Pits of Archeon - this.moveToPreset(sdk.areas.ArreatPlateau, sdk.unittype.Object, sdk.objects.RedPortal); - this.usePortal(sdk.areas.PitofAcheron); - } else if (currArea === sdk.areas.FrozenTundra && targetArea === sdk.areas.InfernalPit) { - // Infernal Pit - this.moveToPreset(sdk.areas.FrozenTundra, sdk.unittype.Object, sdk.objects.RedPortal); - this.usePortal(sdk.areas.InfernalPit); - } else if (targetArea === sdk.areas.MooMooFarm) { - // Moo Moo farm - currArea !== sdk.areas.RogueEncampment && Town.goToTown(1); - Town.move("stash") && (unit = this.getPortal(targetArea)); - unit && this.usePortal(null, null, unit); - } else if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain, sdk.areas.UberTristram].includes(targetArea)) { - // Uber Portals - currArea !== sdk.areas.Harrogath && Town.goToTown(5); - Town.move("stash") && (unit = this.getPortal(targetArea)); - unit && this.usePortal(null, null, unit); - } else { - this.moveToExit(targetArea, true); - } - - // give time for act to load, increases stabilty of changing acts - delay(500); - - if (me.area === targetArea) { - target.course.shift(); - retry = 0; - } else { - if (retry > 3) { - console.warn("Failed to journeyTo " + Pather.getAreaName(area) + " currentarea: " + Pather.getAreaName(me.area)); - return false; - } - retry++; - } - } - - console.info(false, "ÿc4MyArea: ÿc0" + Pather.getAreaName(me.area), "journeyTo"); - return me.area === area; - }, - - plotCourse_openedWpMenu: false, - - /* - Pather.plotCourse(dest, src); - dest - destination area id - src - starting area id - */ - plotCourse: function (dest, src) { - let node, prevArea; - let useWP = false; - let arr = []; - // need to redo this...that's gonna be a pain - const previousAreas = [ - sdk.areas.None, sdk.areas.None, sdk.areas.RogueEncampment, sdk.areas.BloodMoor, sdk.areas.ColdPlains, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, sdk.areas.BlackMarsh, - sdk.areas.BloodMoor, sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.BlackMarsh, sdk.areas.TamoeHighland, sdk.areas.CaveLvl1, sdk.areas.UndergroundPassageLvl1, sdk.areas.HoleLvl1, - sdk.areas.PitLvl1, sdk.areas.ColdPlains, sdk.areas.BurialGrounds, sdk.areas.BurialGrounds, sdk.areas.BlackMarsh, sdk.areas.ForgottenTower, sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, - sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TamoeHighland, sdk.areas.MonasteryGate, sdk.areas.OuterCloister, sdk.areas.Barracks, sdk.areas.JailLvl1, sdk.areas.JailLvl2, - sdk.areas.JailLvl3, sdk.areas.InnerCloister, sdk.areas.Cathedral, sdk.areas.CatacombsLvl1, sdk.areas.CatacombsLvl2, sdk.areas.CatacombsLvl3, sdk.areas.StonyField, sdk.areas.RogueEncampment, - sdk.areas.RogueEncampment, sdk.areas.LutGholein, sdk.areas.RockyWaste, sdk.areas.DryHills, sdk.areas.FarOasis, sdk.areas.LostCity, sdk.areas.ArcaneSanctuary, sdk.areas.LutGholein, - sdk.areas.A2SewersLvl1, sdk.areas.A2SewersLvl2, sdk.areas.LutGholein, sdk.areas.HaremLvl1, sdk.areas.HaremLvl2, sdk.areas.PalaceCellarLvl1, sdk.areas.PalaceCellarLvl2, sdk.areas.RockyWaste, - sdk.areas.DryHills, sdk.areas.HallsoftheDeadLvl1, sdk.areas.ValleyofSnakes, sdk.areas.StonyTombLvl1, sdk.areas.HallsoftheDeadLvl2, sdk.areas.ClawViperTempleLvl1, sdk.areas.FarOasis, - sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.LostCity, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, - sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.RogueEncampment, sdk.areas.PalaceCellarLvl3, sdk.areas.RogueEncampment, sdk.areas.KurastDocktown, sdk.areas.SpiderForest, - sdk.areas.SpiderForest, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.KurastCauseway, - sdk.areas.SpiderForest, sdk.areas.SpiderForest, sdk.areas.FlayerJungle, sdk.areas.SwampyPitLvl1, sdk.areas.FlayerJungle, sdk.areas.FlayerDungeonLvl1, sdk.areas.SwampyPitLvl2, sdk.areas.FlayerDungeonLvl2, - sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.KurastBazaar, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.UpperKurast, sdk.areas.KurastCauseway, sdk.areas.KurastCauseway, - sdk.areas.Travincal, sdk.areas.DuranceofHateLvl1, sdk.areas.DuranceofHateLvl2, sdk.areas.DuranceofHateLvl3, sdk.areas.PandemoniumFortress, sdk.areas.OuterSteppes, sdk.areas.PlainsofDespair, - sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, sdk.areas.PandemoniumFortress, sdk.areas.Harrogath, sdk.areas.BloodyFoothills, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, - sdk.areas.CrystalizedPassage, sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, sdk.areas.GlacialTrail, sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.AncientsWay, sdk.areas.Harrogath, - sdk.areas.NihlathaksTemple, sdk.areas.HallsofAnguish, sdk.areas.HallsofPain, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.FrozenTundra, sdk.areas.ArreatSummit, sdk.areas.WorldstoneLvl1, - sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction, sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath - ]; - let visitedNodes = []; - let toVisitNodes = [{from: dest, to: null}]; - - !src && (src = me.area); - - if (!this.plotCourse_openedWpMenu && me.inTown && this.nextAreas[me.area] !== dest && Pather.useWaypoint(null)) { - this.plotCourse_openedWpMenu = true; - } - - while (toVisitNodes.length > 0) { - node = toVisitNodes[0]; - - // If we've already visited it, just move on - if (visitedNodes[node.from] === undefined) { - visitedNodes[node.from] = node.to; - - if (this.areasConnected(node.from, node.to)) { - // If we have this wp we can start from there - if ((me.inTown // check wp in town - || ((src !== previousAreas[dest] && dest !== previousAreas[src]) // check wp if areas aren't linked - && previousAreas[src] !== previousAreas[dest])) // check wp if areas aren't linked with a common area - && Pather.wpAreas.indexOf(node.from) > 0 && getWaypoint(Pather.wpAreas.indexOf(node.from)) - ) { - if (node.from !== src) { - useWP = true; - } - - src = node.from; - } - - // We found it, time to go - if (node.from === src) { - break; - } - - if ((prevArea = previousAreas[node.from]) !== 0 && visitedNodes.indexOf(prevArea) === -1) { - toVisitNodes.push({from: prevArea, to: node.from}); - } - - for (prevArea = 1; prevArea < previousAreas.length; prevArea += 1) { - // Only interested in those connected to node - if (previousAreas[prevArea] === node.from && visitedNodes.indexOf(prevArea) === -1) { - toVisitNodes.push({from: prevArea, to: node.from}); - } - } - } - - toVisitNodes.shift(); - } else { - useWP = true; - } - } - - arr.push(src); - - node = src; - - while (node !== dest && node !== undefined) { - arr.push(node = visitedNodes[node]); - } - - // Something failed - if (node === undefined) { - return false; - } - - return {course: arr, useWP: useWP}; - }, - - /* - Pather.areasConnected(src, dest); - dest - destination area id - src - starting area id - */ - areasConnected: function (src, dest) { - if (src === sdk.areas.CanyonofMagic && dest === sdk.areas.ArcaneSanctuary) { - return false; - } - - return true; - }, - - areaNames: [ - "None", - "Rogue Encampment", - "Blood Moor", - "Cold Plains", - "Stony Field", - "Dark Wood", - "Black Marsh", - "Tamoe Highland", - "Den Of Evil", - "Cave Level 1", - "Underground Passage Level 1", - "Hole Level 1", - "Pit Level 1", - "Cave Level 2", - "Underground Passage Level 2", - "Hole Level 2", - "Pit Level 2", - "Burial Grounds", - "Crypt", - "Mausoleum", - "Forgotten Tower", - "Tower Cellar Level 1", - "Tower Cellar Level 2", - "Tower Cellar Level 3", - "Tower Cellar Level 4", - "Tower Cellar Level 5", - "Monastery Gate", - "Outer Cloister", - "Barracks", - "Jail Level 1", - "Jail Level 2", - "Jail Level 3", - "Inner Cloister", - "Cathedral", - "Catacombs Level 1", - "Catacombs Level 2", - "Catacombs Level 3", - "Catacombs Level 4", - "Tristram", - "Moo Moo Farm", - "Lut Gholein", - "Rocky Waste", - "Dry Hills", - "Far Oasis", - "Lost City", - "Valley Of Snakes", - "Canyon Of The Magi", - "Sewers Level 1", - "Sewers Level 2", - "Sewers Level 3", - "Harem Level 1", - "Harem Level 2", - "Palace Cellar Level 1", - "Palace Cellar Level 2", - "Palace Cellar Level 3", - "Stony Tomb Level 1", - "Halls Of The Dead Level 1", - "Halls Of The Dead Level 2", - "Claw Viper Temple Level 1", - "Stony Tomb Level 2", - "Halls Of The Dead Level 3", - "Claw Viper Temple Level 2", - "Maggot Lair Level 1", - "Maggot Lair Level 2", - "Maggot Lair Level 3", - "Ancient Tunnels", - "Tal Rashas Tomb #1", - "Tal Rashas Tomb #2", - "Tal Rashas Tomb #3", - "Tal Rashas Tomb #4", - "Tal Rashas Tomb #5", - "Tal Rashas Tomb #6", - "Tal Rashas Tomb #7", - "Duriels Lair", - "Arcane Sanctuary", - "Kurast Docktown", - "Spider Forest", - "Great Marsh", - "Flayer Jungle", - "Lower Kurast", - "Kurast Bazaar", - "Upper Kurast", - "Kurast Causeway", - "Travincal", - "Spider Cave", - "Spider Cavern", - "Swampy Pit Level 1", - "Swampy Pit Level 2", - "Flayer Dungeon Level 1", - "Flayer Dungeon Level 2", - "Swampy Pit Level 3", - "Flayer Dungeon Level 3", - "Sewers Level 1", - "Sewers Level 2", - "Ruined Temple", - "Disused Fane", - "Forgotten Reliquary", - "Forgotten Temple", - "Ruined Fane", - "Disused Reliquary", - "Durance Of Hate Level 1", - "Durance Of Hate Level 2", - "Durance Of Hate Level 3", - "The Pandemonium Fortress", - "Outer Steppes", - "Plains Of Despair", - "City Of The Damned", - "River Of Flame", - "Chaos Sanctuary", - "Harrogath", - "Bloody Foothills", - "Frigid Highlands", - "Arreat Plateau", - "Crystalline Passage", - "Frozen River", - "Glacial Trail", - "Drifter Cavern", - "Frozen Tundra", - "Ancient's Way", - "Icy Cellar", - "Arreat Summit", - "Nihlathak's Temple", - "Halls Of Anguish", - "Halls Of Pain", - "Halls Of Vaught", - "Abaddon", - "Pit Of Acheron", - "Infernal Pit", - "Worldstone Keep Level 1", - "Worldstone Keep Level 2", - "Worldstone Keep Level 3", - "Throne Of Destruction", - "The Worldstone Chamber", - "Matron's Den", - "Forgotten Sands", - "Furnace of Pain", - "Tristram" - ], - - /* - Pather.getAreaName(area); - area - id of the area to get the name for - */ - getAreaName: function (area) { - if (["number", "string"].indexOf(typeof area) === -1) return "undefined"; - if (typeof area === "string") return area; - return (this.areaNames[area] || "undefined"); - }, -}; - -Pather.nextAreas[sdk.areas.RogueEncampment] = sdk.areas.BloodMoor; -Pather.nextAreas[sdk.areas.LutGholein] = sdk.areas.RockyWaste; -Pather.nextAreas[sdk.areas.KurastDocktown] = sdk.areas.SpiderForest; -Pather.nextAreas[sdk.areas.PandemoniumFortress] = sdk.areas.OuterSteppes; -Pather.nextAreas[sdk.areas.Harrogath] = sdk.areas.BloodyFoothills; diff --git a/d2bs/kolbot/libs/common/Pickit.js b/d2bs/kolbot/libs/common/Pickit.js deleted file mode 100644 index f749bca39..000000000 --- a/d2bs/kolbot/libs/common/Pickit.js +++ /dev/null @@ -1,665 +0,0 @@ -/** -* @filename Pickit.js -* @author kolton, theBGuy -* @desc handle item pickup -* -*/ - -const Pickit = { - gidList: [], - invoLocked: true, - beltSize: 1, - /** @enum */ - Result: { - UNID: -1, - UNWANTED: 0, - WANTED: 1, - CUBING: 2, - RUNEWORD: 3, - TRASH: 4, - CRAFTING: 5, - UTILITY: 6 - }, - /** - * Ignored item types for item logging - */ - ignoreLog: [ - sdk.items.type.Gold, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver, - sdk.items.type.Scroll, sdk.items.type.Key, sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, - sdk.items.type.RejuvPotion, sdk.items.type.StaminaPotion, sdk.items.type.AntidotePotion, sdk.items.type.ThawingPotion - ], - tkable: [ - sdk.items.type.Gold, sdk.items.type.Scroll, sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, - sdk.items.type.RejuvPotion, sdk.items.type.StaminaPotion, sdk.items.type.AntidotePotion, sdk.items.type.ThawingPotion - ], - essentials: [sdk.items.type.Gold, sdk.items.type.Scroll, sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion], - - /** - * @param {boolean} notify - */ - init: function (notify) { - Config.PickitFiles.forEach((file) => NTIP.OpenFile("pickit/" + file, notify)); - // check if we can pick up items, only do this is our inventory slots aren't completly locked - Pickit.invoLocked = !Config.Inventory.some(row => row.some(el => el > 0)); - - // sometime Storage isn't loaded? - if (typeof Storage !== "undefined") { - Pickit.beltSize = Storage.BeltSize(); - // If MinColumn is set to be more than our current belt size, set it to be 1 less than the belt size 4x3 belt will give us Config.MinColumn = [2, 2, 2, 2] - Config.MinColumn.forEach((el, index) => { - el >= Pickit.beltSize && (Config.MinColumn[index] = Math.max(1, Pickit.beltSize - 1)); - }); - } - }, - - // eslint-disable-next-line no-unused-vars - itemEvent: function (gid, mode, code, global) { - if (gid > 0 && mode === 0) { - Pickit.gidList.push(gid); - } - }, - - /** - * Just sort by distance for general item pickup - * @param {ItemUnit} unitA - * @param {ItemUnit} unitB - */ - sortItems: function (unitA, unitB) { - return getDistance(me, unitA) - getDistance(me, unitB); - }, - - /** - * Prioritize runes and unique items for fast pick - * @param {ItemUnit} unitA - * @param {ItemUnit} unitB - */ - sortFastPickItems: function (unitA, unitB) { - if (unitA.itemType === sdk.items.type.Rune || unitA.unique) return -1; - if (unitB.itemType === sdk.items.type.Rune || unitB.unique) return 1; - - return getDistance(me, unitA) - getDistance(me, unitB); - }, - - checkBelt: function () { - let check = 0; - let item = me.getItem(-1, sdk.items.mode.inBelt); - - if (item) { - do { - if (item.x < 4) { - check += 1; - } - } while (item.getNext()); - } - - return check === 4; - }, - - /** - * @param {number} quality - */ - itemQualityToName: function (quality) { - let qualNames = ["", "lowquality", "normal", "superior", "magic", "set", "rare", "unique", "crafted"]; - return qualNames[quality]; - }, - - /** - * @param {ItemUnit} unit - * @param {boolean} [type] - */ - itemColor: function (unit, type) { - type === undefined && (type = true); - - if (type) { - switch (unit.itemType) { - case sdk.items.type.Gold: - return "ÿc4"; - case sdk.items.type.Rune: - return "ÿc8"; - case sdk.items.type.HealingPotion: - return "ÿc1"; - case sdk.items.type.ManaPotion: - return "ÿc3"; - case sdk.items.type.RejuvPotion: - return "ÿc;"; - } - } - - switch (unit.quality) { - case sdk.items.quality.Magic: - return "ÿc3"; - case sdk.items.quality.Set: - return "ÿc2"; - case sdk.items.quality.Rare: - return "ÿc9"; - case sdk.items.quality.Unique: - return "ÿc4"; - case sdk.items.quality.Crafted: - return "ÿc8"; - } - - return "ÿc0"; - }, - - /** - * @param {ItemUnit} unit - */ - canPick: function (unit) { - if (!unit) return false; - if (sdk.quest.items.includes(unit.classid) && me.getItem(unit.classid)) return false; - - let tome, potion, needPots, buffers, pottype, myKey, key; - - switch (unit.itemType) { - case sdk.items.type.Gold: - // Check current gold vs max capacity (cLvl*10000) - if (me.getStat(sdk.stats.Gold) === me.getStat(sdk.stats.Level) * 10000) { - return false; // Skip gold if full - } - - break; - case sdk.items.type.Scroll: - // 518 - Tome of Town Portal or 519 - Tome of Identify - tome = me.getItem(unit.classid - 11, sdk.items.mode.inStorage); - - if (tome) { - do { - // In inventory, contains 20 scrolls - if (tome.isInInventory && tome.getStat(sdk.stats.Quantity) === 20) { - return false; // Skip a scroll if its tome is full - } - } while (tome.getNext()); - } else { - return false; // Don't pick scrolls if there's no tome - } - - break; - case sdk.items.type.Key: - // Assassins don't ever need keys - if (me.assassin) return false; - - myKey = me.getItem(sdk.items.Key, sdk.items.mode.inStorage); - key = Game.getItem(-1, -1, unit.gid); // Passed argument isn't an actual unit, we need to get it - - if (myKey && key) { - do { - if (myKey.isInInventory && myKey.getStat(sdk.stats.Quantity) + key.getStat(sdk.stats.Quantity) > 12) { - return false; - } - } while (myKey.getNext()); - } - - break; - case sdk.items.type.SmallCharm: - case sdk.items.type.LargeCharm: - case sdk.items.type.GrandCharm: - if (unit.unique) { - let charm = me.getItem(unit.classid, sdk.items.mode.inStorage); - - if (charm) { - do { - // Skip Gheed's Fortune, Hellfire Torch or Annihilus if we already have one - if (charm.unique) return false; - } while (charm.getNext()); - } - } - - break; - case sdk.items.type.HealingPotion: - case sdk.items.type.ManaPotion: - case sdk.items.type.RejuvPotion: - needPots = 0; - - for (let i = 0; i < 4; i += 1) { - if (typeof unit.code === "string" && unit.code.includes(Config.BeltColumn[i])) { - needPots += this.beltSize; - } - } - - potion = me.getItem(-1, sdk.items.mode.inBelt); - - if (potion) { - do { - if (potion.itemType === unit.itemType) { - needPots -= 1; - } - } while (potion.getNext()); - } - - if (needPots < 1 && this.checkBelt()) { - buffers = ["HPBuffer", "MPBuffer", "RejuvBuffer"]; - - for (let i = 0; i < buffers.length; i += 1) { - if (Config[buffers[i]]) { - pottype = (() => { - switch (buffers[i]) { - case "HPBuffer": - return sdk.items.type.HealingPotion; - case "MPBuffer": - return sdk.items.type.ManaPotion; - case "RejuvBuffer": - return sdk.items.type.RejuvPotion; - default: - return -1; - } - })(); - - if (unit.itemType === pottype) { - if (!Storage.Inventory.CanFit(unit)) return false; - - needPots = Config[buffers[i]]; - potion = me.getItem(-1, sdk.items.mode.inStorage); - - if (potion) { - do { - if (potion.itemType === pottype && potion.isInInventory) { - needPots -= 1; - } - } while (potion.getNext()); - } - } - } - } - } - - if (needPots < 1) { - potion = me.getItem(); - - if (potion) { - do { - if (potion.itemType === unit.itemType && (potion.isInInventory || potion.isInBelt)) { - if (potion.classid < unit.classid) { - potion.use(); - needPots += 1; - - break; - } - } - } while (potion.getNext()); - } - } - - return (needPots > 0); - case undefined: // Yes, it does happen - console.warn("undefined item (!?)"); - - return false; - } - - return true; - }, - - /** - * @param {ItemUnit} unit - * @returns {PickitResult} - * -1 : Needs iding, - * 0 : Unwanted, - * 1 : NTIP wants, - * 2 : Cubing wants, - * 3 : Runeword wants, - * 4 : Pickup to sell (triggered when low on gold) - */ - checkItem: function (unit) { - let rval = NTIP.CheckItem(unit, false, true); - const resultObj = (result, line = null) => ({ - result: result, - line: line - }); - - // make sure we have essentials - no pickit files loaded - if (rval.result === Pickit.Result.UNWANTED && Config.PickitFiles.length === 0 && Pickit.essentials.includes(unit.itemType) && this.canPick(unit)) { - return resultObj(Pickit.Result.WANTED); - } - - if ((unit.classid === sdk.items.runes.Ort || unit.classid === sdk.items.runes.Ral) && Town.repairIngredientCheck(unit)) { - return resultObj(Pickit.Result.UTILITY); - } - - if (CraftingSystem.checkItem(unit)) return resultObj(Pickit.Result.CRAFTING); - if (Cubing.checkItem(unit)) return resultObj(Pickit.Result.CUBING); - if (Runewords.checkItem(unit)) return resultObj(Pickit.Result.RUNEWORD); - - // if Gemhunting, pick Item for Cubing, if no other system needs it - if ((Scripts.GemHunter) // gemhunter active - && rval.result === Pickit.Result.UNWANTED // no other subsystem needs it - && Config.GemHunter.GemList.some((p) => [unit.classid - 1, unit.classid].includes(p)) // base and upgraded gem will be kept - && me.getItemsEx(unit.classid, sdk.items.mode.inStorage) - .filter(i => i.gid !== unit.gid && (!CraftingSystem.checkItem(i) && !Cubing.checkItem(i) && !Runewords.checkItem(i))).length === 0 // bit annoying for now but force only keeping max 1 gem for gemhunter - ) return resultObj(Pickit.Result.WANTED, "GemHunter"); - - if (rval.result === Pickit.Result.UNWANTED && !Town.ignoreType(unit.itemType) && !unit.questItem - && ((unit.isInInventory && (me.inTown || !Config.FieldID.Enabled)) || (me.gold < Config.LowGold || (me.gold < 500000 && Config.PickitFiles.length === 0)))) { - // Gold doesn't take up room, just pick it up - if (unit.classid === sdk.items.Gold) return resultObj(Pickit.Result.TRASH); - - if (!this.invoLocked) { - const itemValue = unit.getItemCost(sdk.items.cost.ToSell); - const itemValuePerSquare = itemValue / (unit.sizex * unit.sizey); - - if (itemValuePerSquare >= 2000) { - // If total gold is less than 500k pick up anything worth 2k gold per square to sell in town. - return resultObj(Pickit.Result.TRASH, "Valuable LowGold Item: " + itemValue); - } else if (itemValuePerSquare >= 10) { - // If total gold is less than LowGold setting pick up anything worth 10 gold per square to sell in town. - return resultObj(Pickit.Result.TRASH, "LowGold Item: " + itemValue); - } - } - } - - return rval; - }, - - /** - * @param {ItemUnit} unit - * @param {PickitResult} status - * @param {string} keptLine - * @param {number} retry - */ - pickItem: function (unit, status, keptLine, retry = 3) { - /** - * @constructor - * @param {ItemUnit} unit - */ - function ItemStats (unit) { - this.ilvl = unit.ilvl; - this.type = unit.itemType; - this.classid = unit.classid; - this.name = unit.name; - this.color = Pickit.itemColor(unit); - this.gold = unit.getStat(sdk.stats.Gold); - this.dist = (unit.distance || Infinity); - this.useTk = (Skill.haveTK && Pickit.tkable.includes(this.type) - && this.dist > 5 && this.dist < 20 && !checkCollision(me, unit, sdk.collision.WallOrRanged)); - this.picked = false; - } - - let gid = (unit.gid || -1); - let cancelFlags = [sdk.uiflags.Inventory, sdk.uiflags.NPCMenu, sdk.uiflags.Waypoint, sdk.uiflags.Shop, sdk.uiflags.Stash, sdk.uiflags.Cube]; - let itemCount = me.itemcount; - let item = gid > -1 ? Game.getItem(-1, -1, gid) : false; - - if (!item) return false; - - for (let i = 0; i < cancelFlags.length; i += 1) { - if (getUIFlag(cancelFlags[i])) { - delay(500); - me.cancel(0); - - break; - } - } - - let stats = new ItemStats(item); - let tkMana = stats.useTk ? Skill.getManaCost(sdk.skills.Telekinesis) * 2 : Infinity; - - MainLoop: - for (let i = 0; i < retry; i += 1) { - if (!Game.getItem(-1, -1, gid)) { - break; - } - - if (me.dead) return false; - - while (!me.idle) { - delay(40); - } - - if (!item.onGroundOrDropping) { - break; - } - - // fastPick check? should only pick items if surrounding monsters have been cleared or if fastPick is active - // note: clear of surrounding monsters of the spectype we are set to clear - if (stats.useTk && me.mp > tkMana) { - if (!Packet.telekinesis(item)) { - i > 1 && (stats.useTk = false); - continue; - } - } else { - if (item.distance > (Config.FastPick || i < 1 ? 6 : 4) || checkCollision(me, item, sdk.collision.BlockWall)) { - if (!Pather.moveNearUnit(item, 4)) { - continue; - } - // we had to move, lets check to see if it's still there - if (me.getItem(-1, -1, gid) || !Game.getItem(-1, -1, gid)) { - // we picked the item during another process or it's gone so don't continue - break; - } - } - - // use packet first, if we fail and not using fast pick use click - (Config.FastPick || i < 1) ? Packet.click(item) : Misc.click(0, 0, item); - } - - let tick = getTickCount(); - - while (getTickCount() - tick < 1000) { - item = copyUnit(item); - - if (stats.classid === sdk.items.Gold) { - if (!item.getStat(sdk.stats.Gold) || item.getStat(sdk.stats.Gold) < stats.gold) { - console.log("ÿc7Picked up " + stats.color + (item.getStat(sdk.stats.Gold) ? (item.getStat(sdk.stats.Gold) - stats.gold) : stats.gold) + " " + stats.name); - - return true; - } - } - - if (!item.onGroundOrDropping) { - switch (stats.classid) { - case sdk.items.Key: - console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc7(" + Town.checkKeys() + "/12)"); - - return true; - case sdk.items.ScrollofTownPortal: - case sdk.items.ScrollofIdentify: - console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc7(" + Town.checkScrolls(stats.classid === sdk.items.ScrollofTownPortal ? "tbk" : "ibk") + "/20)"); - - return true; - } - - break MainLoop; - } - - delay(20); - } - - // TK failed, disable it - stats.useTk = false; - } - - stats.picked = me.itemcount > itemCount || !!me.getItem(-1, -1, gid); - - if (stats.picked) { - DataFile.updateStats("lastArea"); - - switch (status) { - case Pickit.Result.WANTED: - console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + (keptLine ? ") (" + keptLine + ")" : ")")); - - if (this.ignoreLog.indexOf(stats.type) === -1) { - Misc.itemLogger("Kept", item); - Misc.logItem("Kept", item, keptLine); - } - - break; - case Pickit.Result.CUBING: - console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + ")" + " (Cubing)"); - Misc.itemLogger("Kept", item, "Cubing " + me.findItems(item.classid).length); - Cubing.update(); - - break; - case Pickit.Result.RUNEWORD: - console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + ")" + " (Runewords)"); - Misc.itemLogger("Kept", item, "Runewords"); - Runewords.update(stats.classid, gid); - - break; - case Pickit.Result.CRAFTING: - console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + ")" + " (Crafting System)"); - CraftingSystem.update(item); - - break; - default: - console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + (keptLine ? ") (" + keptLine + ")" : ")")); - - break; - } - } - - return true; - }, - - /** - * Check if we can even free up the inventory - */ - canMakeRoom: function () { - if (!Config.MakeRoom) return false; - - let items = Storage.Inventory.Compare(Config.Inventory) || []; - - if (items.length) { - return items.some(item => { - switch (Pickit.checkItem(item).result) { - case Pickit.Result.UNID: - // For low level chars that can't actually get id scrolls -> prevent an infinite loop - return (me.gold > 100); - case Pickit.Result.UNWANTED: - case Pickit.Result.TRASH: - // if we've got items to sell then we can make room as long as we can get to town - return Town.canTpToTown(); - default: // Check if a kept item can be stashed - return Town.canStash(item); - } - }); - } - - return false; - }, - - /** - * @param {number} range - * @returns {boolean} If we picked items - */ - pickItems: function (range = Config.PickRange) { - if (me.dead) return false; - - let needMule = false; - let pickList = []; - - Town.clearBelt(); - - while (!me.idle) { - delay(40); - } - - let item = Game.getItem(); - - if (item) { - do { - if (item.onGroundOrDropping && getDistance(me, item) <= range) { - pickList.push(copyUnit(item)); - } - } while (item.getNext()); - } - - while (pickList.length > 0) { - if (me.dead) return false; - pickList.sort(this.sortItems); - - const itemToPick = pickList.shift(); - - // Check if the item unit is still valid and if it's on ground or being dropped - // Don't pick items behind walls/obstacles when walking - if (copyUnit(itemToPick).x !== undefined && itemToPick.onGroundOrDropping - && (Pather.useTeleport() || me.inTown || !checkCollision(me, itemToPick, sdk.collision.BlockWall))) { - // Check if the item should be picked - let status = this.checkItem(itemToPick); - - if (status.result && this.canPick(itemToPick)) { - // Override canFit for scrolls, potions and gold - let canFit = (Storage.Inventory.CanFit(itemToPick) || Pickit.essentials.includes(itemToPick.itemType)); - - // Field id when our used space is above a certain percent or if we are full try to make room with FieldID - if (Config.FieldID.Enabled && (!canFit || Storage.Inventory.UsedSpacePercent() > Config.FieldID.UsedSpace)) { - Town.fieldID() && (canFit = (itemToPick.gid !== undefined && Storage.Inventory.CanFit(itemToPick))); - } - - // Try to make room by selling items in town - if (!canFit) { - // Check if any of the current inventory items can be stashed or need to be identified and eventually sold to make room - if (this.canMakeRoom()) { - console.log("ÿc7Trying to make room for " + Pickit.itemColor(itemToPick) + itemToPick.name); - - // Go to town and do town chores - if (Town.visitTown()) { - // Recursive check after going to town. We need to remake item list because gids can change. - // Called only if room can be made so it shouldn't error out or block anything. - return this.pickItems(); - } - - // Town visit failed - abort - console.log("ÿc7Not enough room for " + Pickit.itemColor(itemToPick) + itemToPick.name); - - return false; - } - - // Can't make room - trigger automule - Misc.itemLogger("No room for", itemToPick); - console.log("ÿc7Not enough room for " + Pickit.color(itemToPick) + itemToPick.name); - - if (copyUnit(itemToPick).x !== undefined) { - needMule = true; - - break; - } - } - - // Item can fit - pick it up - canFit && this.pickItem(itemToPick, status.result, status.line); - } - } - } - - // Quit current game and transfer the items to mule - if (needMule && AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo") && AutoMule.getMuleItems().length > 0) { - scriptBroadcast("mule"); - scriptBroadcast("quit"); - } - - return true; - }, - - /** - * @param {number} retry - */ - fastPick: function (retry = 3) { - let item, itemList = []; - - while (this.gidList.length > 0) { - let gid = this.gidList.shift(); - item = Game.getItem(-1, -1, gid); - - if (item && item.onGroundOrDropping - && (!Town.ignoreType(item.itemType) || (item.itemType >= sdk.items.type.HealingPotion && item.itemType <= sdk.items.type.RejuvPotion)) - && item.itemType !== sdk.items.type.Gold && getDistance(me, item) <= Config.PickRange) { - itemList.push(copyUnit(item)); - } - } - - while (itemList.length > 0) { - itemList.sort(this.sortFastPickItems); - item = copyUnit(itemList.shift()); - - // Check if the item unit is still valid - if (item.x !== undefined) { - let status = this.checkItem(item); - - if (status.result && this.canPick(item) && (Storage.Inventory.CanFit(item) || Pickit.essentials.includes(item.itemType))) { - this.pickItem(item, status.result, status.line, retry); - } - } - } - - return true; - }, -}; diff --git a/d2bs/kolbot/libs/common/Precast.js b/d2bs/kolbot/libs/common/Precast.js deleted file mode 100644 index ec1f9ed1a..000000000 --- a/d2bs/kolbot/libs/common/Precast.js +++ /dev/null @@ -1,620 +0,0 @@ -/** -* @filename Precast.js -* @author noah-, kolton, theBGuy -* @desc handle player prebuff sequence -* -*/ - -const Precast = new function () { - this.enabled = true; - this.haveCTA = -1; - this.bestSlot = {}; - - // TODO: build better method of keeping track of duration based skills so we can reduce resource usage - // build obj -> figure out which skills we have -> calc duration -> assign tick of last casted -> track tick (background worker maybe?) - // would reduce checking have skill and state calls, just let tick = getTickCount(); -> obj.some((el) => tick - el.lastTick > el.duration) -> true then cast - // would probably make sense to just re-cast everything (except summons) if one of our skills is about to run out rather than do this process again 3 seconds later - this.skills = { - // Not sure how I want to handle cold armors - coldArmor: { - best: false, - duration: 0, - tick: 0 - }, - boneArmor: { - max: 0, - armorPercent: function () { - return this.max > 0 ? Math.round(me.getStat(sdk.stats.SkillBoneArmor) * 100 / this.max) : 100; - }, - }, - holyShield: { - canUse: false, - duration: 0, - tick: 0 - }, - shout: { - duration: 0, - tick: 0 - }, - battleOrders: { - duration: 0, - tick: 0 - }, - battleCommand: { - duration: 0, - tick: 0 - }, - }; - - this.warCries = function (skillId, x, y) { - if (!skillId || x === undefined) return false; - const states = {}; - states[sdk.skills.Shout] = sdk.states.Shout; - states[sdk.skills.BattleOrders] = sdk.states.BattleOrders; - states[sdk.skills.BattleCommand] = sdk.states.BattleCommand; - if (states[skillId] === undefined) return false; - - for (let i = 0; i < 3; i++) { - try { - if (me.getSkill(sdk.skills.get.RightId) !== skillId && !me.setSkill(skillId, sdk.skills.hand.Right)) throw new Error("Failed to set " + getSkillById(skillId) + " on hand"); - // Right hand + No Shift - let clickType = 3, shift = sdk.clicktypes.shift.NoShift; - - MainLoop: - for (let n = 0; n < 3; n += 1) { - typeof x === "object" ? clickMap(clickType, shift, x) : clickMap(clickType, shift, x, y); - delay(20); - typeof x === "object" ? clickMap(clickType + 2, shift, x) : clickMap(clickType + 2, shift, x, y); - - for (let i = 0; i < 8; i += 1) { - if (me.attacking) { - break MainLoop; - } - - delay(20); - } - } - - while (me.attacking) { - delay(10); - } - - if (Misc.poll(() => me.getState(states[skillId]), 300, 50)) return true; - } catch (e) { - console.error(e); - return false; - } - } - return false; - }; - - this.checkCTA = function () { - if (this.haveCTA > -1) return true; - - let check = me.checkItem({name: sdk.locale.items.CalltoArms, equipped: true}); - - if (check.have) { - this.haveCTA = check.item.isOnSwap ? 1 : 0; - } - - return this.haveCTA > -1; - }; - - this.precastCTA = function (force = false) { - if (!Config.UseCta || this.haveCTA === -1 || me.classic || me.barbarian || me.inTown || me.shapeshifted) return false; - if (!force && me.getState(sdk.states.BattleOrders)) return true; - - if (this.haveCTA > -1) { - let slot = me.weaponswitch; - let {x, y} = me; - - me.switchWeapons(this.haveCTA); - this.cast(sdk.skills.BattleCommand, x, y, true); - this.cast(sdk.skills.BattleCommand, x, y, true); - this.cast(sdk.skills.BattleOrders, x, y, true); - - this.skills.battleOrders.tick = getTickCount(); - // does this need to be re-calculated everytime? if no autobuild should really just be done when we initialize - !this.skills.battleOrders.duration && (this.skills.battleOrders.duration = Skill.getDuration(sdk.skills.BattleOrders)); - - me.switchWeapons(slot); - - return true; - } - - return false; - }; - - // should be done in init function? - // should this be part of the skill class instead? - this.getBetterSlot = function (skillId) { - if (this.bestSlot[skillId] !== undefined) return this.bestSlot[skillId]; - - let [classid, skillTab] = (() => { - switch (skillId) { - case sdk.skills.FrozenArmor: - case sdk.skills.ShiverArmor: - case sdk.skills.ChillingArmor: - return [sdk.player.class.Sorceress, sdk.skills.tabs.Cold]; - case sdk.skills.Enchant: - return [sdk.player.class.Sorceress, sdk.skills.tabs.Fire]; - case sdk.skills.ThunderStorm: - case sdk.skills.EnergyShield: - return [sdk.player.class.Sorceress, sdk.skills.tabs.Lightning]; - case sdk.skills.BoneArmor: - return [sdk.player.class.Necromancer, sdk.skills.tabs.PoisonandBone]; - case sdk.skills.HolyShield: - return [sdk.player.class.Paladin, sdk.skills.tabs.PalaCombat]; - case sdk.skills.Taunt: - case sdk.skills.FindItem: - case sdk.skills.BattleCry: - case sdk.skills.WarCry: - case sdk.skills.Shout: - case sdk.skills.BattleOrders: - case sdk.skills.BattleCommand: - return [sdk.player.class.Barbarian, sdk.skills.tabs.Warcries]; - case sdk.skills.CycloneArmor: - return [sdk.player.class.Druid, sdk.skills.tabs.Elemental]; - case sdk.skills.Werewolf: - case sdk.skills.Werebear: - return [sdk.player.class.Druid, sdk.skills.tabs.ShapeShifting]; - case sdk.skills.BurstofSpeed: - case sdk.skills.Fade: - return [sdk.player.class.Assassin, sdk.skills.tabs.ShadowDisciplines]; - case sdk.skills.BladeShield: - return [sdk.player.class.Assassin, sdk.skills.tabs.MartialArts]; - default: - return [-1, -1]; - } - })(); - - if (classid < 0) return me.weaponswitch; - - me.weaponswitch !== 0 && me.switchWeapons(0); - - let [sumCurr, sumSwap] = [0, 0]; - const sumStats = function (item) { - return (item.getStat(sdk.stats.AllSkills) - + item.getStat(sdk.stats.AddClassSkills, classid) + item.getStat(sdk.stats.AddSkillTab, skillTab) - + item.getStat(sdk.stats.SingleSkill, skillId) + item.getStat(sdk.stats.NonClassSkill, skillId)); - }; - - me.getItemsEx() - .filter(item => item.isEquipped && [sdk.body.RightArm, sdk.body.LeftArm, sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary].includes(item.bodylocation)) - .forEach(function (item) { - if (item.isOnMain) { - sumCurr += sumStats(item); - return; - } - - if (item.isOnSwap) { - sumSwap += sumStats(item); - return; - } - }); - this.bestSlot[skillId] = (sumSwap > sumCurr) ? me.weaponswitch ^ 1 : me.weaponswitch; - return this.bestSlot[skillId]; - }; - - this.cast = function (skillId, x = me.x, y = me.y, dontSwitch = false) { - if (!skillId || !Skill.wereFormCheck(skillId) || (me.inTown && !Skill.townSkill(skillId))) return false; - if (Skill.getManaCost(skillId) > me.mp) return false; - - let swap = me.weaponswitch; - let success = true; - // don't use packet casting with summons - or boing - const usePacket = ([ - sdk.skills.Valkyrie, sdk.skills.Decoy, sdk.skills.RaiseSkeleton, sdk.skills.ClayGolem, sdk.skills.RaiseSkeletalMage, sdk.skills.BloodGolem, sdk.skills.Shout, - sdk.skills.IronGolem, sdk.skills.Revive, sdk.skills.Werewolf, sdk.skills.Werebear, sdk.skills.OakSage, sdk.skills.SpiritWolf, sdk.skills.PoisonCreeper, sdk.skills.BattleOrders, - sdk.skills.SummonDireWolf, sdk.skills.Grizzly, sdk.skills.HeartofWolverine, sdk.skills.SpiritofBarbs, sdk.skills.ShadowMaster, sdk.skills.ShadowWarrior, sdk.skills.BattleCommand, - ].indexOf(skillId) === -1); - (typeof x !== "number" || typeof y !== "number") && ({x, y} = me); - - try { - !dontSwitch && me.switchWeapons(this.getBetterSlot(skillId)); - if (me.getSkill(sdk.skills.get.RightId) !== skillId && !me.setSkill(skillId, sdk.skills.hand.Right)) throw new Error("Failed to set " + getSkillById(skillId) + " on hand"); - if ([sdk.skills.Shout, sdk.skills.BattleOrders, sdk.skills.BattleCommand].includes(skillId)) return this.warCries(skillId, x, y); - - if (Config.PacketCasting > 1 || usePacket) { - Config.DebugMode && console.debug("Packet casting: " + skillId); - - switch (typeof x) { - case "number": - Packet.castSkill(sdk.skills.hand.Right, x, y); - - break; - case "object": - Packet.unitCast(sdk.skills.hand.Right, x); - - break; - } - delay(250); - } else { - // Right hand + No Shift - let clickType = 3, shift = sdk.clicktypes.shift.NoShift; - - MainLoop: - for (let n = 0; n < 3; n += 1) { - typeof x === "object" ? clickMap(clickType, shift, x) : clickMap(clickType, shift, x, y); - delay(20); - typeof x === "object" ? clickMap(clickType + 2, shift, x) : clickMap(clickType + 2, shift, x, y); - - for (let i = 0; i < 8; i += 1) { - if (me.attacking) { - break MainLoop; - } - - delay(20); - } - } - - while (me.attacking) { - delay(10); - } - } - - // account for lag, state 121 doesn't kick in immediately - if (Skill.isTimed(skillId)) { - for (let i = 0; i < 10; i += 1) { - if ([sdk.player.mode.GettingHit, sdk.player.mode.Blocking].includes(me.mode) || me.skillDelay) { - break; - } - - delay(10); - } - } - } catch (e) { - console.error(e); - success = false; - } - - !dontSwitch && me.switchWeapons(swap); - - return success; - }; - - this.summon = function (skillId, minionType) { - if (!Skill.canUse(skillId)) return false; - - let rv, retry = 0; - let count = Skill.getMaxSummonCount(skillId); - - while (me.getMinionCount(minionType) < count) { - rv = true; - - if (retry > count * 2) { - if (me.inTown) { - Town.heal() && me.cancelUIFlags(); - Town.move("portalspot"); - Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y); - } else { - let coord = CollMap.getRandCoordinate(me.x, -6, 6, me.y, -6, 6); - - // Keep bots from getting stuck trying to summon - if (!!coord && Attack.validSpot(coord.x, coord.y)) { - Pather.moveTo(coord.x, coord.y); - Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y); - } - } - - if (me.getMinionCount(minionType) === count) { - return true; - } else { - console.warn("Failed to summon minion " + skillId); - - return false; - } - } - - // todo - only delay if we are close to the mana amount we need based on our mana regen rate or potion state - // also take into account surrounding mobs so we don't delay for mana in the middle of a mob pack - if (Skill.getManaCost(skillId) > me.mp) { - if (!Misc.poll(() => me.mp >= Skill.getManaCost(skillId), 500, 100)) { - retry++; - continue; - } - } - - let coord = CollMap.getRandCoordinate(me.x, -4, 4, me.y, -4, 4); - - if (!!coord && Attack.validSpot(coord.x, coord.y)) { - Skill.cast(skillId, sdk.skills.hand.Right, coord.x, coord.y); - - if (me.getMinionCount(minionType) === count) { - break; - } else { - retry++; - } - } - - delay(200); - } - - return !!rv; - }; - - this.enchant = function () { - let unit, slot = me.weaponswitch, chanted = []; - - me.switchWeapons(this.getBetterSlot(sdk.skills.Enchant)); - - // Player - unit = Game.getPlayer(); - - if (unit) { - do { - if (!unit.dead && Misc.inMyParty(unit.name) && unit.distance <= 40) { - Skill.cast(sdk.skills.Enchant, sdk.skills.hand.Right, unit); - chanted.push(unit.name); - } - } while (unit.getNext()); - } - - // Minion - unit = Game.getMonster(); - - if (unit) { - do { - if (unit.getParent() && chanted.includes(unit.getParent().name) && unit.distance <= 40) { - Skill.cast(sdk.skills.Enchant, sdk.skills.hand.Right, unit); - } - } while (unit.getNext()); - } - - me.switchWeapons(slot); - - return true; - }; - - // should the config check still be included even though its part of Skill.init? - // todo: durations - this.doPrecast = function (force = false) { - if (!this.enabled) return false; - - while (!me.gameReady) { - delay(40); - } - - let [buffSummons, forceBo] = [false, false]; - - // Force BO 30 seconds before it expires - if (this.haveCTA > -1) { - forceBo = (force - || (getTickCount() - this.skills.battleOrders.tick >= this.skills.battleOrders.duration - 30000) - || !me.getState(sdk.states.BattleCommand)); - forceBo && this.precastCTA(forceBo); - } - - const needToCast = (state) => (force || !me.getState(state)); - - switch (me.classid) { - case sdk.player.class.Amazon: - Skill.canUse(sdk.skills.Valkyrie) && (buffSummons = this.summon(sdk.skills.Valkyrie, sdk.summons.type.Valkyrie)); - - break; - case sdk.player.class.Sorceress: - if (Skill.canUse(sdk.skills.ThunderStorm) && needToCast(sdk.states.ThunderStorm)) { - this.cast(sdk.skills.ThunderStorm); - } - - if (Skill.canUse(sdk.skills.EnergyShield) && needToCast(sdk.states.EnergyShield)) { - this.cast(sdk.skills.EnergyShield); - } - - if (Config.UseColdArmor) { - let choosenSkill = (typeof Config.UseColdArmor === "number" && Skill.canUse(Config.UseColdArmor) - ? Config.UseColdArmor - : (Precast.skills.coldArmor.best || -1)); - - if (Precast.skills.coldArmor.tick > 0 && Precast.skills.coldArmor.duration > Time.seconds(45)) { - if (getTickCount() - Precast.skills.coldArmor.tick >= Precast.skills.coldArmor.duration - Time.seconds(30)) { - force = true; - } - } - switch (choosenSkill) { - case sdk.skills.FrozenArmor: - if (needToCast(sdk.states.FrozenArmor)) { - Precast.cast(sdk.skills.FrozenArmor) && (Precast.skills.coldArmor.tick = getTickCount()); - } - - break; - case sdk.skills.ChillingArmor: - if (needToCast(sdk.states.ChillingArmor)) { - Precast.cast(sdk.skills.ChillingArmor) && (Precast.skills.coldArmor.tick = getTickCount()); - } - - break; - case sdk.skills.ShiverArmor: - if (needToCast(sdk.states.ShiverArmor)) { - Precast.cast(sdk.skills.ShiverArmor) && (Precast.skills.coldArmor.tick = getTickCount()); - } - - break; - default: - break; - } - } - - if (Skill.canUse(sdk.skills.Enchant) && needToCast(sdk.states.Enchant)) { - this.enchant(); - } - - break; - case sdk.player.class.Necromancer: - if (Skill.canUse(sdk.skills.BoneArmor) - && (force || this.skills.boneArmor.armorPercent() < 75 || !me.getState(sdk.states.BoneArmor))) { - this.cast(sdk.skills.BoneArmor); - this.skills.boneArmor.max === 0 && (this.skills.boneArmor.max = me.getStat(sdk.stats.SkillBoneArmorMax)); - } - - (() => { - switch (Config.Golem) { - case 1: - case "Clay": - return this.summon(sdk.skills.ClayGolem, sdk.summons.type.Golem); - case 2: - case "Blood": - return this.summon(sdk.skills.BloodGolem, sdk.summons.type.Golem); - case 3: - case "Fire": - return this.summon(sdk.skills.FireGolem, sdk.summons.type.Golem); - case 0: - case "None": - default: - return false; - } - })(); - - break; - case sdk.player.class.Paladin: - if (Skill.canUse(sdk.skills.HolyShield) && Precast.skills.holyShield.canUse && needToCast(sdk.states.HolyShield)) { - this.cast(sdk.skills.HolyShield); - } - - break; - case sdk.player.class.Barbarian: // - TODO: durations - if (!Config.UseWarcries) { - break; - } - let needShout = (Skill.canUse(sdk.skills.Shout) && needToCast(sdk.states.Shout)); - let needBo = (Skill.canUse(sdk.skills.BattleOrders) && needToCast(sdk.states.BattleOrders)); - let needBc = (Skill.canUse(sdk.skills.BattleCommand) && needToCast(sdk.states.BattleCommand)); - - if (needShout || needBo || needBc) { - let primary = Attack.getPrimarySlot(); - let { x, y } = me; - (needBo || needBc) && me.switchWeapons(this.getBetterSlot(sdk.skills.BattleOrders)); - - needBc && this.cast(sdk.skills.BattleCommand, x, y, true); - needBo && this.cast(sdk.skills.BattleOrders, x, y, true); - needShout && this.cast(sdk.skills.Shout, x, y, true); - - me.weaponswitch !== primary && me.switchWeapons(primary); - } - - break; - case sdk.player.class.Druid: - if (Skill.canUse(sdk.skills.CycloneArmor) && needToCast(sdk.states.CycloneArmor)) { - this.cast(sdk.skills.CycloneArmor); - } - - Skill.canUse(sdk.skills.Raven) && this.summon(sdk.skills.Raven, sdk.summons.type.Raven); - - buffSummons = (() => { - switch (Config.SummonAnimal) { - case 1: - case "Spirit Wolf": - return (this.summon(sdk.skills.SummonSpiritWolf, sdk.summons.type.SpiritWolf) || buffSummons); - case 2: - case "Dire Wolf": - return (this.summon(sdk.skills.SummonDireWolf, sdk.summons.type.DireWolf) || buffSummons); - case 3: - case "Grizzly": - return (this.summon(sdk.skills.SummonGrizzly, sdk.summons.type.Grizzly) || buffSummons); - default: - return buffSummons; - } - })(); - - buffSummons = (() => { - switch (Config.SummonVine) { - case 1: - case "Poison Creeper": - return (this.summon(sdk.skills.PoisonCreeper, sdk.summons.type.Vine) || buffSummons); - case 2: - case "Carrion Vine": - return (this.summon(sdk.skills.CarrionVine, sdk.summons.type.Vine) || buffSummons); - case 3: - case "Solar Creeper": - return (this.summon(sdk.skills.SolarCreeper, sdk.summons.type.Vine) || buffSummons); - default: - return buffSummons; - } - })(); - - buffSummons = (() => { - switch (Config.SummonSpirit) { - case 1: - case "Oak Sage": - return (this.summon(sdk.skills.OakSage, sdk.summons.type.Spirit) || buffSummons); - case 2: - case "Heart of Wolverine": - return (this.summon(sdk.skills.HeartofWolverine, sdk.summons.type.Spirit) || buffSummons); - case 3: - case "Spirit of Barbs": - return this.summon(sdk.skills.SpiritofBarbs, sdk.summons.type.Spirit) || buffSummons; - default: - return buffSummons; - } - })(); - - if (Skill.canUse(sdk.skills.Hurricane) && needToCast(sdk.states.Hurricane)) { - this.cast(sdk.skills.Hurricane); - } - - break; - case sdk.player.class.Assassin: - if (Skill.canUse(sdk.skills.Fade) && needToCast(sdk.states.Fade)) { - this.cast(sdk.skills.Fade); - } - - if (Skill.canUse(sdk.skills.Venom) && needToCast(sdk.states.Venom)) { - this.cast(sdk.skills.Venom); - } - - if (Skill.canUse(sdk.skills.BladeShield) && needToCast(sdk.states.BladeShield)) { - this.cast(sdk.skills.BladeShield); - } - - if (!Config.UseFade && Skill.canUse(sdk.skills.BurstofSpeed) && needToCast(sdk.states.BurstofSpeed)) { - this.cast(sdk.skills.BurstofSpeed); - } - - buffSummons = (() => { - switch (Config.SummonShadow) { - case 1: - case "Warrior": - return this.summon(sdk.skills.ShadowWarrior, sdk.summons.type.Shadow); - case 2: - case "Master": - return this.summon(sdk.skills.ShadowMaster, sdk.summons.type.Shadow); - default: - return false; - } - })(); - - break; - } - - buffSummons && this.haveCTA > -1 && this.precastCTA(force); - me.switchWeapons(Attack.getPrimarySlot()); - - return true; - }; - - this.needOutOfTownCast = function () { - return Skill.canUse(sdk.skills.Shout) || Skill.canUse(sdk.skills.BattleOrders) || Precast.checkCTA(); - }; - - this.doRandomPrecast = function (force = false, goToWhenDone = undefined) { - let returnTo = (goToWhenDone && typeof goToWhenDone === "number" ? goToWhenDone : me.area); - - try { - // Only do this is you are a barb or actually have a cta. Otherwise its just a waste of time and you can precast in town - if (Precast.needOutOfTownCast()) { - Pather.useWaypoint("random") && Precast.doPrecast(force); - } else { - Precast.doPrecast(force); - } - Pather.useWaypoint(returnTo); - } catch (e) { - console.error(e); - } finally { - if (me.area !== returnTo && (!Pather.useWaypoint(returnTo) || !Pather.useWaypoint(sdk.areas.townOf(me.area)))) { - Pather.journeyTo(returnTo); - } - } - - return (me.area === returnTo); - }; -}; diff --git a/d2bs/kolbot/libs/common/Prototypes.js b/d2bs/kolbot/libs/common/Prototypes.js deleted file mode 100644 index 162f26c39..000000000 --- a/d2bs/kolbot/libs/common/Prototypes.js +++ /dev/null @@ -1,2765 +0,0 @@ -/** -* @filename Prototypes.js -* @author kolton, theBGuy -* @credit Jaenster -* @desc various 'Unit' and 'me' prototypes -* -*/ - -// Ensure these are in polyfill.js -!isIncluded("Polyfill.js") && include("Polyfill.js"); -// Make sure we have our util functions -!isIncluded("common/Util.js") && include("common/Util.js"); - -let sdk = require("../modules/sdk"); - -(function (global, original) { - let firstRun = true; - global.getUnit = function (...args) { - if (firstRun) { - delay(1500); - firstRun = false; - } - - // Stupid reference thing - // eslint-disable-next-line no-unused-vars - const test = original(-1); - - let [first] = args, second = args.length >= 2 ? args[1] : undefined; - - const ret = original.apply(this, args); - - // deal with bug - if (first === 1 && typeof second === "string" && ret - && ((me.act === 1 && ret.classid === sdk.monsters.Dummy1) || me.act === 2 && ret.classid === sdk.monsters.Dummy2)) { - return null; - } - - return original.apply(this, args); - }; -})([].filter.constructor("return this")(), getUnit); - -// Check if unit is idle -Unit.prototype.__defineGetter__("idle", function () { - if (this.type > sdk.unittype.Player) throw new Error("Unit.idle: Must be used with player units."); - // Dead is pretty idle too - return (this.mode === sdk.player.mode.StandingOutsideTown || this.mode === sdk.player.mode.StandingInTown || this.mode === sdk.player.mode.Dead); -}); - -Unit.prototype.__defineGetter__("gold", function () { - return this.getStat(sdk.stats.Gold) + this.getStat(sdk.stats.GoldBank); -}); - -// Death check -Unit.prototype.__defineGetter__("dead", function () { - switch (this.type) { - case sdk.unittype.Player: - return this.mode === sdk.player.mode.Death || this.mode === sdk.player.mode.Dead; - case sdk.unittype.Monster: - return this.mode === sdk.monsters.mode.Death || this.mode === sdk.monsters.mode.Dead; - default: - return false; - } -}); - -// Check if unit is in town -Unit.prototype.__defineGetter__("inTown", function () { - if (this.type > sdk.unittype.Player) throw new Error("Unit.inTown: Must be used with player units."); - return sdk.areas.Towns.includes(this.area); -}); - -// Check if party unit is in town -Party.prototype.__defineGetter__("inTown", function () { - return sdk.areas.Towns.includes(this.area); -}); - -Unit.prototype.__defineGetter__("attacking", function () { - if (this.type > sdk.unittype.Monster) throw new Error("Unit.attacking: Must be used with Monster or Player units."); - switch (this.type) { - case sdk.unittype.Player: - return [ - sdk.player.mode.Attacking1, sdk.player.mode.Attacking2, sdk.player.mode.CastingSkill, sdk.player.mode.ThrowingItem, - sdk.player.mode.Kicking, sdk.player.mode.UsingSkill1, sdk.player.mode.UsingSkill2, sdk.player.mode.UsingSkill3, - sdk.player.mode.UsingSkill4, sdk.player.mode.SkillActionSequence - ].includes(this.mode); - case sdk.unittype.Monster: - return [ - sdk.monsters.mode.Attacking1, sdk.monsters.mode.Attacking2, sdk.monsters.mode.CastingSkill, - sdk.monsters.mode.UsingSkill1, sdk.monsters.mode.UsingSkill2, sdk.monsters.mode.UsingSkill3, sdk.monsters.mode.UsingSkill4 - ].includes(this.mode); - default: - return false; - } -}); - -Unit.prototype.__defineGetter__("durabilityPercent", function () { - if (this.type !== sdk.unittype.Item) throw new Error("Unit.durabilityPercent: Must be used on items."); - if (this.getStat(sdk.stats.Quantity) || !this.getStat(sdk.stats.MaxDurability)) return 100; - return Math.round(this.getStat(sdk.stats.Durability) * 100 / this.getStat(sdk.stats.MaxDurability)); -}); - -// Open NPC menu -Unit.prototype.openMenu = function (addDelay) { - if (Config.PacketShopping) return Packet.openMenu(this); - if (this.type !== sdk.unittype.NPC) throw new Error("Unit.openMenu: Must be used on NPCs."); - if (getUIFlag(sdk.uiflags.NPCMenu)) return true; - - addDelay === undefined && (addDelay = 0); - let pingDelay = (me.gameReady ? me.ping : 125); - - for (let i = 0; i < 5; i += 1) { - getDistance(me, this) > 4 && Pather.moveToUnit(this); - - Misc.click(0, 0, this); - let tick = getTickCount(); - - while (getTickCount() - tick < 5000) { - if (getUIFlag(sdk.uiflags.NPCMenu)) { - delay(Math.max(700 + pingDelay, 500 + pingDelay * 2 + addDelay * 500)); - - return true; - } - - if (getInteractedNPC() && getTickCount() - tick > 1000) { - me.cancel(); - } - - delay(100); - } - - sendPacket(1, sdk.packets.send.NPCInit, 4, 1, 4, this.gid); - delay(pingDelay * 2 + 1); - Packet.cancelNPC(this); - delay(pingDelay * 2 + 1); - Packet.flash(me.gid); - } - - return false; -}; - -// mode = "Gamble", "Repair" or "Shop" -Unit.prototype.startTrade = function (mode) { - if (Config.PacketShopping) return Packet.startTrade(this, mode); - if (this.type !== sdk.unittype.NPC) throw new Error("Unit.startTrade: Must be used on NPCs."); - console.log("Starting " + mode + " at " + this.name); - if (getUIFlag(sdk.uiflags.Shop)) return true; - - let menuId = mode === "Gamble" ? sdk.menu.Gamble : mode === "Repair" ? sdk.menu.TradeRepair : sdk.menu.Trade; - - for (let i = 0; i < 3; i += 1) { - // Incremental delay on retries - if (this.openMenu(i)) { - Misc.useMenu(menuId); - - let tick = getTickCount(); - - while (getTickCount() - tick < 1000) { - if (getUIFlag(sdk.uiflags.Shop) && this.itemcount > 0) { - delay(200); - console.log("Successfully started " + mode + " at " + this.name); - - return true; - } - - delay(25); - } - - me.cancel(); - } - } - - return false; -}; - -Unit.prototype.buy = function (shiftBuy, gamble) { - if (Config.PacketShopping) return Packet.buyItem(this, shiftBuy, gamble); - // Check if it's an item we want to buy - if (this.type !== sdk.unittype.Item) throw new Error("Unit.buy: Must be used on items."); - - // Check if it's an item belonging to a NPC - if (!getUIFlag(sdk.uiflags.Shop) || (this.getParent() && this.getParent().gid !== getInteractedNPC().gid)) { - throw new Error("Unit.buy: Must be used in shops."); - } - - // Can we afford the item? - if (me.gold < this.getItemCost(sdk.items.cost.ToBuy)) return false; - - let oldGold = me.gold; - let itemCount = me.itemcount; - - for (let i = 0; i < 3; i += 1) { - this.shop(shiftBuy ? 6 : 2); - - let tick = getTickCount(); - - while (getTickCount() - tick < Math.max(2000, me.ping * 2 + 500)) { - if ((shiftBuy && me.gold < oldGold) || itemCount !== me.itemcount) { - delay(500); - - return true; - } - - delay(10); - } - } - - return false; -}; - -// Item owner name -Unit.prototype.__defineGetter__("parentName", - function () { - if (this.type !== sdk.unittype.Item) throw new Error("Unit.parentName: Must be used with item units."); - - let parent = this.getParent(); - - return parent ? parent.name : false; - }); - -// You MUST use a delay after Unit.sell() if using custom scripts. delay(500) works best, dynamic delay is used when identifying/selling (500 - item id time) -Unit.prototype.sell = function () { - if (Config.PacketShopping) return Packet.sellItem(this); - - // Check if it's an item we want to buy - if (this.type !== sdk.unittype.Item) throw new Error("Unit.sell: Must be used on items."); - if (!this.sellable) { - console.error((new Error("Item is unsellable"))); - return false; - } - - // Check if it's an item belonging to a NPC - if (!getUIFlag(sdk.uiflags.Shop)) throw new Error("Unit.sell: Must be used in shops."); - - let itemCount = me.itemcount; - - for (let i = 0; i < 5; i += 1) { - this.shop(1); - - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (me.itemcount !== itemCount) { - //delay(500); - - return true; - } - - delay(10); - } - } - - return false; -}; - -Unit.prototype.toCursor = function (usePacket = false) { - if (this.type !== sdk.unittype.Item) throw new Error("Unit.toCursor: Must be used with items."); - if (me.itemoncursor && this.mode === sdk.items.mode.onCursor) return true; - - this.location === sdk.storage.Stash && Town.openStash(); - this.location === sdk.storage.Cube && Cubing.openCube(); - - if (usePacket) return Packet.itemToCursor(this); - - for (let i = 0; i < 3; i += 1) { - try { - if (this.mode === sdk.items.mode.Equipped) { - // fix for equipped items (cubing viper staff for example) - clickItem(sdk.clicktypes.click.item.Left, this.bodylocation); - } else { - clickItem(sdk.clicktypes.click.item.Left, this); - } - } catch (e) { - return false; - } - - let tick = getTickCount(); - - while (getTickCount() - tick < 1000) { - if (me.itemoncursor) { - delay(200); - - return true; - } - - delay(10); - } - } - - return false; -}; - -Unit.prototype.drop = function () { - if (this.type !== sdk.unittype.Item) throw new Error("Unit.drop: Must be used with items. Unit Name: " + this.name); - if (!this.toCursor()) return false; - - let tick = getTickCount(); - let timeout = Math.max(1000, me.ping * 6); - - while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash) || !me.gameReady) { - if (getTickCount() - tick > timeout) return false; - - if (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { - me.cancel(0); - } - - delay(me.ping * 2 + 100); - } - - for (let i = 0; i < 3; i += 1) { - clickMap(0, 0, me.x, me.y); - delay(40); - clickMap(2, 0, me.x, me.y); - - tick = getTickCount(); - - while (getTickCount() - tick < 500) { - if (!me.itemoncursor) { - delay(200); - - return true; - } - - delay(10); - } - } - - return false; -}; - -me.walk = () => me.runwalk = 0; -me.run = () => me.runwalk = 1; - -// calling me.ping can cause issues, use this instead to assign a value -// might need work to be more accurate but works for now -me.getPingDelay = function () { - // single-player - if (!me.gameserverip) return 25; - let pingDelay = me.gameReady ? me.ping : 250; - pingDelay < 10 && (pingDelay = 50); - return pingDelay; -}; - -/** - * @description use consumable item, fixes issue with interact() returning false even if we used an item - * @returns boolean - */ -Unit.prototype.use = function () { - if (this === undefined || !this.type) return false; - if (this.type !== sdk.unittype.Item) throw new Error("Unit.use: Must be used with items. Unit Name: " + this.name); - if (!getBaseStat("items", this.classid, "useable")) throw new Error("Unit.use: Must be used with consumable items. Unit Name: " + this.name); - - let gid = this.gid; - let pingDelay = me.getPingDelay(); - let quantity = 0; - let iType = this.itemType; - let checkQuantity = false; - - switch (this.location) { - case sdk.storage.Stash: - case sdk.storage.Inventory: - if (this.isInStash && !Town.openStash()) return false; - // doesn't work, not sure why but it's missing something - //new PacketBuilder().byte(sdk.packets.send.UseItem).dword(gid).dword(this.x).dword(this.y).send(); - checkQuantity = iType === sdk.items.type.Book; - checkQuantity && (quantity = this.getStat(sdk.stats.Quantity)); - this.interact(); // use interact instead, was hoping to skip this since its really just doing the same thing over but oh well - - break; - case sdk.storage.Belt: - new PacketBuilder().byte(sdk.packets.send.UseBeltItem).dword(gid).dword(0).dword(0).send(); - - break; - default: - return false; - } - - if (checkQuantity) { - return Misc.poll(() => this.getStat(sdk.stats.Quantity) < quantity, 200 + pingDelay, 50); - } else { - return Misc.poll(() => !Game.getItem(-1, -1, gid), 200 + pingDelay, 50); - } -}; - -me.findItem = function (id = -1, mode = -1, loc = -1, quality = -1) { - let item = me.getItem(id, mode); - - if (item) { - do { - if ((loc === -1 || item.location === loc) && (quality === -1 || item.quality === quality)) { - return item; - } - } while (item.getNext()); - } - - return false; -}; - -me.findItems = function (id = -1, mode = -1, loc = false) { - let list = []; - let item = me.getItem(id, mode); - - if (item) { - do { - if (loc) { - if (item.location === loc) { - list.push(copyUnit(item)); - } - } else { - list.push(copyUnit(item)); - } - } while (item.getNext()); - } - - return list; -}; - -me.cancelUIFlags = function () { - while (!me.gameReady) { - delay(25); - } - - const flags = [ - sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.SkillWindow, sdk.uiflags.NPCMenu, - sdk.uiflags.Waypoint, sdk.uiflags.Party, sdk.uiflags.Shop, sdk.uiflags.Quest, sdk.uiflags.Stash, - sdk.uiflags.Cube, sdk.uiflags.KeytotheCairnStonesScreen, sdk.uiflags.SubmitItem - ]; - - for (let i = 0; i < flags.length; i++) { - if (getUIFlag(flags[i]) && me.cancel()) { - delay(250); - i = 0; // Reset - } - } -}; - -me.switchWeapons = function (slot) { - if (this.gametype === sdk.game.gametype.Classic || (slot !== undefined && this.weaponswitch === slot)) { - return true; - } - - while (typeof me !== "object") { - delay(10); - } - - while (!me.gameReady) { - delay(25); - } - - let originalSlot = this.weaponswitch; - let switched = false; - let packetHandler = (bytes) => bytes.length > 0 && bytes[0] === sdk.packets.recv.WeaponSwitch && (switched = true) && false; // false to not block - addEventListener("gamepacket", packetHandler); - try { - for (let i = 0; i < 10; i += 1) { - for (let j = 10; --j && me.idle;) { - delay(3); - } - - i > 0 && delay(10); - !switched && sendPacket(1, sdk.packets.send.SwapWeapon); // Swap weapons - - let tick = getTickCount(); - while (getTickCount() - tick < 300) { - if (switched || originalSlot !== me.weaponswitch) { - delay(50); - return true; - } - - delay(3); - } - // Retry - } - } finally { - removeEventListener("gamepacket", packetHandler); - } - - return false; -}; - -// Returns the number of frames needed to cast a given skill at a given FCR for a given char. -me.castingFrames = function (skillId, fcr, charClass) { - if (skillId === undefined) return 0; - - fcr === undefined && (fcr = me.FCR); - charClass === undefined && (charClass = this.classid); - - // https://diablo.fandom.com/wiki/Faster_Cast_Rate - let effectiveFCR = Math.min(75, Math.floor(fcr * 120 / (fcr + 120)) | 0); - let isLightning = skillId === sdk.skills.Lightning || skillId === sdk.skills.ChainLightning; - let baseCastRate = [20, isLightning ? 19 : 14, 16, 16, 14, 15, 17][charClass]; - let animationSpeed = { - normal: 256, - human: 208, - wolf: 229, - bear: 228 - }[charClass === sdk.player.class.Druid ? (me.getState(sdk.states.Wolf) || me.getState(sdk.states.Bear)) : "normal"]; - return Math.ceil(256 * baseCastRate / Math.floor(animationSpeed * (100 + effectiveFCR) / 100) - (isLightning ? 0 : 1)); -}; - -// Returns the duration in seconds needed to cast a given skill at a given FCR for a given char. -me.castingDuration = function (skillId, fcr = me.FCR, charClass = me.classid) { - return (me.castingFrames(skillId, fcr, charClass) / 25); -}; - -me.getWeaponQuantity = function (weaponLoc = sdk.body.RightArm) { - let currItem = me.getItemsEx(-1, sdk.items.mode.Equipped).filter(i => i.bodylocation === weaponLoc).first(); - return !!currItem ? currItem.getStat(sdk.stats.Quantity) : 0; -}; - -/** - * @description Returns item given by itemInfo - * @param itemInfo object - - * { - * classid: Number, - * itemtype: Number, - * quality: Number, - * runeword: Boolean, - * ethereal: Boolean, - * name: getLocaleString(id) || localeStringId, - * equipped: Boolean || Number (bodylocation) - * } - * @returns Unit[] - */ -Unit.prototype.checkItem = function (itemInfo) { - if (this === undefined || this.type > 1 || typeof itemInfo !== "object") return {have: false, item: null}; - - const itemObj = Object.assign({}, { - classid: -1, - itemtype: -1, - quality: -1, - runeword: null, - ethereal: null, - equipped: null, - basetype: null, - name: "" - }, itemInfo); - - // convert id into string - typeof itemObj.name === "number" && (itemObj.name = getLocaleString(itemObj.name)); - - let items = this.getItemsEx() - .filter(function (item) { - return (!item.questItem - && (itemObj.classid === -1 || item.classid === itemObj.classid) - && (itemObj.itemtype === -1 || item.itemType === itemObj.itemtype) - && (itemObj.quality === -1 || item.quality === itemObj.quality) - && (itemObj.runeword === null || (item.runeword === itemObj.runeword)) - && (itemObj.ethereal === null || (item.ethereal === itemObj.ethereal)) - && (itemObj.equipped === null || (typeof itemObj.equipped === "number" ? item.bodylocation === itemObj.equipped : item.isEquipped === itemObj.equipped)) - && (itemObj.basetype === null || ((item.normal || item.superior) === itemObj.basetype)) - && (!itemObj.name || item.fname.toLowerCase().includes(itemObj.name.toLowerCase())) - ); - }); - if (items.length > 0) { - return { - have: true, - item: copyUnit(items.first()) - }; - } else { - return { - have: false, - item: null - }; - } -}; - -/** - * @description Returns first item given by itemInfo - * @param itemInfo array of objects - - * { - * classid: Number, - * itemtype: Number, - * quality: Number, - * runeword: Boolean, - * ethereal: Boolean, - * name: getLocaleString(id) || localeStringId, - * equipped: Boolean || Number (bodylocation) - * } - * @returns Unit[] - */ -Unit.prototype.findFirst = function (itemInfo = []) { - if (this === undefined || this.type > 1) return {have: false, item: null}; - if (!Array.isArray(itemInfo) || typeof itemInfo[0] !== "object") return {have: false, item: null}; - let itemList = this.getItemsEx(); - - for (let i = 0; i < itemInfo.length; i++) { - const itemObj = Object.assign({}, { - classid: -1, - itemtype: -1, - quality: -1, - runeword: null, - ethereal: null, - equipped: null, - name: "" - }, itemInfo[i]); - - // convert id into string - typeof itemObj.name === "number" && (itemObj.name = getLocaleString(itemObj.name)); - - let items = itemList - .filter(function (item) { - return (!item.questItem - && (itemObj.classid === -1 || item.classid === itemObj.classid) - && (itemObj.itemtype === -1 || item.itemType === itemObj.itemtype) - && (itemObj.quality === -1 || item.quality === itemObj.quality) - && (itemObj.runeword === null || (item.runeword === itemObj.runeword)) - && (itemObj.ethereal === null || (item.ethereal === itemObj.ethereal)) - && (itemObj.equipped === null || (typeof itemObj.equipped === "number" ? item.bodylocation === itemObj.equipped : item.isEquipped === itemObj.equipped)) - && (!itemObj.name || item.fname.toLowerCase().includes(itemObj.name.toLowerCase())) - ); - }); - if (items.length > 0) { - return { - have: true, - item: copyUnit(items.first()) - }; - } - } - - return { - have: false, - item: null - }; -}; - -/** - * @description Returns boolean if we have all the items given by itemInfo - * @param itemInfo array of objects - - * { - * classid: Number, - * itemtype: Number, - * quality: Number, - * runeword: Boolean, - * ethereal: Boolean, - * name: getLocaleString(id) || localeStringId, - * equipped: Boolean || Number (bodylocation) - * } - * @returns Boolean - */ -Unit.prototype.haveAll = function (itemInfo = [], returnIfSome = false) { - if (this === undefined || this.type > 1) return false; - // if an object but not an array convert to array - !Array.isArray(itemInfo) && typeof itemInfo === "object" && (itemInfo = [itemInfo]); - if (!Array.isArray(itemInfo) || typeof itemInfo[0] !== "object") return false; - let itemList = this.getItemsEx(); - let haveAll = false; - let checkedGids = []; - - for (let i = 0; i < itemInfo.length; i++) { - const itemObj = Object.assign({}, { - classid: -1, - itemtype: -1, - quality: -1, - runeword: null, - ethereal: null, - equipped: null, - basetype: null, - name: "" - }, itemInfo[i]); - - // convert id into string - typeof itemObj.name === "number" && (itemObj.name = getLocaleString(itemObj.name)); - - let items = itemList - .filter(function (item) { - return (!item.questItem - && (checkedGids.indexOf(item.gid) === -1) - && (itemObj.classid === -1 || item.classid === itemObj.classid) - && (itemObj.itemtype === -1 || item.itemType === itemObj.itemtype) - && (itemObj.quality === -1 || item.quality === itemObj.quality) - && (itemObj.runeword === null || (item.runeword === itemObj.runeword)) - && (itemObj.ethereal === null || (item.ethereal === itemObj.ethereal)) - && (itemObj.equipped === null || (typeof itemObj.equipped === "number" ? item.bodylocation === itemObj.equipped : item.isEquipped === itemObj.equipped)) - && (itemObj.basetype === null || ((item.normal || item.superior) === itemObj.basetype)) - && (!itemObj.name.length || item.fname.toLowerCase().includes(itemObj.name.toLowerCase())) - ); - }); - if (items.length > 0) { - if (returnIfSome) return true; - checkedGids.push(items.first().gid); - haveAll = true; - } else { - if (returnIfSome) continue; - return false; - } - } - - return haveAll; -}; - -Unit.prototype.haveSome = function (itemInfo = []) { - return this.haveAll(itemInfo, true); -}; - -/** - * @description Return the items of a player, or an empty array - * @param args - * @returns Unit[] - */ -Unit.prototype.getItems = function (...args) { - let items = []; - let item = this.getItem.apply(this, args); - - if (item) { - do { - items.push(copyUnit(item)); - } while (item.getNext()); - } - - return Array.isArray(items) ? items : []; -}; - -Unit.prototype.getItemsEx = function (...args) { - let items = []; - let item = this.getItem.apply(this, args); - - if (item) { - do { - items.push(copyUnit(item)); - } while (item.getNext()); - } - - return items; -}; - -Unit.prototype.getPrefix = function (id) { - switch (typeof id) { - case "number": - if (typeof this.prefixnums !== "object") return this.prefixnum === id; - - for (let i = 0; i < this.prefixnums.length; i += 1) { - if (id === this.prefixnums[i]) { - return true; - } - } - - break; - case "string": - if (typeof this.prefixes !== "object") { - return this.prefix.replace(/\s+/g, "").toLowerCase() === id.replace(/\s+/g, "").toLowerCase(); - } - - for (let i = 0; i < this.prefixes.length; i += 1) { - if (id.replace(/\s+/g, "").toLowerCase() === this.prefixes[i].replace(/\s+/g, "").toLowerCase()) { - return true; - } - } - - break; - } - - return false; -}; - -Unit.prototype.getSuffix = function (id) { - switch (typeof id) { - case "number": - if (typeof this.suffixnums !== "object") return this.suffixnum === id; - - for (let i = 0; i < this.suffixnums.length; i += 1) { - if (id === this.suffixnums[i]) { - return true; - } - } - - break; - case "string": - if (typeof this.suffixes !== "object") { - return this.suffix.replace(/\s+/g, "").toLowerCase() === id.replace(/\s+/g, "").toLowerCase(); - } - - for (let i = 0; i < this.suffixes.length; i += 1) { - if (id.replace(/\s+/g, "").toLowerCase() === this.suffixes[i].replace(/\s+/g, "").toLowerCase()) { - return true; - } - } - - break; - } - - return false; -}; - -Unit.prototype.__defineGetter__("dexreq", - function () { - let ethereal = this.getFlag(sdk.items.flags.Ethereal); - let reqModifier = this.getStat(sdk.stats.ReqPercent); - let baseReq = getBaseStat("items", this.classid, "reqdex"); - let finalReq = baseReq + Math.floor(baseReq * reqModifier / 100) - (ethereal ? 10 : 0); - - return Math.max(finalReq, 0); - }); - -Unit.prototype.__defineGetter__("strreq", - function () { - let ethereal = this.getFlag(sdk.items.flags.Ethereal); - let reqModifier = this.getStat(sdk.stats.ReqPercent); - let baseReq = getBaseStat("items", this.classid, "reqstr"); - let finalReq = baseReq + Math.floor(baseReq * reqModifier / 100) - (ethereal ? 10 : 0); - - return Math.max(finalReq, 0); - }); - -Unit.prototype.__defineGetter__("itemclass", - function () { - if (getBaseStat("items", this.classid, "code") === undefined) return 0; - if (getBaseStat("items", this.classid, "code") === getBaseStat(0, this.classid, "ultracode")) return 2; - if (getBaseStat("items", this.classid, "code") === getBaseStat(0, this.classid, "ubercode")) return 1; - - return 0; - }); - -Unit.prototype.getStatEx = function (id, subid) { - let temp, rval, regex; - - switch (id) { - case sdk.stats.AllRes: - // calculates all res, doesn't exist though - // Block scope due to the variable declaration - { - // Get all res - let allres = [ - this.getStatEx(sdk.stats.FireResist), - this.getStatEx(sdk.stats.ColdResist), - this.getStatEx(sdk.stats.LightningResist), - this.getStatEx(sdk.stats.PoisonResist) - ]; - - // What is the minimum of the 4? - let min = Math.min.apply(null, allres); - - // Cap all res to the minimum amount of res - allres = allres.map(res => res > min ? min : res); - - // Get it in local variables, its more easy to read - let [fire, cold, light, psn] = allres; - - return fire === cold && cold === light && light === psn ? min : 0; - } - case sdk.stats.ToBlock: - switch (this.classid) { - case sdk.items.Buckler: - return this.getStat(sdk.stats.ToBlock); - case sdk.items.PreservedHead: - case sdk.items.MummifiedTrophy: - case sdk.items.MinionSkull: - return this.getStat(sdk.stats.ToBlock) - 3; - case sdk.items.SmallShield: - case sdk.items.ZombieHead: - case sdk.items.FetishTrophy: - case sdk.items.HellspawnSkull: - return this.getStat(sdk.stats.ToBlock) - 5; - case sdk.items.KiteShield: - case sdk.items.UnravellerHead: - case sdk.items.SextonTrophy: - case sdk.items.OverseerSkull: - return this.getStat(sdk.stats.ToBlock) - 8; - case sdk.items.SpikedShield: - case sdk.items.Defender: - case sdk.items.GargoyleHead: - case sdk.items.CantorTrophy: - case sdk.items.SuccubusSkull: - case sdk.items.Targe: - case sdk.items.AkaranTarge: - return this.getStat(sdk.stats.ToBlock) - 10; - case sdk.items.LargeShield: - case sdk.items.RoundShield: - case sdk.items.DemonHead: - case sdk.items.HierophantTrophy: - case sdk.items.BloodlordSkull: - return this.getStat(sdk.stats.ToBlock) - 12; - case sdk.items.Scutum: - return this.getStat(sdk.stats.ToBlock) - 14; - case sdk.items.Rondache: - case sdk.items.AkaranRondache: - return this.getStat(sdk.stats.ToBlock) - 15; - case sdk.items.GothicShield: - case sdk.items.AncientShield: - return this.getStat(sdk.stats.ToBlock) - 16; - case sdk.items.BarbedShield: - return this.getStat(sdk.stats.ToBlock) - 17; - case sdk.items.DragonShield: - return this.getStat(sdk.stats.ToBlock) - 18; - case sdk.items.VortexShield: - return this.getStat(sdk.stats.ToBlock) - 19; - case sdk.items.BoneShield: - case sdk.items.GrimShield: - case sdk.items.Luna: - case sdk.items.BladeBarrier: - case sdk.items.TrollNest: - case sdk.items.HeraldicShield: - case sdk.items.ProtectorShield: - return this.getStat(sdk.stats.ToBlock) - 20; - case sdk.items.Heater: - case sdk.items.Monarch: - case sdk.items.AerinShield: - case sdk.items.GildedShield: - case sdk.items.ZakarumShield: - return this.getStat(sdk.stats.ToBlock) - 22; - case sdk.items.TowerShield: - case sdk.items.Pavise: - case sdk.items.Hyperion: - case sdk.items.Aegis: - case sdk.items.Ward: - return this.getStat(sdk.stats.ToBlock) - 24; - case sdk.items.CrownShield: - case sdk.items.RoyalShield: - case sdk.items.KurastShield: - return this.getStat(sdk.stats.ToBlock) - 25; - case sdk.items.SacredRondache: - return this.getStat(sdk.stats.ToBlock) - 28; - case sdk.items.SacredTarge: - return this.getStat(sdk.stats.ToBlock) - 30; - } - - break; - case sdk.stats.MinDamage: - case sdk.stats.MaxDamage: - if (subid === 1) { - temp = this.getStat(-1); - rval = 0; - - for (let i = 0; i < temp.length; i += 1) { - switch (temp[i][0]) { - case id: // plus one handed dmg - case id + 2: // plus two handed dmg - // There are 2 occurrences of min/max if the item has +damage. Total damage is the sum of both. - // First occurrence is +damage, second is base item damage. - - if (rval) { // First occurence stored, return if the second one exists - return rval; - } - - if (this.getStat(temp[i][0]) > 0 && this.getStat(temp[i][0]) > temp[i][2]) { - rval = temp[i][2]; // Store the potential +dmg value - } - - break; - } - } - - return 0; - } - - break; - case sdk.stats.Defense: - if (subid === 0) { - if ([0, 1].indexOf(this.mode) < 0) { - break; - } - - switch (this.itemType) { - case sdk.items.type.Jewel: - case sdk.items.type.SmallCharm: - case sdk.items.type.LargeCharm: - case sdk.items.type.GrandCharm: - // defense is the same as plusdefense for these items - return this.getStat(sdk.stats.Defense); - } - - // can fail sometimes - !this.desc && (this.desc = this.description); - - if (this.desc) { - temp = this.desc.split("\n"); - regex = new RegExp("\\+\\d+ " + getLocaleString(sdk.locale.text.Defense).replace(/^\s+|\s+$/g, "")); - - for (let i = 0; i < temp.length; i += 1) { - if (temp[i].match(regex, "i")) { - return parseInt(temp[i].replace(/ÿc[0-9!"+<;.*]/, ""), 10); - } - } - } - - return 0; - } - - break; - case sdk.stats.PoisonMinDamage: - if (subid === 1) { - return Math.round(this.getStat(sdk.stats.PoisonMinDamage) * this.getStat(sdk.stats.PoisonLength) / 256); - } - - break; - case sdk.stats.AddClassSkills: - if (subid === undefined) { - for (let i = 0; i < 7; i += 1) { - let cSkill = this.getStat(sdk.stats.AddClassSkills, i); - if (cSkill) return cSkill; - } - - return 0; - } - - break; - case sdk.stats.AddSkillTab: - if (subid === undefined) { - temp = Object.values(sdk.skills.tabs); - - for (let i = 0; i < temp.length; i += 1) { - let sTab = this.getStat(sdk.stats.AddSkillTab, temp[i]); - if (sTab) return sTab; - } - - return 0; - } - - break; - case sdk.stats.SkillOnAttack: - case sdk.stats.SkillOnKill: - case sdk.stats.SkillOnDeath: - case sdk.stats.SkillOnStrike: - case sdk.stats.SkillOnLevelUp: - case sdk.stats.SkillWhenStruck: - case sdk.stats.ChargedSkill: - if (subid === 1) { - temp = this.getStat(-2); - - if (temp.hasOwnProperty(id)) { - if (temp[id] instanceof Array) { - for (let i = 0; i < temp[id].length; i += 1) { - if (temp[id][i] !== undefined) { - return temp[id][i].skill; - } - } - } else { - return temp[id].skill; - } - } - - return 0; - } - - if (subid === 2) { - temp = this.getStat(-2); - - if (temp.hasOwnProperty(id)) { - if (temp[id] instanceof Array) { - for (let i = 0; i < temp[id].length; i += 1) { - if (temp[id][i] !== undefined) { - return temp[id][i].level; - } - } - } else { - return temp[id].level; - } - } - - return 0; - } - - break; - case sdk.stats.PerLevelHp: // (for example Fortitude with hp per lvl can be defined now with 1.5) - return this.getStat(sdk.stats.PerLevelHp) / 2048; - } - - if (this.getFlag(sdk.items.flags.Runeword)) { - switch (id) { - case sdk.stats.ArmorPercent: - if ([0, 1].indexOf(this.mode) < 0) { - break; - } - - !this.desc && (this.desc = this.description); - - if (this.desc) { - temp = this.desc.split("\n"); - - for (let i = 0; i < temp.length; i += 1) { - if (temp[i].match(getLocaleString(sdk.locale.text.EnhancedDefense).replace(/^\s+|\s+$/g, ""), "i")) { - return parseInt(temp[i].replace(/ÿc[0-9!"+<;.*]/, ""), 10); - } - } - } - - return 0; - case sdk.stats.EnhancedDamage: - if ([0, 1].indexOf(this.mode) < 0) { - break; - } - - !this.desc && (this.desc = this.description); - - if (this.desc) { - temp = this.desc.split("\n"); - - for (let i = 0; i < temp.length; i += 1) { - if (temp[i].match(getLocaleString(sdk.locale.text.EnhancedDamage).replace(/^\s+|\s+$/g, ""), "i")) { - return parseInt(temp[i].replace(/ÿc[0-9!"+<;.*]/, ""), 10); - } - } - } - - return 0; - } - } - - return (subid === undefined ? this.getStat(id) : this.getStat(id, subid)); -}; - -/* - _NTIPAliasColor["black"] = 3; - _NTIPAliasColor["lightblue"] = 4; - _NTIPAliasColor["darkblue"] = 5; - _NTIPAliasColor["crystalblue"] = 6; - _NTIPAliasColor["lightred"] = 7; - _NTIPAliasColor["darkred"] = 8; - _NTIPAliasColor["crystalred"] = 9; - _NTIPAliasColor["darkgreen"] = 11; - _NTIPAliasColor["crystalgreen"] = 12; - _NTIPAliasColor["lightyellow"] = 13; - _NTIPAliasColor["darkyellow"] = 14; - _NTIPAliasColor["lightgold"] = 15; - _NTIPAliasColor["darkgold"] = 16; - _NTIPAliasColor["lightpurple"] = 17; - _NTIPAliasColor["orange"] = 19; - _NTIPAliasColor["white"] = 20; -*/ - -Unit.prototype.getColor = function () { - let colors; - let Color = { - black: 3, - lightblue: 4, - darkblue: 5, - crystalblue: 6, - lightred: 7, - darkred: 8, - crystalred: 9, - darkgreen: 11, - crystalgreen: 12, - lightyellow: 13, - darkyellow: 14, - lightgold: 15, - darkgold: 16, - lightpurple: 17, - orange: 19, - white: 20 - }; - - // check type - switch (this.itemType) { - case sdk.items.type.Shield: - case sdk.items.type.Armor: - case sdk.items.type.Boots: - case sdk.items.type.Gloves: - case sdk.items.type.Belt: - case sdk.items.type.AuricShields: - case sdk.items.type.VoodooHeads: - case sdk.items.type.Helm: - case sdk.items.type.PrimalHelm: - case sdk.items.type.Circlet: - case sdk.items.type.Pelt: - case sdk.items.type.Scepter: - case sdk.items.type.Wand: - case sdk.items.type.Staff: - case sdk.items.type.Bow: - case sdk.items.type.Axe: - case sdk.items.type.Club: - case sdk.items.type.Sword: - case sdk.items.type.Hammer: - case sdk.items.type.Knife: - case sdk.items.type.Spear: - case sdk.items.type.Polearm: - case sdk.items.type.Crossbow: - case sdk.items.type.Mace: - case sdk.items.type.ThrowingKnife: - case sdk.items.type.ThrowingAxe: - case sdk.items.type.Javelin: - case sdk.items.type.Orb: - case sdk.items.type.AmazonBow: - case sdk.items.type.AmazonSpear: - case sdk.items.type.AmazonJavelin: - case sdk.items.type.MissilePotion: - case sdk.items.type.HandtoHand: - case sdk.items.type.AssassinClaw: - break; - default: - return -1; - } - - // check quality - if ([sdk.items.quality.Magic, sdk.items.quality.Set, sdk.items.quality.Rare, sdk.items.quality.Unique].indexOf(this.quality) === -1) { - return -1; - } - - if (this.quality === sdk.items.quality.Magic || this.quality === sdk.items.quality.Rare) { - colors = { - "Screaming": Color.orange, - "Howling": Color.orange, - "Wailing": Color.orange, - "Sapphire": Color.lightblue, - "Snowy": Color.lightblue, - "Shivering": Color.lightblue, - "Boreal": Color.lightblue, - "Hibernal": Color.lightblue, - "Ruby": Color.lightred, - "Amber": Color.lightyellow, - "Static": Color.lightyellow, - "Glowing": Color.lightyellow, - "Buzzing": Color.lightyellow, - "Arcing": Color.lightyellow, - "Shocking": Color.lightyellow, - "Emerald": Color.crystalgreen, - "Saintly": Color.darkgold, - "Holy": Color.darkgold, - "Godly": Color.darkgold, - "Visionary": Color.white, - "Mnemonic": Color.crystalblue, - "Bowyer's": Color.lightgold, - "Gymnastic": Color.lightgold, - "Spearmaiden's": Color.lightgold, - "Archer's": Color.lightgold, - "Athlete's": Color.lightgold, - "Lancer's": Color.lightgold, - "Charged": Color.lightgold, - "Blazing": Color.lightgold, - "Freezing": Color.lightgold, - "Glacial": Color.lightgold, - "Powered": Color.lightgold, - "Volcanic": Color.lightgold, - "Blighting": Color.lightgold, - "Noxious": Color.lightgold, - "Mojo": Color.lightgold, - "Cursing": Color.lightgold, - "Venomous": Color.lightgold, - "Golemlord's": Color.lightgold, - "Warden's": Color.lightgold, - "Hawk Branded": Color.lightgold, - "Commander's": Color.lightgold, - "Marshal's": Color.lightgold, - "Rose Branded": Color.lightgold, - "Guardian's": Color.lightgold, - "Veteran's": Color.lightgold, - "Resonant": Color.lightgold, - "Raging": Color.lightgold, - "Echoing": Color.lightgold, - "Furious": Color.lightgold, - "Master's": Color.lightgold, // there's 2x masters... - "Caretaker's": Color.lightgold, - "Terrene": Color.lightgold, - "Feral": Color.lightgold, - "Gaean": Color.lightgold, - "Communal": Color.lightgold, - "Keeper's": Color.lightgold, - "Sensei's": Color.lightgold, - "Trickster's": Color.lightgold, - "Psychic": Color.lightgold, - "Kenshi's": Color.lightgold, - "Cunning": Color.lightgold, - "Shadow": Color.lightgold, - "Faithful": Color.white, - "Priest's": Color.crystalgreen, - "Dragon's": Color.crystalblue, - "Vulpine": Color.crystalblue, - "Shimmering": Color.lightpurple, - "Rainbow": Color.lightpurple, - "Scintillating": Color.lightpurple, - "Prismatic": Color.lightpurple, - "Chromatic": Color.lightpurple, - "Hierophant's": Color.crystalgreen, - "Berserker's": Color.crystalgreen, - "Necromancer's": Color.crystalgreen, - "Witch-hunter's": Color.crystalgreen, - "Arch-Angel's": Color.crystalgreen, - "Valkyrie's": Color.crystalgreen, - "Massive": Color.darkgold, - "Savage": Color.darkgold, - "Merciless": Color.darkgold, - "Ferocious": Color.black, - "Grinding": Color.white, - "Cruel": Color.black, - "Gold": Color.lightgold, - "Platinum": Color.lightgold, - "Meteoric": Color.lightgold, - "Strange": Color.lightgold, - "Weird": Color.lightgold, - "Knight's": Color.darkgold, - "Lord's": Color.darkgold, - "Fool's": Color.white, - "King's": Color.darkgold, - //"Master's": Color.darkgold, - "Elysian": Color.darkgold, - "Fiery": Color.darkred, - "Smoldering": Color.darkred, - "Smoking": Color.darkred, - "Flaming": Color.darkred, - "Condensing": Color.darkred, - "Septic": Color.darkgreen, - "Foul": Color.darkgreen, - "Corrosive": Color.darkgreen, - "Toxic": Color.darkgreen, - "Pestilent": Color.darkgreen, - "of Quickness": Color.darkyellow, - "of the Glacier": Color.darkblue, - "of Winter": Color.darkblue, - "of Burning": Color.darkred, - "of Incineration": Color.darkred, - "of Thunder": Color.darkyellow, - "of Storms": Color.darkyellow, - "of Carnage": Color.black, - "of Slaughter": Color.black, - "of Butchery": Color.black, - "of Evisceration": Color.black, - "of Performance": Color.black, - "of Transcendence": Color.black, - "of Pestilence": Color.darkgreen, - "of Anthrax": Color.darkgreen, - "of the Locust": Color.crystalred, - "of the Lamprey": Color.crystalred, - "of the Wraith": Color.crystalred, - "of the Vampire": Color.crystalred, - "of Icebolt": Color.lightblue, - "of Nova": Color.crystalblue, - "of the Mammoth": Color.crystalred, - "of Frost Shield": Color.lightblue, - "of Nova Shield": Color.crystalblue, - "of Wealth": Color.lightgold, - "of Fortune": Color.lightgold, - "of Luck": Color.lightgold, - "of Perfection": Color.darkgold, - "of Regrowth": Color.crystalred, - "of Spikes": Color.orange, - "of Razors": Color.orange, - "of Swords": Color.orange, - "of Stability": Color.darkyellow, - "of the Colosuss": Color.crystalred, - "of the Squid": Color.crystalred, - "of the Whale": Color.crystalred, - "of Defiance": Color.darkred, - "of the Titan": Color.darkgold, - "of Atlas": Color.darkgold, - "of Wizardry": Color.darkgold - }; - - switch (this.itemType) { - case sdk.items.type.Boots: - colors["of Precision"] = Color.darkgold; - - break; - case sdk.items.type.Gloves: - colors["of Alacrity"] = Color.darkyellow; - colors["of the Leech"] = Color.crystalred; - colors["of the Bat"] = Color.crystalred; - colors["of the Giant"] = Color.darkgold; - - break; - } - } else if (this.set) { - if (this.identified) { - for (let i = 0; i < 127; i += 1) { - if (this.fname.split("\n").reverse()[0].includes(getLocaleString(getBaseStat(16, i, 3)))) { - return getBaseStat(16, i, 12) > 20 ? -1 : getBaseStat(16, i, 12); - } - } - } else { - return Color.lightyellow; // Unidentified set item - } - } else if (this.unique) { - for (let i = 0; i < 401; i += 1) { - if (this.code === getBaseStat(17, i, 4).replace(/^\s+|\s+$/g, "") && this.fname.split("\n").reverse()[0].includes(getLocaleString(getBaseStat(17, i, 2)))) { - return getBaseStat(17, i, 13) > 20 ? -1 : getBaseStat(17, i, 13); - } - } - } - - for (let i = 0; i < this.suffixes.length; i += 1) { - if (colors.hasOwnProperty(this.suffixes[i])) { - return colors[this.suffixes[i]]; - } - } - - for (let i = 0; i < this.prefixes.length; i += 1) { - if (colors.hasOwnProperty(this.prefixes[i])) { - return colors[this.prefixes[i]]; - } - } - - return -1; -}; - -/** - * @description Used upon item units like ArachnidMesh.castChargedSkill([skillId]) or directly on the "me" unit me.castChargedSkill(278); - * @param {int} skillId = undefined - * @param {int} x = undefined - * @param {int} y = undefined - * @return boolean - * @throws Error - */ -Unit.prototype.castChargedSkill = function (...args) { - let skillId, x, y, unit, chargedItem, charge; - let chargedItems = []; - let validCharge = function (itemCharge) { - return itemCharge.skill === skillId && itemCharge.charges; - }; - - switch (args.length) { - case 0: // item.castChargedSkill() - break; - case 1: - if (args[0] instanceof Unit) { // hellfire.castChargedSkill(monster); - unit = args[0]; - } else { - skillId = args[0]; - } - - break; - case 2: - if (typeof args[0] === "number") { - if (args[1] instanceof Unit) { // me.castChargedSkill(skillId,unit) - [skillId, unit] = [...args]; - } else if (typeof args[1] === "number") { // item.castChargedSkill(x,y) - [x, y] = [...args]; - } - } else { - throw new Error(" invalid arguments, expected (skillId, unit) or (x, y)"); - } - - break; - case 3: - // If all arguments are numbers - if (typeof args[0] === "number" && typeof args[1] === "number" && typeof args[2] === "number") { - [skillId, x, y] = [...args]; - } - - break; - default: - throw new Error("invalid arguments, expected 'me' object or 'item' unit"); - } - - // Charged skills can only be casted on x, y coordinates - unit && ([x, y] = [unit.x, unit.y]); - - if (this !== me && this.type !== sdk.unittype.Item) { - throw Error("invalid arguments, expected 'me' object or 'item' unit"); - } - - // Called the function the unit, me. - if (this === me) { - if (!skillId) throw Error("Must supply skillId on me.castChargedSkill"); - - chargedItems = []; - - // Item must be equipped, or a charm in inventory - this.getItemsEx(-1) - .filter(item => item && (item.isEquipped || (item.isInInventory && item.isCharm))) - .forEach(function (item) { - let stats = item.getStat(-2); - - if (stats.hasOwnProperty(sdk.stats.ChargedSkill)) { - if (stats[sdk.stats.ChargedSkill] instanceof Array) { - stats = stats[sdk.stats.ChargedSkill].filter(validCharge); - stats.length && chargedItems.push({ - charge: stats.first(), - item: item - }); - } else { - if (stats[sdk.stats.ChargedSkill].skill === skillId && stats[sdk.stats.ChargedSkill].charges > 1) { - chargedItems.push({ - charge: stats[sdk.stats.ChargedSkill].charges, - item: item - }); - } - } - } - }); - - if (chargedItems.length === 0) throw Error("Don't have the charged skill (" + skillId + "), or not enough charges"); - - chargedItem = chargedItems.sort((a, b) => a.charge.level - b.charge.level).first().item; - - return chargedItem.castChargedSkill.apply(chargedItem, args); - } else if (this.type === sdk.unittype.Item) { - charge = this.getStat(-2)[sdk.stats.ChargedSkill]; // WARNING. Somehow this gives duplicates - - if (!charge) throw Error("No charged skill on this item"); - - if (skillId) { - // Filter out all other charged skills - charge = charge.filter(item => (skillId && item.skill === skillId) && !!item.charges); - } else if (charge.length > 1) { - throw new Error("multiple charges on this item without a given skillId"); - } - - charge = charge.first(); - - if (charge) { - // Setting skill on hand - if (!Config.PacketCasting || Config.PacketCasting === 1 && skillId !== sdk.skills.Teleport) { - return Skill.cast(skillId, sdk.skills.hand.Right, x || me.x, y || me.y, this); // Non packet casting - } - - // Packet casting - sendPacket(1, sdk.packets.send.SelectSkill, 2, charge.skill, 1, 0x0, 1, 0x00, 4, this.gid); - // No need for a delay, since its TCP, the server recv's the next statement always after the send cast skill packet - - // The result of "successfully" casted is different, so we cant wait for it here. We have to assume it worked - sendPacket(1, sdk.packets.send.RightSkillOnLocation, 2, x || me.x, 2, y || me.y); // Cast the skill - - return true; - } - } - - return false; -}; - -/** - * @description equip an item. - */ -Unit.prototype.equip = function (destLocation = undefined) { - if (this.isEquipped) return true; // Item already equiped - - const findspot = function (item) { - let tempspot = Storage.Stash.FindSpot(item); - - if (getUIFlag(sdk.uiflags.Stash) && tempspot) { - return {location: Storage.Stash.location, coord: tempspot}; - } - - tempspot = Storage.Inventory.FindSpot(item); - - return tempspot ? {location: Storage.Inventory.location, coord: tempspot} : false; - }; - const doubleHanded = [ - sdk.items.type.Staff, sdk.items.type.Bow, sdk.items.type.Polearm, sdk.items.type.Crossbow, - sdk.items.type.HandtoHand, sdk.items.type.AmazonBow, sdk.items.type.AmazonSpear - ]; - - // Not an item, or unidentified, or not enough stats - if (this.type !== sdk.unittype.Item || !this.getFlag(sdk.items.flags.Identified) - || this.getStat(sdk.stats.LevelReq) > me.getStat(sdk.stats.Level) - || this.dexreq > me.getStat(sdk.stats.Dexterity) - || this.strreq > me.getStat(sdk.stats.Strength)) { - return false; - } - - // If not a specific location is given, figure it out (can be useful to equip a double weapon) - !destLocation && (destLocation = this.getBodyLoc()); - // If destLocation isnt an array, make it one - !Array.isArray(destLocation) && (destLocation = [destLocation]); - - console.log("equiping " + this.name + " to bodylocation: " + destLocation.first()); - - let currentEquiped = me.getItemsEx(-1).filter(item => - destLocation.indexOf(item.bodylocation) !== -1 - || ( // Deal with double handed weapons - - (item.isOnMain) - && [sdk.body.RightArm, sdk.body.LeftArm].indexOf(destLocation) // in case destination is on the weapon/shield slot - && ( - doubleHanded.indexOf(this.itemType) !== -1 // this item is a double handed item - || doubleHanded.indexOf(item.itemType) !== -1 // current item is a double handed item - ) - ) - ).sort((a, b) => b - a); // shields first - - // if nothing is equipped at the moment, just equip it - if (!currentEquiped.length) { - clickItemAndWait(sdk.clicktypes.click.item.Left, this); - clickItemAndWait(sdk.clicktypes.click.item.Left, destLocation.first()); - } else { - // unequip / swap items - currentEquiped.forEach((item, index) => { - // Last item, so swap instead of putting off first - if (index === (currentEquiped.length - 1)) { - print("swap " + this.name + " for " + item.name); - let oldLoc = {x: this.x, y: this.y, location: this.location}; - clickItemAndWait(sdk.clicktypes.click.item.Left, this); // Pick up current item - clickItemAndWait(sdk.clicktypes.click.item.Left, destLocation.first()); // the swap of items - // Find a spot for the current item - let spot = findspot(item); - - if (!spot) { // If no spot is found for the item, rollback - clickItemAndWait(sdk.clicktypes.click.item.Left, destLocation.first()); // swap again - clickItemAndWait(sdk.clicktypes.click.item.Left, oldLoc.x, oldLoc.y, oldLoc.location); // put item back on old spot - throw Error("cant find spot for unequipped item"); - } - - clickItemAndWait(sdk.clicktypes.click.item.Left, spot.coord.y, spot.coord.x, spot.location); // put item on the found spot - - return; - } - - print("Unequip item first " + item.name); - // Incase multiple items are equipped - let spot = findspot(item); // Find a spot for the current item - - if (!spot) throw Error("cant find spot for unequipped item"); - - clickItemAndWait(sdk.clicktypes.click.item.Left, item.bodylocation); - clickItemAndWait(sdk.clicktypes.click.item.Left, spot.coord.x, spot.coord.y, spot.location); - }); - } - - return { - success: this.bodylocation === destLocation.first(), - unequiped: currentEquiped, - rollback: () => currentEquiped.forEach(item => item.equip()) // Note; rollback only works if you had other items equipped before. - }; -}; - -Unit.prototype.getBodyLoc = function () { - const types = {}; - types[sdk.body.Head] = [sdk.items.type.Helm, sdk.items.type.Pelt, sdk.items.type.PrimalHelm]; // helm - types[sdk.body.Neck] = [sdk.items.type.Amulet]; // amulet - types[sdk.body.Armor] = [sdk.items.type.Armor]; // armor - types[sdk.body.RightArm] = [ - sdk.items.type.Scepter, sdk.items.type.Wand, sdk.items.type.Staff, sdk.items.type.Bow, sdk.items.type.Axe, sdk.items.type.Club, sdk.items.type.Sword, sdk.items.type.Hammer, - sdk.items.type.Knife, sdk.items.type.Spear, sdk.items.type.Polearm, sdk.items.type.Crossbow, sdk.items.type.Mace, sdk.items.type.ThrowingKnife, sdk.items.type.ThrowingAxe, - sdk.items.type.Javelin, sdk.items.type.HandtoHand, sdk.items.type.Orb, sdk.items.type.AmazonBow, sdk.items.type.AmazonSpear, sdk.items.type.AmazonJavelin, sdk.items.type.AssassinClaw - ]; // weapons - types[sdk.body.LeftArm] = [sdk.items.type.Shield, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver, sdk.items.type.AuricShields, sdk.items.type.VoodooHeads], // shields / Arrows / bolts - types[sdk.body.RingRight] = [sdk.items.type.Ring]; // ring slot 1 - types[sdk.body.RingLeft] = [sdk.items.type.Ring]; // ring slot 2 - types[sdk.body.Belt] = [sdk.items.type.Belt]; // belt - types[sdk.body.Feet] = [sdk.items.type.Boots]; // boots - types[sdk.body.Gloves] = [sdk.items.type.Gloves]; // gloves - //types[sdk.body.RightArmSecondary] = types[sdk.body.RightArm]; - //types[sdk.body.LeftArmSecondary] = types[sdk.body.LeftArm]; - let bodyLoc = []; - - for (let i in types) { - this.itemType && types[i].indexOf(this.itemType) !== -1 && bodyLoc.push(i); - } - - // Strings are hard to calculate with, parse to int - return bodyLoc.map(parseInt); -}; - -Unit.prototype.getRes = function (type, difficulty) { - if (!type || ![sdk.stats.FireResist, sdk.stats.ColdResist, sdk.stats.PoisonResist, sdk.stats.LightningResist].includes(type)) { - return -1; - } - - difficulty === undefined || difficulty < 0 && (difficulty = 0); - difficulty > 2 && (difficulty = 2); - - let modifier = me.classic ? [0, 20, 50][difficulty] : [0, 40, 100][difficulty]; - if (this === me) { - switch (type) { - case sdk.stats.FireResist: - me.getState(sdk.states.ShrineResFire) && (modifier += 75); - - break; - case sdk.stats.ColdResist: - me.getState(sdk.states.ShrineResCold) && (modifier += 75); - me.getState(sdk.states.Thawing) && (modifier += 50); - - break; - case sdk.stats.LightningResist: - me.getState(sdk.states.ShrineResLighting) && (modifier += 75); - - break; - case sdk.stats.PoisonResist: - me.getState(sdk.states.ShrineResPoison) && (modifier += 75); - me.getState(sdk.states.Antidote) && (modifier += 50); - - break; - } - } - return this.getStat(type) - modifier; -}; - -{ - let coords = function () { - if (Array.isArray(this) && this.length > 1) { - return [this[0], this[1]]; - } - - if (typeof this.x !== "undefined" && typeof this.y !== "undefined") { - return this instanceof PresetUnit && [this.roomx * 5 + this.x, this.roomy * 5 + this.y] || [this.x, this.y]; - } - - return [undefined, undefined]; - }; - - Object.defineProperties(Object.prototype, { - distance: { - get: function () { - return !me.gameReady ? NaN : /* Math.round */(getDistance.apply(null, [me, ...coords.apply(this)])); - }, - enumerable: false, - }, - }); - - Object.prototype.mobCount = function (givenSettings = {}) { - let [x, y] = coords.apply(this); - const settings = Object.assign({}, { - range: 5, - coll: (sdk.collision.BlockWall | sdk.collision.LineOfSight | sdk.collision.BlockMissile), - type: 0, - ignoreClassids: [], - }, givenSettings); - return getUnits(sdk.unittype.Monster) - .filter(function (mon) { - return mon.attackable && getDistance(x, y, mon.x, mon.y) < settings.range - && (!settings.type || (settings.type & mon.spectype)) - && (settings.ignoreClassids.indexOf(mon.classid) === -1) - && !CollMap.checkColl({x: x, y: y}, mon, settings.coll, 1); - }).length; - }; - Object.defineProperty(Object.prototype, "mobCount", {enumerable: false}); -} - -Object.defineProperties(Unit.prototype, { - isChampion: { - get: function () { - return (this.spectype & sdk.monsters.spectype.Champion) > 0; - }, - }, - isUnique: { - get: function () { - return (this.spectype & sdk.monsters.spectype.Unique) > 0; - }, - }, - isMinion: { - get: function () { - return (this.spectype & sdk.monsters.spectype.Minion) > 0; - }, - }, - isSuperUnique: { - get: function () { - return (this.spectype & (sdk.monsters.spectype.Super | sdk.monsters.spectype.Unique)) > 0; - }, - }, - isSpecial: { - get: function () { - return (this.isChampion || this.isUnique || this.isSuperUnique); - }, - }, - isPlayer: { - get: function () { - return this.type === sdk.unittype.Player; - }, - }, - isMonster: { - get: function () { - return this.type === sdk.unittype.Monster; - }, - }, - isNPC: { - get: function () { - return this.type === sdk.unittype.Monster && this.getStat(sdk.stats.Alignment) === 2; - }, - }, - // todo - monster types - isPrimeEvil: { - get: function () { - return [ - sdk.monsters.Andariel, sdk.monsters.Duriel, sdk.monsters.Mephisto, sdk.monsters.Diablo, - sdk.monsters.Baal, sdk.monsters.BaalClone, sdk.monsters.UberDuriel, sdk.monsters.UberIzual, - sdk.monsters.UberMephisto, sdk.monsters.UberDiablo, sdk.monsters.UberBaal, sdk.monsters.Lilith, sdk.monsters.DiabloClone - ].includes(this.classid) || getBaseStat("monstats", this.classid, "primeevil"); - }, - }, - isBoss: { - get: function () { - return this.isPrimeEvil - || - [ - sdk.monsters.TheSmith, sdk.monsters.BloodRaven, sdk.monsters.Radament, sdk.monsters.Griswold, - sdk.monsters.TheSummoner, sdk.monsters.Izual, sdk.monsters.Hephasto, sdk.monsters.KorlictheProtector, - sdk.monsters.TalictheDefender, sdk.monsters.MadawctheGuardian, sdk.monsters.ListerTheTormenter, - sdk.monsters.TheCowKing, sdk.monsters.ColdwormtheBurrower, sdk.monsters.Nihlathak - ].includes(this.classid); - }, - }, - isGhost: { - get: function () { - return [ - sdk.monsters.Ghost1, sdk.monsters.Wraith1, sdk.monsters.Specter1, - sdk.monsters.Apparition, sdk.monsters.DarkShape, sdk.monsters.Ghost2, sdk.monsters.Wraith2, sdk.monsters.Specter2 - ].includes(this.classid) || getBaseStat("monstats", this.classid, "MonType") === sdk.monsters.type.Wraith; - }, - }, - isDoll: { - get: function () { - return [ - sdk.monsters.BoneFetish1, sdk.monsters.BoneFetish2, sdk.monsters.BoneFetish3, - sdk.monsters.SoulKiller3, sdk.monsters.StygianDoll2, sdk.monsters.StygianDoll6, sdk.monsters.SoulKiller - ].includes(this.classid); - }, - }, - isMonsterObject: { - get: function () { - return [ - sdk.monsters.Turret1, sdk.monsters.Turret2, sdk.monsters.Turret3, sdk.monsters.MummyGenerator, - sdk.monsters.GargoyleTrap, sdk.monsters.LightningSpire, sdk.monsters.FireTower, - sdk.monsters.BarricadeDoor1, sdk.monsters.BarricadeDoor2, sdk.monsters.BarricadeWall1, sdk.monsters.BarricadeWall2, - sdk.monsters.CatapultS, sdk.monsters.CatapultE, sdk.monsters.CatapultSiege, sdk.monsters.CatapultW, - sdk.monsters.BarricadeTower, sdk.monsters.PrisonDoor, sdk.monsters.DiablosBoneCage, sdk.monsters.Hut, - ].includes(this.classid); - }, - }, - isMonsterEgg: { - get: function () { - return [ - sdk.monsters.SandMaggotEgg, sdk.monsters.RockWormEgg, sdk.monsters.DevourerEgg, sdk.monsters.GiantLampreyEgg, - sdk.monsters.WorldKillerEgg1, sdk.monsters.WorldKillerEgg2 - ].includes(this.classid); - }, - }, - isMonsterNest: { - get: function () { - return [ - sdk.monsters.FoulCrowNest, sdk.monsters.BlackVultureNest, sdk.monsters.BloodHawkNest, sdk.monsters.BloodHookNest, - sdk.monsters.BloodWingNest, sdk.monsters.CloudStalkerNest, sdk.monsters.FeederNest, sdk.monsters.SuckerNest - ].includes(this.classid); - }, - }, - isBaalTentacle: { - get: function () { - return [ - sdk.monsters.Tentacle1, sdk.monsters.Tentacle2, - sdk.monsters.Tentacle3, sdk.monsters.Tentacle4, sdk.monsters.Tentacle5 - ].includes(this.classid); - }, - }, - isShaman: { - get: function () { - return [ - sdk.monsters.FallenShaman, sdk.monsters.CarverShaman2, sdk.monsters.DevilkinShaman2, sdk.monsters.DarkShaman1, - sdk.monsters.WarpedShaman, sdk.monsters.CarverShaman, sdk.monsters.DevilkinShaman, sdk.monsters.DarkShaman2 - ].includes(this.classid); - }, - }, - isUnraveler: { - get: function () { - return getBaseStat("monstats", this.classid, "MonType") === sdk.monsters.type.Unraveler; - }, - }, - isFallen: { - get: function () { - return [ - sdk.monsters.Fallen, sdk.monsters.Carver2, sdk.monsters.Devilkin2, sdk.monsters.DarkOne1, sdk.monsters.WarpedFallen, - sdk.monsters.Carver1, sdk.monsters.Devilkin, sdk.monsters.DarkOne2 - ].includes(this.classid); - }, - }, - isBeetle: { - get: function () { - return getBaseStat("monstats", this.classid, "MonType") === sdk.monsters.type.Scarab; - }, - }, - isWalking: { - get: function () { - return (this.mode === sdk.monsters.mode.Walking && (this.targetx !== this.x || this.targety !== this.y)); - } - }, - isRunning: { - get: function () { - return (this.mode === sdk.monsters.mode.Running && (this.targetx !== this.x || this.targety !== this.y)); - } - }, - isMoving: { - get: function () { - return (this.isWalking || this.isRunning); - }, - }, - isFrozen: { - get: function () { - return this.getState(sdk.states.FrozenSolid); - }, - }, - isChilled: { - get: function () { - return this.getState(sdk.states.Frozen); - }, - }, - extraStrong: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.ExtraStrong); - }, - }, - extraFast: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.ExtraFast); - }, - }, - cursed: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.Cursed); - }, - }, - magicResistant: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.MagicResistant); - }, - }, - fireEnchanted: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.FireEnchanted); - }, - }, - lightningEnchanted: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.LightningEnchanted); - }, - }, - coldEnchanted: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.ColdEnchanted); - }, - }, - manBurn: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.ManaBurn); - }, - }, - teleportation: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.Teleportation); - }, - }, - spectralHit: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.SpectralHit); - }, - }, - stoneSkin: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.StoneSkin); - }, - }, - multiShot: { - get: function () { - if (!this.isMonster) return false; - return this.getEnchant(sdk.enchant.MultipleShots); - }, - }, - resPenalty: { - value: me.classic ? [0, 20, 50][me.diff] : [0, 40, 100][me.diff], - writable: true - }, - fireRes: { - get: function () { - let modifier = 0; - if (this === me) { - me.getState(sdk.states.ShrineResFire) && (modifier += 75); - } - return this.getStat(sdk.stats.FireResist) - me.resPenalty - modifier; - } - }, - coldRes: { - get: function () { - let modifier = 0; - if (this === me) { - me.getState(sdk.states.ShrineResCold) && (modifier += 75); - me.getState(sdk.states.Thawing) && (modifier += 50); - } - return this.getStat(sdk.stats.ColdResist) - me.resPenalty - modifier; - } - }, - lightRes: { - get: function () { - let modifier = 0; - if (this === me) { - me.getState(sdk.states.ShrineResLighting) && (modifier += 75); - } - return this.getStat(sdk.stats.LightResist) - me.resPenalty - modifier; - } - }, - poisonRes: { - get: function () { - let modifier = 0; - if (this === me) { - me.getState(sdk.states.ShrineResPoison) && (modifier += 75); - me.getState(sdk.states.Antidote) && (modifier += 50); - } - return this.getStat(sdk.stats.PoisonResist) - me.resPenalty - modifier; - } - }, - hpPercent: { - get: function () { - return Math.round(this.hp * 100 / this.hpmax); - } - }, - isEquipped: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.location === sdk.storage.Equipped; - } - }, - isEquippedCharm: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return (this.location === sdk.storage.Inventory && [sdk.items.type.SmallCharm, sdk.items.type.LargeCharm, sdk.items.type.GrandCharm].includes(this.itemType)); - } - }, - isInInventory: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.location === sdk.storage.Inventory && this.mode === sdk.items.mode.inStorage; - } - }, - isInStash: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.location === sdk.storage.Stash && this.mode === sdk.items.mode.inStorage; - } - }, - isInCube: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.location === sdk.storage.Cube && this.mode === sdk.items.mode.inStorage; - } - }, - isInStorage: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.mode === sdk.items.mode.inStorage && [sdk.storage.Inventory, sdk.storage.Cube, sdk.storage.Stash].includes(this.location); - } - }, - isInBelt: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.location === sdk.storage.Belt && this.mode === sdk.items.mode.inBelt; - } - }, - isOnMain: { - get: function () { - if (this.type !== sdk.unittype.Item || this.location !== sdk.storage.Equipped) return false; - return [sdk.body.RightArm, sdk.body.LeftArm].includes(this.bodylocation); - } - }, - isOnSwap: { - get: function () { - if (this.type !== sdk.unittype.Item || this.location !== sdk.storage.Equipped) return false; - switch (me.weaponswitch) { - case sdk.player.slot.Main: - return [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary].includes(this.bodylocation); - case sdk.player.slot.Secondary: - return [sdk.body.RightArm, sdk.body.LeftArm].includes(this.bodylocation); - } - return false; - } - }, - identified: { - get: function () { - // Can't tell, as it isn't an item - if (this.type !== sdk.unittype.Item) return undefined; - // Is also true for white items - return this.getFlag(sdk.items.flags.Identified); - } - }, - ethereal: { - get: function () { - // Can't tell, as it isn't an item - if (this.type !== sdk.unittype.Item) return undefined; - return this.getFlag(sdk.items.flags.Ethereal); - } - }, - twoHanded: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return getBaseStat("items", this.classid, "2handed") === 1; - } - }, - oneOrTwoHanded: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return getBaseStat("items", this.classid, "1or2handed") === 1; - } - }, - strictlyTwoHanded: { - get: function () { - return this.twoHanded && !this.oneOrTwoHanded; - } - }, - runeword: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return !!this.getFlag(sdk.items.flags.Runeword); - } - }, - questItem: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return (this.itemType === sdk.items.type.Quest - || [ - sdk.items.quest.HoradricMalus, sdk.items.quest.WirtsLeg, sdk.items.quest.HoradricStaff, sdk.items.quest.ShaftoftheHoradricStaff, - sdk.items.quest.ViperAmulet, sdk.items.quest.DecoyGidbinn, sdk.items.quest.TheGidbinn, sdk.items.quest.KhalimsFlail, - sdk.items.quest.KhalimsWill, sdk.items.quest.HellForgeHammer, sdk.items.quest.StandardofHeroes - ].includes(this.classid)); - } - }, - sellable: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - if (this.getItemCost(sdk.items.cost.ToSell) <= 1) return false; - return (!this.questItem - && [ - sdk.items.quest.KeyofTerror, sdk.items.quest.KeyofHate, sdk.items.quest.KeyofDestruction, sdk.items.quest.DiablosHorn, - sdk.items.quest.BaalsEye, sdk.items.quest.MephistosBrain, sdk.items.quest.TokenofAbsolution, sdk.items.quest.TwistedEssenceofSuffering, - sdk.items.quest.ChargedEssenceofHatred, sdk.items.quest.BurningEssenceofTerror, sdk.items.quest.FesteringEssenceofDestruction - ].indexOf(this.classid) === -1); - } - }, - lowquality: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.LowQuality; - }, - }, - normal: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.Normal; - }, - }, - superior: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.Superior; - }, - }, - magic: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.Magic; - }, - }, - set: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.Set; - }, - }, - rare: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.Rare; - }, - }, - unique: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.Unique; - }, - }, - crafted: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.quality === sdk.items.quality.Crafted; - }, - }, - sockets: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.getStat(sdk.stats.NumSockets); - }, - }, - onGroundOrDropping: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return (this.mode === sdk.items.mode.onGround || this.mode === sdk.items.mode.Dropping); - }, - }, - isShield: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return [sdk.items.type.Shield, sdk.items.type.AuricShields, sdk.items.type.VoodooHeads].includes(this.itemType); - }, - }, - isAnni: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.unique && this.itemType === sdk.items.type.SmallCharm; - }, - }, - isTorch: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.unique && this.itemType === sdk.items.type.LargeCharm; - }, - }, - isGheeds: { - get: function () { - if (this.type !== sdk.unittype.Item) return false; - return this.unique && this.itemType === sdk.items.type.GrandCharm; - }, - }, - prettyPrint: { - get: function () { - if (this.type !== sdk.unittype.Item) return this.name; - return this.fname.split("\n").reverse().join(" "); - } - }, -}); - -Unit.prototype.hasEnchant = function (...enchants) { - if (!this.isMonster) return false; - for (let enchant of enchants) { - if (this.getEnchant(enchant)) return true; - } - return false; -}; - -Unit.prototype.usingShield = function () { - if (this.type > sdk.unittype.Monster) return false; - // always switch to main hand if we are checking ourselves - this === me && me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main); - let shield = this.getItemsEx(-1, sdk.items.mode.Equipped).filter(s => s.isShield).first(); - return !!shield; -}; - -Object.defineProperties(me, { - inShop: { - get: function () { - if (getUIFlag(sdk.uiflags.Shop)) return true; - if (!Config.PacketShopping) return false; - let npc = getInteractedNPC(); - return !!(npc && npc.itemcount > 0); - } - }, - walking: { - get: function () { - return me.runwalk === sdk.player.move.Walk; - } - }, - running: { - get: function () { - return me.runwalk === sdk.player.move.Run; - } - }, - deadOrInSequence: { - get: function () { - return me.dead || me.mode === sdk.player.mode.SkillActionSequence; - } - }, - moving: { - get: function () { - return [sdk.player.mode.Walking, sdk.player.mode.Running, sdk.player.mode.WalkingInTown].includes(me.mode); - } - }, - highestAct: { - get: function () { - let acts = [true, - me.getQuest(sdk.quest.id.AbleToGotoActII, sdk.quest.states.Completed), - me.getQuest(sdk.quest.id.AbleToGotoActIII, sdk.quest.states.Completed), - me.getQuest(sdk.quest.id.AbleToGotoActIV, sdk.quest.states.Completed), - me.getQuest(sdk.quest.id.AbleToGotoActV, sdk.quest.states.Completed)]; - let index = acts.findIndex((i) => !i); // find first false, returns between 1 and 5 - return index === -1 ? 5 : index; - } - }, - highestQuestDone: { - get: function () { - for (let i = sdk.quest.id.Respec; i >= sdk.quest.id.SpokeToWarriv; i--) { - if (me.getQuest(i, sdk.quest.states.Completed)) { - return i; - } - - // check if we've completed main part but not used our reward - if ([sdk.quest.id.RescueonMountArreat, sdk.quest.id.SiegeOnHarrogath, sdk.quest.id.ToolsoftheTrade].includes(i) && me.getQuest(i, sdk.quest.states.ReqComplete)) { - return i; - } - } - return undefined; - } - }, - staminaPercent: { - get: function () { - return Math.round((me.stamina / me.staminamax) * 100); - } - }, - staminaDrainPerSec: { - get: function () { - let bonusReduction = me.getStat(sdk.stats.StaminaRecoveryBonus); - let armorMalusReduction = 0; // TODO - return 25 * Math.max(40 * (1 + armorMalusReduction / 10) * (100 - bonusReduction) / 100, 1) / 256; - } - }, - staminaTimeLeft: { - get: function () { - return me.stamina / me.staminaDrainPerSec; - } - }, - staminaMaxDuration: { - get: function () { - return me.staminamax / me.staminaDrainPerSec; - } - }, - FCR: { - get: function () { - return me.getStat(sdk.stats.FCR) - (!!Config ? Config.FCR : 0); - } - }, - FHR: { - get: function () { - return me.getStat(sdk.stats.FHR) - (!!Config ? Config.FHR : 0); - } - }, - FBR: { - get: function () { - return me.getStat(sdk.stats.FBR) - (!!Config ? Config.FBR : 0); - } - }, - IAS: { - get: function () { - return me.getStat(sdk.stats.IAS) - (!!Config ? Config.IAS : 0); - } - }, - shapeshifted: { - get: function () { - return me.getState(sdk.states.Wolf) || me.getState(sdk.states.Bear) || me.getState(sdk.states.Delerium); - } - }, - mpPercent: { - get: function () { - return Math.round(me.mp * 100 / me.mpmax); - } - }, - skillDelay: { - get: function () { - return me.getState(sdk.states.SkillDelay); - } - }, - classic: { - get: function () { - return me.gametype === sdk.game.gametype.Classic; - } - }, - expansion: { - get: function () { - return me.gametype === sdk.game.gametype.Expansion; - } - }, - softcore: { - get: function () { - return me.playertype === false; - } - }, - hardcore: { - get: function () { - return me.playertype === true; - } - }, - normal: { - get: function () { - return me.diff === sdk.difficulty.Normal; - } - }, - nightmare: { - get: function () { - return me.diff === sdk.difficulty.Nightmare; - } - }, - hell: { - get: function () { - return me.diff === sdk.difficulty.Hell; - } - }, - amazon: { - get: function () { - return me.classid === sdk.player.class.Amazon; - } - }, - sorceress: { - get: function () { - return me.classid === sdk.player.class.Sorceress; - } - }, - necromancer: { - get: function () { - return me.classid === sdk.player.class.Necromancer; - } - }, - paladin: { - get: function () { - return me.classid === sdk.player.class.Paladin; - } - }, - barbarian: { - get: function () { - return me.classid === sdk.player.class.Barbarian; - } - }, - druid: { - get: function () { - return me.classid === sdk.player.class.Druid; - } - }, - assassin: { - get: function () { - return me.classid === sdk.player.class.Assassin; - } - }, - // quest items - wirtsleg: { - get: function () { - return me.getItem(sdk.quest.item.WirtsLeg); - } - }, - cube: { - get: function () { - return me.getItem(sdk.quest.item.Cube); - } - }, - shaft: { - get: function () { - return me.getItem(sdk.quest.item.ShaftoftheHoradricStaff); - } - }, - amulet: { - get: function () { - return me.getItem(sdk.quest.item.ViperAmulet); - } - }, - staff: { - get: function () { - return me.getItem(sdk.quest.item.HoradricStaff); - } - }, - completestaff: { - get: function () { - return me.getItem(sdk.quest.item.HoradricStaff); - } - }, - eye: { - get: function () { - return me.getItem(sdk.items.quest.KhalimsEye); - } - }, - brain: { - get: function () { - return me.getItem(sdk.quest.item.KhalimsBrain); - } - }, - heart: { - get: function () { - return me.getItem(sdk.quest.item.KhalimsHeart); - } - }, - khalimswill: { - get: function () { - return me.getItem(sdk.quest.item.KhalimsWill); - } - }, - khalimsflail: { - get: function () { - return me.getItem(sdk.quest.item.KhalimsFlail); - } - }, - malahspotion: { - get: function () { - return me.getItem(sdk.quest.item.MalahsPotion); - } - }, - scrollofresistance: { - get: function () { - return me.getItem(sdk.quest.item.ScrollofResistance); - } - }, - // quests - den: { - get: function () { - return me.getQuest(sdk.quest.id.DenofEvil, sdk.quest.states.Completed); - } - }, - bloodraven: { - get: function () { - return me.getQuest(sdk.quest.id.SistersBurialGrounds, sdk.quest.states.Completed); - } - }, - smith: { - get: function () { - return me.getQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.Completed); - } - }, - cain: { - get: function () { - return me.getQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed); - } - }, - tristram: { - get: function () { - // update where this is used and change the state to be portal opened and me.cain to be quest completed - return me.getQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed); - } - }, - countess: { - get: function () { - return me.getQuest(sdk.quest.id.ForgottenTower, sdk.quest.states.Completed); - } - }, - andariel: { - get: function () { - return me.getQuest(sdk.quest.id.AbleToGotoActII, sdk.quest.states.Completed); - } - }, - radament: { - get: function () { - return me.getQuest(sdk.quest.id.RadamentsLair, sdk.quest.states.Completed); - } - }, - horadricstaff: { - get: function () { - return me.getQuest(sdk.quest.id.TheHoradricStaff, sdk.quest.states.Completed); - } - }, - summoner: { - get: function () { - return me.getQuest(sdk.quest.id.TheSummoner, sdk.quest.states.Completed); - } - }, - duriel: { - get: function () { - return me.getQuest(sdk.quest.id.AbleToGotoActIII, sdk.quest.states.Completed); - } - }, - goldenbird: { - get: function () { - return me.getQuest(sdk.quest.id.TheGoldenBird, sdk.quest.states.Completed); - } - }, - lamessen: { - get: function () { - return me.getQuest(sdk.quest.id.LamEsensTome, sdk.quest.states.Completed); - } - }, - gidbinn: { - get: function () { - return me.getQuest(sdk.quest.id.BladeoftheOldReligion, sdk.quest.states.Completed); - } - }, - travincal: { - get: function () { - return me.getQuest(sdk.quest.id.KhalimsWill, sdk.quest.states.Completed); - } - }, - mephisto: { - get: function () { - return me.getQuest(sdk.quest.id.AbleToGotoActIV, sdk.quest.states.Completed); - } - }, - izual: { - get: function () { - return me.getQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.Completed); - } - }, - hellforge: { - get: function () { - return me.getQuest(sdk.quest.id.HellsForge, sdk.quest.states.Completed); - } - }, - diablo: { - get: function () { - return me.getQuest(sdk.quest.id.TerrorsEnd, sdk.quest.states.Completed); - } - }, - shenk: { - get: function () { - return me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.Completed); - } - }, - larzuk: { - get: function () { - return me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.ReqComplete); - } - }, - savebarby: { - get: function () { - return me.getQuest(sdk.quest.id.RescueonMountArreat, sdk.quest.states.Completed); - } - }, - barbrescue: { - get: function () { - return me.getQuest(sdk.quest.id.RescueonMountArreat, sdk.quest.states.Completed); - } - }, - anya: { - get: function () { - return me.getQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.Completed); - } - }, - ancients: { - get: function () { - return me.getQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed); - } - }, - baal: { - get: function () { - return me.getQuest(sdk.quest.id.EyeofDestruction, sdk.quest.states.Completed); - } - }, - // Misc - cows: { - get: function () { - return me.getQuest(sdk.quest.id.TheSearchForCain, 10); - } - }, - respec: { - get: function () { - return me.getQuest(sdk.quest.id.Respec, sdk.quest.states.Completed); - } - }, - diffCompleted: { - get: function () { - return !!((me.classic && me.diablo) || me.baal); - } - }, -}); - -// something in here is causing demon imps in barricade towers to be skipped - todo: figure out what -Unit.prototype.__defineGetter__("attackable", function () { - if (this === undefined || !copyUnit(this).x) return false; - if (this.type > sdk.unittype.Monster) return false; - // must be in same area - if (this.area !== me.area) return false; - // player and they are hostile - if (this.type === sdk.unittype.Player && getPlayerFlag(me.gid, this.gid, 8) && !this.dead) return true; - // Dead monster - if (this.hp === 0 || this.mode === sdk.monsters.mode.Death || this.mode === sdk.monsters.mode.Dead) return false; - // Friendly monster/NPC - if (this.getStat(sdk.stats.Alignment) === 2) return false; - // catapults were returning a level of 0 and hanging up clear scripts - if (this.charlvl < 1) return false; - // neverCount base stat - hydras, traps etc. - if (!this.isMonsterObject && getBaseStat("monstats", this.classid, "neverCount")) { - return false; - } - // Monsters that are in flight - if ([ - sdk.monsters.CarrionBird1, sdk.monsters.UndeadScavenger, sdk.monsters.HellBuzzard, - sdk.monsters.WingedNightmare, sdk.monsters.SoulKiller2/*feel like this one is wrong*/, - sdk.monsters.CarrionBird2].includes(this.classid) && this.mode === sdk.monsters.mode.UsingSkill1) { - return false; - } - // Monsters that are Burrowed/Submerged - if ([ - sdk.monsters.SandMaggot, sdk.monsters.RockWorm, sdk.monsters.Devourer, sdk.monsters.GiantLamprey, sdk.monsters.WorldKiller2, - sdk.monsters.WaterWatcherLimb, sdk.monsters.RiverStalkerLimb, sdk.monsters.StygianWatcherLimb, - sdk.monsters.WaterWatcherHead, sdk.monsters.RiverStalkerHead, sdk.monsters.StygianWatcherHead].includes(this.classid) && this.mode === sdk.monsters.mode.Spawning) { - return false; - } - - return [sdk.monsters.ThroneBaal, sdk.monsters.Cow/*an evil force*/].indexOf(this.classid) === -1; -}); - -Unit.prototype.__defineGetter__("curseable", function () { - // must be player or monster - if (this === undefined || !copyUnit(this).x || this.type > 1) return false; - // Dead monster - if (this.hp === 0 || this.mode === sdk.monsters.mode.Death || this.mode === sdk.monsters.mode.Dead) return false; - // attract can't be overridden - if (this.getState(sdk.states.Attract)) return false; - // "Possessed" - if (!!this.name && !!this.name.includes(getLocaleString(sdk.locale.text.Possessed))) return false; - if (this.type === sdk.unittype.Player && getPlayerFlag(me.gid, this.gid, 8) && !this.dead) return true; - // Friendly monster/NPC - if (this.getStat(sdk.stats.Alignment) === 2) return false; - // catapults were returning a level of 0 and hanging up clear scripts - if (this.charlvl < 1) return false; - // Monsters that are in flight - if ([ - sdk.monsters.CarrionBird1, sdk.monsters.UndeadScavenger, sdk.monsters.HellBuzzard, - sdk.monsters.WingedNightmare, sdk.monsters.SoulKiller2/*feel like this one is wrong*/, - sdk.monsters.CarrionBird2].includes(this.classid) && this.mode === sdk.monsters.mode.UsingSkill1) { - return false; - } - // Monsters that are Burrowed/Submerged - if ([ - sdk.monsters.SandMaggot, sdk.monsters.RockWorm, sdk.monsters.Devourer, sdk.monsters.GiantLamprey, sdk.monsters.WorldKiller2, - sdk.monsters.WaterWatcherLimb, sdk.monsters.RiverStalkerLimb, sdk.monsters.StygianWatcherLimb, - sdk.monsters.WaterWatcherHead, sdk.monsters.RiverStalkerHead, sdk.monsters.StygianWatcherHead].includes(this.classid) && this.mode === sdk.monsters.mode.Spawning) { - return false; - } - - return (!this.isMonsterObject && !this.isMonsterEgg && !this.isMonsterNest && !this.isBaalTentacle && [ - sdk.monsters.WaterWatcherLimb, sdk.monsters.WaterWatcherHead, sdk.monsters.Flavie, sdk.monsters.ThroneBaal, sdk.monsters.Cow - ].indexOf(this.classid) === -1); -}); - -Unit.prototype.__defineGetter__("scareable", function () { - return this.curseable && !(this.isSpecial) && this.classid !== sdk.monsters.ListerTheTormenter; -}); - -Unit.prototype.getMobCount = function (range = 10, coll = 0, type = 0, noSpecialMobs = false) { - if (this === undefined) return 0; - const _this = this; - return getUnits(sdk.unittype.Monster) - .filter(function (mon) { - return mon.attackable && getDistance(_this, mon) < range - && (!type || ((type & mon.spectype) && !noSpecialMobs)) - && (!coll || !checkCollision(_this, mon, coll)); - }).length; -}; - -Unit.prototype.checkForMobs = function (givenSettings = {}) { - if (this === undefined) return 0; - const _this = this; - const settings = Object.assign({ - range: 10, - count: 1, - coll: 0, - spectype: sdk.monsters.spectype.All - }, givenSettings); - let mob = Game.getMonster(); - let count = 0; - if (mob) { - do { - if (getDistance(_this, mob) < settings.range && mob.attackable - && (!settings.spectype || ((settings.spectype & mob.spectype))) - && (!settings.coll || !checkCollision(_this, mob, settings.coll))) { - count++; - } - if (count >= settings.count) { - return true; - } - } while (mob.getNext()); - } - return false; -}; - -/** - * @description check if unit is in an area - * @param {number} area - * @returns {boolean} if unit is in specified area - */ -Unit.prototype.inArea = function (area = 0) { - if (this === undefined) return false; - return this.area === area; -}; - -// should this be broken into two functions for item vs unit (player, monster, ect) -/** - * @description check if unit is a certain unit by classid - * @param {number} classid - * @returns {boolean} if unit matches the specified classid - */ -Unit.prototype.isUnit = function (classid = -1) { - if (this === undefined) return false; - return this.classid === classid; -}; - -/** - * - * @param {any} key - * @returns value of key if it exists - * @description replicate .? operator of modern js since d2bs doesn't have it - */ -Object.prototype.test = function (key, last = false) { - if (this === undefined) return false; - return this[key] !== undefined ? this[key] : last ? null : {}; -}; -Object.defineProperty(Object.prototype, "test", { enumerable: false }); - -PresetUnit.prototype.realCoords = function () { - return { - area: this.level, // for some reason, preset units names the area "level" - x: this.roomx * 5 + this.x, - y: this.roomy * 5 + this.y, - }; -}; diff --git a/d2bs/kolbot/libs/common/Runewords.js b/d2bs/kolbot/libs/common/Runewords.js deleted file mode 100644 index f51270314..000000000 --- a/d2bs/kolbot/libs/common/Runewords.js +++ /dev/null @@ -1,404 +0,0 @@ -/** -* @filename Runewords.js -* @author kolton -* @desc make and reroll runewords -* -*/ - -// TODO: Config.Runewords[i][0] can be false, but array methods can be used on it - -const Runeword = { - // 1.09 - AncientsPledge: [sdk.items.runes.Ral, sdk.items.runes.Ort, sdk.items.runes.Tal], // Ral + Ort + Tal - Black: [sdk.items.runes.Thul, sdk.items.runes.Io, sdk.items.runes.Nef], // Thul + Io + Nef - Fury: [sdk.items.runes.Jah, sdk.items.runes.Gul, sdk.items.runes.Eth], // Jah + Gul + Eth - HolyThunder: [sdk.items.runes.Eth, sdk.items.runes.Ral, sdk.items.runes.Ort, sdk.items.runes.Tal], // Eth + Ral + Ort + Tal - Honor: [sdk.items.runes.Amn, sdk.items.runes.El, sdk.items.runes.Ith, sdk.items.runes.Tir, sdk.items.runes.Sol], // Amn + El + Ith + Tir + Sol - KingsGrace: [sdk.items.runes.Amn, sdk.items.runes.Ral, sdk.items.runes.Thul], // Amn + Ral + Thul - Leaf: [sdk.items.runes.Tir, sdk.items.runes.Ral], // Tir + Ral - Lionheart: [sdk.items.runes.Hel, sdk.items.runes.Lum, sdk.items.runes.Fal], // Hel + Lum + Fal - Lore: [sdk.items.runes.Ort, sdk.items.runes.Sol], // Ort + Sol - Malice: [sdk.items.runes.Ith, sdk.items.runes.El, sdk.items.runes.Eth], // Ith + El + Eth - Melody: [sdk.items.runes.Shael, sdk.items.runes.Ko, sdk.items.runes.Nef], // Shael + Ko + Nef - Memory: [sdk.items.runes.Lum, sdk.items.runes.Io, sdk.items.runes.Sol, sdk.items.runes.Eth], // Lum + Io + Sol + Eth - Nadir: [sdk.items.runes.Nef, sdk.items.runes.Tir], // Nef + Tir - Radiance: [sdk.items.runes.Nef, sdk.items.runes.Sol, sdk.items.runes.Ith], // Nef + Sol + Ith - Rhyme: [sdk.items.runes.Shael, sdk.items.runes.Eth], // Shael + Eth - Silence: [sdk.items.runes.Dol, sdk.items.runes.Eld, sdk.items.runes.Hel, sdk.items.runes.Ist, sdk.items.runes.Tir, sdk.items.runes.Vex], // Dol + Eld + Hel + Ist + Tir + Vex - Smoke: [sdk.items.runes.Nef, sdk.items.runes.Lum], // Nef + Lum - Stealth: [sdk.items.runes.Tal, sdk.items.runes.Eth], // Tal + Eth - Steel: [sdk.items.runes.Tir, sdk.items.runes.El], // Tir + El - Strength: [sdk.items.runes.Amn, sdk.items.runes.Tir], // Amn + Tir - Venom: [sdk.items.runes.Tal, sdk.items.runes.Dol, sdk.items.runes.Mal], // Tal + Dol + Mal - Wealth: [sdk.items.runes.Lem, sdk.items.runes.Ko, sdk.items.runes.Tir], // Lem + Ko + Tir - White: [sdk.items.runes.Dol, sdk.items.runes.Io], // Dol + Io - Zephyr: [sdk.items.runes.Ort, sdk.items.runes.Eth], // Ort + Eth - - // 1.10 - Beast: [sdk.items.runes.Ber, sdk.items.runes.Tir, sdk.items.runes.Um, sdk.items.runes.Mal, sdk.items.runes.Lum], // Ber + Tir + Um + Mal + Lum - Bramble: [sdk.items.runes.Ral, sdk.items.runes.Ohm, sdk.items.runes.Sur, sdk.items.runes.Eth], // Ral + Ohm + Sur + Eth - BreathoftheDying: [sdk.items.runes.Vex, sdk.items.runes.Hel, sdk.items.runes.El, sdk.items.runes.Eld, sdk.items.runes.Zod, sdk.items.runes.Eth], // Vex + Hel + El + Eld + Zod + Eth - CallToArms: [sdk.items.runes.Amn, sdk.items.runes.Ral, sdk.items.runes.Mal, sdk.items.runes.Ist, sdk.items.runes.Ohm], // Amn + Ral + Mal + Ist + Ohm - ChainsofHonor: [sdk.items.runes.Dol, sdk.items.runes.Um, sdk.items.runes.Ber, sdk.items.runes.Ist], // Dol + Um + Ber + Ist - Chaos: [sdk.items.runes.Fal, sdk.items.runes.Ohm, sdk.items.runes.Um], // Fal + Ohm + Um - CrescentMoon: [sdk.items.runes.Shael, sdk.items.runes.Um, sdk.items.runes.Tir], // Shael + Um + Tir - Delirium: [sdk.items.runes.Lem, sdk.items.runes.Ist, sdk.items.runes.Io], // Lem + Ist + Io - Doom: [sdk.items.runes.Hel, sdk.items.runes.Ohm, sdk.items.runes.Um, sdk.items.runes.Lo, sdk.items.runes.Cham], // Hel + Ohm + Um + Lo + Cham - Duress: [sdk.items.runes.Shael, sdk.items.runes.Um, sdk.items.runes.Thul], // Shael + Um + Thul - Enigma: [sdk.items.runes.Jah, sdk.items.runes.Ith, sdk.items.runes.Ber], // Jah + Ith + Ber - Eternity: [sdk.items.runes.Amn, sdk.items.runes.Ber, sdk.items.runes.Ist, sdk.items.runes.Sol, sdk.items.runes.Sur], // Amn + Ber + Ist + Sol + Sur - Exile: [sdk.items.runes.Vex, sdk.items.runes.Ohm, sdk.items.runes.Ist, sdk.items.runes.Dol], // Vex + Ohm + Ist + Dol - Famine: [sdk.items.runes.Fal, sdk.items.runes.Ohm, sdk.items.runes.Ort, sdk.items.runes.Jah], // Fal + Ohm + Ort + Jah - Gloom: [sdk.items.runes.Fal, sdk.items.runes.Um, sdk.items.runes.Pul], // Fal + Um + Pul - HandofJustice: [sdk.items.runes.Sur, sdk.items.runes.Cham, sdk.items.runes.Amn, sdk.items.runes.Lo], // Sur + Cham + Amn + Lo - HeartoftheOak: [sdk.items.runes.Ko, sdk.items.runes.Vex, sdk.items.runes.Pul, sdk.items.runes.Thul], // Ko + Vex + Pul + Thul - Kingslayer: [sdk.items.runes.Mal, sdk.items.runes.Um, sdk.items.runes.Gul, sdk.items.runes.Fal], // Mal + Um + Gul + Fal - Passion: [sdk.items.runes.Dol, sdk.items.runes.Ort, sdk.items.runes.Eld, sdk.items.runes.Lem], // Dol + Ort + Eld + Lem - Prudence: [sdk.items.runes.Mal, sdk.items.runes.Tir], // Mal + Tir - Sanctuary: [sdk.items.runes.Ko, sdk.items.runes.Ko, sdk.items.runes.Mal], // Ko + Ko + Mal - Splendor: [sdk.items.runes.Eth, sdk.items.runes.Lum], // Eth + Lum - Stone: [sdk.items.runes.Shael, sdk.items.runes.Um, sdk.items.runes.Pul, sdk.items.runes.Lum], // Shael + Um + Pul + Lum - Wind: [sdk.items.runes.Sur, sdk.items.runes.El], // Sur + El - - // Don't use ladder-only on NL - Brand: me.ladder ? [sdk.items.runes.Jah, sdk.items.runes.Lo, sdk.items.runes.Mal, sdk.items.runes.Gul] : false, // Jah + Lo + Mal + Gul - Death: me.ladder ? [sdk.items.runes.Hel, sdk.items.runes.El, sdk.items.runes.Vex, sdk.items.runes.Ort, sdk.items.runes.Gul] : false, // Hel + El + Vex + Ort + Gul - Destruction: me.ladder ? [sdk.items.runes.Vex, sdk.items.runes.Lo, sdk.items.runes.Ber, sdk.items.runes.Jah, sdk.items.runes.Ko] : false, // Vex + Lo + Ber + Jah + Ko - Dragon: me.ladder ? [sdk.items.runes.Sur, sdk.items.runes.Lo, sdk.items.runes.Sol] : false, // Sur + Lo + Sol - Dream: me.ladder ? [sdk.items.runes.Io, sdk.items.runes.Jah, sdk.items.runes.Pul] : false, // Io + Jah + Pul - Edge: me.ladder ? [sdk.items.runes.Tir, sdk.items.runes.Tal, sdk.items.runes.Amn] : false, // Tir + Tal + Amn - Faith: me.ladder ? [sdk.items.runes.Ohm, sdk.items.runes.Jah, sdk.items.runes.Lem, sdk.items.runes.Eld] : false, // Ohm + Jah + Lem + Eld - Fortitude: me.ladder ? [sdk.items.runes.El, sdk.items.runes.Sol, sdk.items.runes.Dol, sdk.items.runes.Lo] : false, // El + Sol + Dol + Lo - Grief: me.ladder ? [sdk.items.runes.Eth, sdk.items.runes.Tir, sdk.items.runes.Lo, sdk.items.runes.Mal, sdk.items.runes.Ral] : false, // Eth + Tir + Lo + Mal + Ral - Harmony: me.ladder ? [sdk.items.runes.Tir, sdk.items.runes.Ith, sdk.items.runes.Sol, sdk.items.runes.Ko] : false, // Tir + Ith + Sol + Ko - Ice: me.ladder ? [sdk.items.runes.Amn, sdk.items.runes.Shael, sdk.items.runes.Jah, sdk.items.runes.Lo] : false, // Amn + Shael + Jah + Lo - "Infinity": me.ladder ? [sdk.items.runes.Ber, sdk.items.runes.Mal, sdk.items.runes.Ber, sdk.items.runes.Ist] : false, // Ber + Mal + Ber + Ist - Insight: me.ladder ? [sdk.items.runes.Ral, sdk.items.runes.Tir, sdk.items.runes.Tal, sdk.items.runes.Sol] : false, // Ral + Tir + Tal + Sol - LastWish: me.ladder ? [sdk.items.runes.Jah, sdk.items.runes.Mal, sdk.items.runes.Jah, sdk.items.runes.Sur, sdk.items.runes.Jah, sdk.items.runes.Ber] : false, // Jah + Mal + Jah + Sur + Jah + Ber - Lawbringer: me.ladder ? [sdk.items.runes.Amn, sdk.items.runes.Lem, sdk.items.runes.Ko] : false, // Amn + Lem + Ko - Oath: me.ladder ? [sdk.items.runes.Shael, sdk.items.runes.Pul, sdk.items.runes.Mal, sdk.items.runes.Lum] : false, // Shael + Pul + Mal + Lum - Obedience: me.ladder ? [sdk.items.runes.Hel, sdk.items.runes.Ko, sdk.items.runes.Thul, sdk.items.runes.Eth, sdk.items.runes.Fal] : false, // Hel + Ko + Thul + Eth + Fal - Phoenix: me.ladder ? [sdk.items.runes.Vex, sdk.items.runes.Vex, sdk.items.runes.Lo, sdk.items.runes.Jah] : false, // Vex + Vex + Lo + Jah - Pride: me.ladder ? [sdk.items.runes.Cham, sdk.items.runes.Sur, sdk.items.runes.Io, sdk.items.runes.Lo] : false, // Cham + Sur + Io + Lo - Rift: me.ladder ? [sdk.items.runes.Hel, sdk.items.runes.Ko, sdk.items.runes.Lem, sdk.items.runes.Gul] : false, // Hel + Ko + Lem + Gul - Spirit: me.ladder ? [sdk.items.runes.Tal, sdk.items.runes.Thul, sdk.items.runes.Ort, sdk.items.runes.Amn] : false, // Tal + Thul + Ort + Amn - VoiceofReason: me.ladder ? [sdk.items.runes.Lem, sdk.items.runes.Ko, sdk.items.runes.El, sdk.items.runes.Eld] : false, // Lem + Ko + El + Eld - Wrath: me.ladder ? [sdk.items.runes.Pul, sdk.items.runes.Lum, sdk.items.runes.Ber, sdk.items.runes.Mal] : false, // Pul + Lum + Ber + Mal - - // 1.11 - Bone: [sdk.items.runes.Sol, sdk.items.runes.Um, sdk.items.runes.Um], // Sol + Um + Um - Enlightenment: [sdk.items.runes.Pul, sdk.items.runes.Ral, sdk.items.runes.Sol], // Pul + Ral + Sol - Myth: [sdk.items.runes.Hel, sdk.items.runes.Amn, sdk.items.runes.Nef], // Hel + Amn + Nef - Peace: [sdk.items.runes.Shael, sdk.items.runes.Thul, sdk.items.runes.Amn], // Shael + Thul + Amn - Principle: [sdk.items.runes.Ral, sdk.items.runes.Gul, sdk.items.runes.Eld], // Ral + Gul + Eld - Rain: [sdk.items.runes.Ort, sdk.items.runes.Mal, sdk.items.runes.Ith], // Ort + Mal + Ith - Treachery: [sdk.items.runes.Shael, sdk.items.runes.Thul, sdk.items.runes.Lem], // Shael + Thul + Lem - - Test: [sdk.items.runes.Hel, sdk.items.runes.Hel, sdk.items.runes.Hel] -}; - -const Runewords = { - needList: [], - pickitEntries: [], - validGids: [], - - init: function () { - if (!Config.MakeRunewords) return; - - this.pickitEntries = []; - - // initiate pickit entries - for (let i = 0; i < Config.KeepRunewords.length; i += 1) { - let info = { - file: "Character Config", - line: Config.KeepRunewords[i] - }; - - let parsedLine = NTIP.ParseLineInt(Config.KeepRunewords[i], info); - parsedLine && this.pickitEntries.push(NTIP.ParseLineInt(Config.KeepRunewords[i], info)); - } - - // change text to classid - for (let i = 0; i < Config.Runewords.length; i += 1) { - if (Config.Runewords[i][0] !== false) { - if (isNaN(Config.Runewords[i][1])) { - if (NTIPAliasClassID.hasOwnProperty(Config.Runewords[i][1].replace(/\s+/g, "").toLowerCase())) { - Config.Runewords[i][1] = NTIPAliasClassID[Config.Runewords[i][1].replace(/\s+/g, "").toLowerCase()]; - } else { - Misc.errorReport("ÿc1Invalid runewords entry:ÿc0 " + Config.Runewords[i][1]); - Config.Runewords.splice(i, 1); - - i -= 1; - } - } - } - } - - this.buildLists(); - }, - - validItem: function (item) { - return CraftingSystem.validGids.indexOf(item.gid) === -1; - }, - - // build a list of needed runes. won't count runes until the base item is found for a given runeword - buildLists: function () { - this.validGids = []; - this.needList = []; - let baseCheck; - let items = me.findItems(-1, sdk.items.mode.inStorage); - - for (let i = 0; i < Config.Runewords.length; i += 1) { - if (!baseCheck) { - baseCheck = this.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0)) || this.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0), true); - } - - if (this.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0))) { - RuneLoop: - for (let j = 0; j < Config.Runewords[i][0].length; j += 1) { - for (let k = 0; k < items.length; k += 1) { - if (items[k].classid === Config.Runewords[i][0][j] && this.validItem(items[k])) { - this.validGids.push(items[k].gid); - items.splice(k, 1); - - k -= 1; - - continue RuneLoop; - } - } - - this.needList.push(Config.Runewords[i][0][j]); - } - } - } - - // hel rune for rerolling purposes - if (baseCheck) { - let hel = me.getItem(sdk.items.runes.Hel, sdk.items.mode.inStorage); - - if (hel) { - do { - if (this.validGids.indexOf(hel.gid) === -1 && this.validItem(hel)) { - this.validGids.push(hel.gid); - - return; - } - } while (hel.getNext()); - } - - this.needList.push(sdk.items.runes.Hel); - } - }, - - update: function (classid, gid) { - for (let i = 0; i < this.needList.length; i += 1) { - if (this.needList[i] === classid) { - this.needList.splice(i, 1); - - i -= 1; - - break; - } - } - - this.validGids.push(gid); - }, - - // returns an array of items that make a runeword if found, false if we don't have enough items for any - checkRunewords: function () { - let items = me.findItems(-1, sdk.items.mode.inStorage); - - for (let i = 0; i < Config.Runewords.length; i += 1) { - let itemList = []; // reset item list - let base = this.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0)); // check base - - if (base) { - itemList.push(base); // push the base - - for (let j = 0; j < Config.Runewords[i][0].length; j += 1) { - for (let k = 0; k < items.length; k += 1) { - if (items[k].classid === Config.Runewords[i][0][j]) { // rune matched - itemList.push(items[k]); // push into the item list - items.splice(k, 1); // remove from item list as to not count it twice - - k -= 1; - - break; // stop item cycle - we found the item - } - } - - // can't complete runeword - go to next one - if (itemList.length !== j + 2) { - break; - } - - if (itemList.length === Config.Runewords[i][0].length + 1) { // runes + base - return itemList; // these items are our runeword - } - } - } - } - - return false; - }, - - // for pickit - checkItem: function (unit) { - if (!Config.MakeRunewords) return false; - return (unit.itemType === sdk.items.type.Rune && this.needList.includes(unit.classid)); - }, - - // for clearInventory - don't drop runes that are a part of runeword recipe - keepItem: function (unit) { - return this.validGids.includes(unit.gid); - }, - - /* get the base item based on classid and runeword recipe - optional reroll argument = gets a runeword that needs rerolling - rigged to accept item or classid as 2nd arg - */ - getBase: function (runeword, base, ethFlag, reroll) { - let item = typeof base === "object" ? base : me.getItem(base, sdk.items.mode.inStorage); - - if (item) { - do { - if (item && item.quality < sdk.items.quality.Magic && item.sockets === runeword.length) { - /* check if item has items socketed in it - better check than getFlag(sdk.items.flags.Runeword) because randomly socketed items return false for it - */ - - if ((!reroll && !item.getItem()) || (reroll && item.getItem() && !NTIP.CheckItem(item, this.pickitEntries))) { - if (!ethFlag || (ethFlag === Roll.Eth && item.ethereal) || (ethFlag === Roll.NonEth && !item.ethereal)) { - return copyUnit(item); - } - } - } - } while (typeof base !== "object" && item.getNext()); - } - - return false; - }, - - // args named this way to prevent confusion - socketItem: function (base, rune) { - if (!rune.toCursor()) return false; - - for (let i = 0; i < 3; i += 1) { - clickItem(sdk.clicktypes.click.item.Left, base.x, base.y, base.location); - - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (!me.itemoncursor) { - delay(300); - - return true; - } - - delay(10); - } - } - - return false; - }, - - getScroll: function () { - let scroll = me.getItem(sdk.items.ScrollofTownPortal, sdk.items.mode.inStorage); // check if we already have the scroll - if (scroll) return scroll; - - let npc = Town.initNPC("Shop"); - if (!npc) return false; - - scroll = npc.getItem(sdk.items.ScrollofTownPortal); - - if (scroll) { - for (let i = 0; i < 3; i += 1) { - scroll.buy(true); - - if (me.getItem(sdk.items.ScrollofTownPortal)) { - break; - } - } - } - - me.cancel(); - - return me.getItem(sdk.items.ScrollofTownPortal, sdk.items.mode.inStorage); - }, - - makeRunewords: function () { - if (!Config.MakeRunewords) return false; - - while (true) { - this.buildLists(); - - let items = this.checkRunewords(); // get a runeword. format = [base, runes...] - - // can't make runewords - exit loop - if (!items) { - break; - } - - if (!Town.openStash()) return false; - - for (let i = 1; i < items.length; i += 1) { - this.socketItem(items[0], items[i]); - } - - print("ÿc4Runewords: ÿc0Made runeword: " + items[0].fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "")); - D2Bot.printToConsole("Made runeword: " + items[0].fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, ""), sdk.colors.D2Bot.Green); - - if (NTIP.CheckItem(items[0], this.pickitEntries)) { - Misc.itemLogger("Runeword Kept", items[0]); - Misc.logItem("Runeword Kept", items[0]); - } - } - - me.cancel(); - - this.rerollRunewords(); - - return true; - }, - - rerollRunewords: function () { - for (let i = 0; i < Config.Runewords.length; i += 1) { - let hel = me.getItem(sdk.items.runes.Hel, sdk.items.mode.inStorage); - if (!hel) return false; - - let base = this.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0), true); // get a bad runeword - - if (base) { - let scroll = this.getScroll(); - - // failed to get scroll or open stash most likely means we're stuck somewhere in town, so it's better to return false - if (!scroll || !Town.openStash() || !Cubing.emptyCube()) return false; - - // not a fatal error, if the cube can't be emptied, the func will return false on next cycle - if (!Storage.Cube.MoveTo(base) || !Storage.Cube.MoveTo(hel) || !Storage.Cube.MoveTo(scroll)) { - continue; - } - - // probably only happens on server crash - if (!Cubing.openCube()) return false; - - print("ÿc4Runewords: ÿc0Rerolling runeword: " + base.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "")); - D2Bot.printToConsole("Rerolling runeword: " + base.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, ""), sdk.colors.D2Bot.Green); - transmute(); - delay(500); - - // can't pull the item out = no space = fail - if (!Cubing.emptyCube()) return false; - } - } - - this.buildLists(); - - while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { - me.cancel(); - delay(300); - } - - return true; - } -}; diff --git a/d2bs/kolbot/libs/common/Storage.js b/d2bs/kolbot/libs/common/Storage.js deleted file mode 100644 index 39d4fd5a6..000000000 --- a/d2bs/kolbot/libs/common/Storage.js +++ /dev/null @@ -1,397 +0,0 @@ -/** -* @filename Storage.js -* @author McGod, kolton (small kolbot related edits) -* @desc manage inventory, belt, stash, cube -* -*/ - -let Container = function (name, width, height, location) { - let h, w; - - this.name = name; - this.width = width; - this.height = height; - this.location = location; - this.buffer = []; - this.itemList = []; - this.openPositions = this.height * this.width; - - // Initalize the buffer array for use, set all as empty. - for (h = 0; h < this.height; h += 1) { - this.buffer.push([]); - - for (w = 0; w < this.width; w += 1) { - this.buffer[h][w] = 0; - } - } - - /* Container.Mark(item) - * Marks the item in the buffer, and adds it to the item list. - */ - this.Mark = function (item) { - let x, y; - - //Make sure it is in this container. - if (item.location !== this.location || (item.mode !== 0 && item.mode !== 2)) { - return false; - } - - //Mark item in buffer. - for (x = item.x; x < (item.x + item.sizex); x += 1) { - for (y = item.y; y < (item.y + item.sizey); y += 1) { - this.buffer[y][x] = this.itemList.length + 1; - this.openPositions -= 1; - } - } - - //Add item to list. - this.itemList.push(copyUnit(item)); - - return true; - }; - - /* Container.isLocked(item) - * Checks if the item is in a locked spot - */ - this.IsLocked = function (item, baseRef) { - let h, w, reference; - - reference = baseRef.slice(0); - - //Make sure it is in this container. - if (item.mode !== 0 || item.location !== this.location) { - return false; - } - - // Make sure the item is ours - if (!item.getParent() || item.getParent().type !== me.type || item.getParent().gid !== me.gid) { - return false; - } - - //Insure valid reference. - if (typeof (reference) !== "object" || reference.length !== this.buffer.length || reference[0].length !== this.buffer[0].length) { - throw new Error("Storage.IsLocked: Invalid inventory reference"); - } - - try { - // Check if the item lies in a locked spot. - for (h = item.y; h < (item.y + item.sizey); h += 1) { - for (w = item.x; w < (item.x + item.sizex); w += 1) { - if (reference[h][w] === 0) { - return true; - } - } - } - } catch (e2) { - throw new Error("Storage.IsLocked error! Item info: " + item.name + " " + item.y + " " + item.sizey + " " + item.x + " " + item.sizex + " " + item.mode + " " + item.location); - } - - return false; - }; - - this.Reset = function () { - let h, w; - - for (h = 0; h < this.height; h += 1) { - for (w = 0; w < this.width; w += 1) { - this.buffer[h][w] = 0; - } - } - - this.itemList = []; - this.openPositions = this.height * this.width; - return true; - }; - - /* Container.CanFit(item) - * Checks to see if we can fit the item in the buffer. - */ - this.CanFit = function (item) { - return (!!this.FindSpot(item)); - }; - - /* Container.FindSpot(item) - * Finds a spot available in the buffer to place the item. - */ - this.FindSpot = function (item) { - let x, y, nx, ny; - - //Make sure it's a valid item - if (!item) { - return false; - } - - Storage.Reload(); - - //Loop buffer looking for spot to place item. - for (y = 0; y < this.width - (item.sizex - 1); y += 1) { - Loop: - for (x = 0; x < this.height - (item.sizey - 1); x += 1) { - //Check if there is something in this spot. - if (this.buffer[x][y] > 0) { - continue; - } - - //Loop the item size to make sure we can fit it. - for (nx = 0; nx < item.sizey; nx += 1) { - for (ny = 0; ny < item.sizex; ny += 1) { - if (this.buffer[x + nx][y + ny]) { - continue Loop; - } - } - } - - return ({x: x, y: y}); - } - } - - return false; - }; - - /* Container.MoveTo(item) - * Takes any item and moves it into given buffer. - */ - this.MoveTo = function (item) { - let nPos, n, nDelay, cItem, cube; - - try { - //Can we even fit it in here? - nPos = this.FindSpot(item); - - if (!nPos) { - return false; - } - - //Cube -> Stash, must place item in inventory first - if (item.location === 6 && this.location === 7 && !Storage.Inventory.MoveTo(item)) { - return false; - } - - //Can't deal with items on ground! - if (item.mode === 3) { - return false; - } - - //Item already on the cursor. - if (me.itemoncursor && item.mode !== 4) { - return false; - } - - //Make sure stash is open - if (this.location === 7 && !Town.openStash()) { - return false; - } - - //Pick to cursor if not already. - if (!item.toCursor()) { - return false; - } - - //Loop three times to try and place it. - for (n = 0; n < 5; n += 1) { - if (this.location === 6) { // place item into cube - cItem = Game.getCursorUnit(); - cube = me.getItem(sdk.quest.item.Cube); - - if (cItem !== null && cube !== null) { - sendPacket(1, sdk.packets.send.ItemToCube, 4, cItem.gid, 4, cube.gid); - } - } else if (this.location === 2) { - cItem = Game.getCursorUnit(); - if (cItem !== null) { - sendPacket(1, sdk.packets.send.ItemToBelt, 4, cItem.gid, 4, nPos.y); - } - } else { - clickItemAndWait(sdk.clicktypes.click.item.Left, nPos.y, nPos.x, this.location); - } - - nDelay = getTickCount(); - - while ((getTickCount() - nDelay) < Math.max(1000, me.ping * 3 + 500)) { - if (!me.itemoncursor) { - print("Successfully placed " + item.name + " at X: " + nPos.x + " Y: " + nPos.y); - delay(200); - - return true; - } - - delay(10); - } - } - - return true; - } catch (e) { - return false; - } - }; - - /* Container.Dump() - * Prints all known information about container. - */ - this.Dump = function () { - let x, y, string; - - print(this.name + " has the width of " + this.width + " and the height of " + this.height); - print(this.name + " has " + this.itemList.length + " items inside, and has " + this.openPositions + " spots left."); - - for (x = 0; x < this.height; x += 1) { - string = ""; - - for (y = 0; y < this.width; y += 1) { - string += (this.buffer[x][y] > 0) ? "ÿc1x" : "ÿc0o"; - } - - print(string); - } - }; - - /* Container.UsedSpacePercent() - * Returns percentage of the container used. - */ - this.UsedSpacePercent = function () { - let x, y, - usedSpace = 0, - totalSpace = this.height * this.width; - - Storage.Reload(); - - for (x = 0; x < this.height; x += 1) { - for (y = 0; y < this.width; y += 1) { - if (this.buffer[x][y] > 0) { - usedSpace += 1; - } - } - } - - return usedSpace * 100 / totalSpace; - }; - - /* Container.compare(reference) - * Compare given container versus the current one, return all new items in current buffer. - */ - this.Compare = function (baseRef) { - let h, w, n, item, itemList, reference; - - Storage.Reload(); - - try { - itemList = []; - reference = baseRef.slice(0, baseRef.length); - - //Insure valid reference. - if (typeof (reference) !== "object" || reference.length !== this.buffer.length || reference[0].length !== this.buffer[0].length) { - throw new Error("Unable to compare different containers."); - } - - for (h = 0; h < this.height; h += 1) { - Loop: - for (w = 0; w < this.width; w += 1) { - item = this.itemList[this.buffer[h][w] - 1]; - - if (!item) { - continue; - } - - for (n = 0; n < itemList.length; n += 1) { - if (itemList[n].gid === item.gid) { - continue Loop; - } - } - - //Check if the buffers changed and the current buffer has an item there. - if (this.buffer[h][w] > 0 && reference[h][w] > 0) { - itemList.push(copyUnit(item)); - } - } - } - - return itemList; - } catch (e) { - return false; - } - }; - - this.toSource = function () { - return this.buffer.toSource(); - }; -}; - -let Storage = new function () { - this.Init = function () { - this.StashY = me.gametype === 0 ? 4 : 8; - this.Inventory = new Container("Inventory", 10, 4, 3); - this.TradeScreen = new Container("Inventory", 10, 4, 5); - this.Stash = new Container("Stash", 6, this.StashY, 7); - this.Belt = new Container("Belt", 4 * this.BeltSize(), 1, 2); - this.Cube = new Container("Horadric Cube", 3, 4, 6); - this.InvRef = []; - - this.Reload(); - }; - - this.BeltSize = function () { - let item = me.getItem(-1, sdk.items.mode.Equipped); // get equipped item - - if (!item) { // nothing equipped - return 1; - } - - do { - if (item.bodylocation === 8) { // belt slot - switch (item.code) { - case "lbl": // sash - case "vbl": // light belt - return 2; - case "mbl": // belt - case "tbl": // heavy belt - return 3; - default: // everything else - return 4; - } - } - } while (item.getNext()); - - return 1; // no belt - }; - - this.Reload = function () { - this.Inventory.Reset(); - this.Stash.Reset(); - this.Belt.Reset(); - this.Cube.Reset(); - this.TradeScreen.Reset(); - - let item = me.getItem(); - - if (!item) { - return false; - } - - do { - switch (item.location) { - case 3: - this.Inventory.Mark(item); - - break; - case 5: - this.TradeScreen.Mark(item); - - break; - case 2: - this.Belt.Mark(item); - - break; - case 6: - this.Cube.Mark(item); - - break; - case 7: - this.Stash.Mark(item); - - break; - } - } while (item.getNext()); - - return true; - }; -}; diff --git a/d2bs/kolbot/libs/common/Town.js b/d2bs/kolbot/libs/common/Town.js deleted file mode 100644 index 45ca80f64..000000000 --- a/d2bs/kolbot/libs/common/Town.js +++ /dev/null @@ -1,2488 +0,0 @@ -/** -* @filename Town.js -* @author kolton, theBGuy -* @desc do town chores like buying, selling and gambling -* -*/ - -const NPC = { - Akara: getLocaleString(sdk.locale.npcs.Akara).toLowerCase(), - Gheed: getLocaleString(sdk.locale.npcs.Gheed).toLowerCase(), - Charsi: getLocaleString(sdk.locale.npcs.Charsi).toLowerCase(), - Kashya: getLocaleString(sdk.locale.npcs.Kashya).toLowerCase(), - Warriv: getLocaleString(sdk.locale.npcs.Warriv).toLowerCase(), - - Fara: getLocaleString(sdk.locale.npcs.Fara).toLowerCase(), - Drognan: getLocaleString(sdk.locale.npcs.Drognan).toLowerCase(), - Elzix: getLocaleString(sdk.locale.npcs.Elzix).toLowerCase(), - Greiz: getLocaleString(sdk.locale.npcs.Greiz).toLowerCase(), - Lysander: getLocaleString(sdk.locale.npcs.Lysander).toLowerCase(), - Jerhyn: getLocaleString(sdk.locale.npcs.Jerhyn).toLowerCase(), - Meshif: getLocaleString(sdk.locale.npcs.Meshif).toLowerCase(), - Atma: getLocaleString(sdk.locale.npcs.Atma).toLowerCase(), - - Ormus: getLocaleString(sdk.locale.npcs.Ormus).toLowerCase(), - Alkor: getLocaleString(sdk.locale.npcs.Alkor).toLowerCase(), - Hratli: getLocaleString(sdk.locale.npcs.Hratli).toLowerCase(), - Asheara: getLocaleString(sdk.locale.npcs.Asheara).toLowerCase(), - - Jamella: getLocaleString(sdk.locale.npcs.Jamella).toLowerCase(), - Halbu: getLocaleString(sdk.locale.npcs.Halbu).toLowerCase(), - Tyrael: getLocaleString(sdk.locale.npcs.Tyrael).toLowerCase(), - - Malah: getLocaleString(sdk.locale.npcs.Malah).toLowerCase(), - Anya: getLocaleString(sdk.locale.npcs.Anya).toLowerCase(), - Larzuk: getLocaleString(sdk.locale.npcs.Larzuk).toLowerCase(), - Qual_Kehk: getLocaleString(sdk.locale.npcs.QualKehk).toLowerCase(), - Nihlathak: getLocaleString(sdk.locale.npcs.Nihlathak2).toLowerCase(), - - Cain: getLocaleString(sdk.locale.npcs.DeckardCain).toLowerCase() -}; - -const Town = { - telekinesis: true, - sellTimer: getTickCount(), // shop speedup test - lastInteractedNPC: { - unit: null, - tick: 0, - set: function (npc) { - this.unit = npc; - this.tick = getTickCount(); - }, - get: function () { - try { - if (!!this.unit && getTickCount() - this.tick < Time.seconds(15) - && this.unit.name.toLowerCase() !== "an evil force" && this.unit.area === me.area) { - Config.DebugMode && console.debug("used stored value"); - return this.unit; - } else { - this.reset(); - Config.DebugMode && console.debug("getting new npc"); - return getInteractedNPC(); - } - } catch (e) { - Config.DebugMode && console.error(e); - this.reset(); - Config.DebugMode && console.debug("getting new npc"); - return getInteractedNPC(); - } - }, - reset: function () { - this.unit = null; - this.tick = 0; - } - }, - - tasks: [ - {Heal: NPC.Akara, Shop: NPC.Akara, Gamble: NPC.Gheed, Repair: NPC.Charsi, Merc: NPC.Kashya, Key: NPC.Akara, CainID: NPC.Cain}, - {Heal: NPC.Fara, Shop: NPC.Drognan, Gamble: NPC.Elzix, Repair: NPC.Fara, Merc: NPC.Greiz, Key: NPC.Lysander, CainID: NPC.Cain}, - {Heal: NPC.Ormus, Shop: NPC.Ormus, Gamble: NPC.Alkor, Repair: NPC.Hratli, Merc: NPC.Asheara, Key: NPC.Hratli, CainID: NPC.Cain}, - {Heal: NPC.Jamella, Shop: NPC.Jamella, Gamble: NPC.Jamella, Repair: NPC.Halbu, Merc: NPC.Tyrael, Key: NPC.Jamella, CainID: NPC.Cain}, - {Heal: NPC.Malah, Shop: NPC.Malah, Gamble: NPC.Anya, Repair: NPC.Larzuk, Merc: NPC.Qual_Kehk, Key: NPC.Malah, CainID: NPC.Cain} - ], - - ignoredItemTypes: [ - // Items that won't be stashed - sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver, sdk.items.type.Book, - sdk.items.type.Scroll, sdk.items.type.Key, sdk.items.type.HealingPotion, - sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion, sdk.items.type.StaminaPotion, - sdk.items.type.AntidotePotion, sdk.items.type.ThawingPotion - ], - - ignoreType: function (type) { - return Town.ignoredItemTypes.includes(type); - }, - - needPotions: function () { - // we aren't using MinColumn if none of the values are set - if (!Config.MinColumn.some(el => el > 0)) return false; - // no hp pots or mp pots in Config.BeltColumn (who uses only rejuv pots?) - if (!Config.BeltColumn.some(el => ["hp", "mp"].includes(el))) return false; - - // Start - if (me.charlvl > 2 && me.gold > 1000) { - let pots = { - hp: [], - mp: [], - }; - me.getItemsEx(-1, sdk.items.mode.inBelt) - .filter(p => [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion].includes(p.itemType) && p.x < 4) - .forEach(p => { - if (p.itemType === sdk.items.type.HealingPotion) { - pots.hp.push(copyUnit(p)); - } else if (p.itemType === sdk.items.type.ManaPotion) { - pots.mp.push(copyUnit(p)); - } - }); - - // quick check - if ((Config.BeltColumn.includes("hp") && !pots.hp.length) - || (Config.BeltColumn.includes("mp") && !pots.mp.length)) { - return true; - } - - // if we have no belt what should qualify is to go to town at this point? - // we've confirmed having at least some potions in the above check - // if (!me.inTown && Storage.BeltSize() === 1) return false; - - // should we check the actual amount in the column? - // For now just keeping the way it was and checking if a column is empty - for (let i = 0; i < 4; i += 1) { - if (Config.MinColumn[i] <= 0) { - continue; - } - - switch (Config.BeltColumn[i]) { - case "hp": - if (!pots.hp.some(p => p.x === i)) { - console.debug("Column: " + (i + 1) + " needs hp pots"); - return true; - } - break; - case "mp": - if (!pots.mp.some(p => p.x === i)) { - console.debug("Column: " + (i + 1) + " needs mp pots"); - return true; - } - break; - } - } - } - - return false; - }, - - // Do town chores - doChores: function (repair = false) { - delay(250); - - console.time("doChores"); - console.info(true); - - !me.inTown && this.goToTown(); - if (!Misc.poll(() => me.gameReady && me.inTown, 2000, 250)) throw new Error("Failed to go to town for chores"); - - let preAct = me.act; - - // Burst of speed while in town - if (Skill.canUse(sdk.skills.BurstofSpeed) && !me.getState(sdk.states.BurstofSpeed)) { - Skill.cast(sdk.skills.BurstofSpeed, sdk.skills.hand.Right); - } - - me.switchWeapons(Attack.getPrimarySlot()); - - this.heal(); - this.identify(); - this.clearInventory(); - this.fillTome(sdk.items.TomeofTownPortal); - this.buyPotions(); - Config.FieldID.Enabled && this.fillTome(sdk.items.TomeofIdentify); - this.shopItems(); - this.buyKeys(); - this.repair(repair); - this.gamble(); - this.reviveMerc(); - Cubing.doCubing(); - Runewords.makeRunewords(); - this.stash(true); - !!me.getItem(sdk.items.TomeofTownPortal) && this.clearScrolls(); - - me.act !== preAct && this.goToTown(preAct); - me.cancelUIFlags(); - !me.barbarian && Precast.haveCTA === -1 && Precast.doPrecast(false); - - delay(250); - console.info(false, null, "doChores"); - - return true; - }, - - npcInteract: function (name = "", cancel = true) { - !name.includes("_") && (name = name.capitalize(true)); - name.includes("_") && (name = "Qual_Kehk"); - - !me.inTown && Town.goToTown(); - me.cancelUIFlags(); - - switch (NPC[name]) { - case NPC.Jerhyn: - !Game.getNPC(NPC.Jerhyn) && Town.move("palace"); - break; - case NPC.Hratli: - if (!me.getQuest(sdk.quest.id.SpokeToHratli, sdk.quest.states.Completed)) { - Town.move(NPC.Meshif); - break; - } - // eslint-disable-next-line no-fallthrough - default: - Town.move(NPC[name]); - } - - let npc = Game.getNPC(NPC[name]); - - // In case Jerhyn is by Warriv - if (name === "Jerhyn" && !npc) { - me.cancel(); - Pather.moveTo(5166, 5206); - npc = Game.getNPC(NPC[name]); - } - - Packet.flash(me.gid); - delay(40); - - if (npc && npc.openMenu()) { - cancel && me.cancel(); - this.lastInteractedNPC.set(npc); - return npc; - } - - return false; - }, - - // just consumables - checkQuestItems: function () { - // Radament skill book - let book = me.getItem(sdk.quest.item.BookofSkill); - if (book) { - book.isInStash && this.openStash() && delay(300 + me.ping); - book.use(); - } - - // Act 3 - // Figurine -> Golden Bird - if (me.getItem(sdk.quest.item.AJadeFigurine)) { - Town.goToTown(3) && Town.npcInteract("meshif"); - } - - // Golden Bird -> Ashes - if (me.getItem(sdk.items.quest.TheGoldenBird)) { - Town.goToTown(3) && Town.npcInteract("alkor"); - } - - // Potion of life - let pol = me.getItem(sdk.quest.item.PotofLife); - if (pol) { - pol.isInStash && this.openStash() && delay(300 + me.ping); - pol.use(); - } - - // LamEssen's Tome - let tome = me.getItem(sdk.quest.item.LamEsensTome); - if (tome) { - !me.inTown && Town.goToTown(3); - tome.isInStash && Town.openStash() && Storage.Inventory.MoveTo(tome); - Town.npcInteract("alkor"); - } - - // Scroll of resistance - let sor = me.getItem(sdk.items.quest.ScrollofResistance); - if (sor) { - sor.isInStash && this.openStash() && delay(300 + me.ping); - sor.use(); - } - }, - - getTpTool: function () { - let items = me.getItemsEx(-1, sdk.items.mode.inStorage).filter((item) => item.isInInventory && [sdk.items.ScrollofTownPortal, sdk.items.TomeofTownPortal].includes(item.classid)); - if (!items.length) return null; - let tome = items.find((i) => i.classid === sdk.items.TomeofTownPortal && i.getStat(sdk.stats.Quantity) > 0); - if (tome) return tome; - let scroll = items.find((i) => i.classid === sdk.items.ScrollofTownPortal); - if (scroll) return scroll; - return null; - }, - - getIdTool: function () { - let items = me.getItemsEx().filter((i) => i.isInInventory && [sdk.items.ScrollofIdentify, sdk.items.TomeofIdentify].includes(i.classid)); - if (!items.length) return null; - let tome = items.find((i) => i.isInInventory && i.classid === sdk.items.TomeofIdentify && i.getStat(sdk.stats.Quantity) > 0); - if (tome) return tome; - let scroll = items.find((i) => i.isInInventory && i.classid === sdk.items.ScrollofIdentify); - if (scroll) return scroll; - return null; - }, - - canTpToTown: function () { - // can't tp if dead - if (me.dead) return false; - let badAreas = [ - sdk.areas.RogueEncampment, sdk.areas.LutGholein, sdk.areas.KurastDocktown, - sdk.areas.PandemoniumFortress, sdk.areas.Harrogath, sdk.areas.ArreatSummit, sdk.areas.UberTristram - ]; - let myArea = me.area; - // can't tp from town or Uber Trist, and shouldn't tp from arreat summit - if (badAreas.includes(myArea)) return false; - // If we made it this far, we can only tp if we even have a tp - return !!this.getTpTool(); - }, - - // Start a task and return the NPC Unit - initNPC: function (task = "", reason = "undefined") { - console.time("initNPC"); - console.info(true, reason); - task = task.capitalize(false); - - delay(250); - - let npc = this.lastInteractedNPC.get(); - let justUseClosest = (["clearInventory", "sell"].includes(reason) && !Town.getUnids()); - - try { - if (!!npc) { - if (!justUseClosest && ((npc.name.toLowerCase() !== this.tasks[me.act - 1][task]) - // Jamella gamble fix - || (task === "Gamble" && npc.name.toLowerCase() === NPC.Jamella))) { - me.cancelUIFlags(); - npc = null; - this.lastInteractedNPC.reset(); - } - } - - // we are just trying to clear our inventory, use the closest npc - // what if we have unid items? Should we use cain if he is closer than the npc with scrolls? - // for now it won't get here with unids - // need to also take into account what our next task is - if (justUseClosest) { - let npcs = this.tasks[me.act - 1]; - npc = getUnits(sdk.unittype.NPC) - .sort((a, b) => a.distance - b.distance) - .find(unit => [npcs.Shop, npcs.Repair].includes(unit.name.toLowerCase())); - } - - if (!npc) { - npc = Game.getNPC(this.tasks[me.act - 1][task]); - - if (!npc) { - this.move(this.tasks[me.act - 1][task]); - npc = Game.getNPC(this.tasks[me.act - 1][task]); - } - } - - if (!npc || npc.area !== me.area || (!getUIFlag(sdk.uiflags.NPCMenu) && !npc.openMenu())) { - throw new Error("Couldn't interact with npc"); - } - - delay(40); - - switch (task) { - case "Shop": - case "Repair": - case "Gamble": - if (!getUIFlag(sdk.uiflags.Shop) && !npc.startTrade(task)) { - throw new Error("Failed to complete " + reason + " at " + npc.name); - } - break; - case "Key": - if (!getUIFlag(sdk.uiflags.Shop) && !npc.startTrade(me.act === 3 ? "Repair" : "Shop")) { - throw new Error("Failed to complete " + reason + " at " + npc.name); - } - break; - case "CainID": - Misc.useMenu(sdk.menu.IdentifyItems); - me.cancelUIFlags(); - - break; - case "Heal": - break; - } - - console.info(false, "Did " + reason + " at " + npc.name, "initNPC"); - } catch (e) { - console.error(e); - - if (!!e.message && e.message === "Couldn't interact with npc") { - // getUnit bug probably, lets see if going to different act helps - let highestAct = me.highestAct; - if (highestAct === 1) return false; // can't go to any of the other acts - let myAct = me.act; - let potentialActs = [1, 2, 3, 4, 5].filter(a => a <= highestAct && a !== myAct); - let goTo = potentialActs[rand(0, potentialActs.length - 1)]; - Config.DebugMode && console.debug("Going to Act " + goTo + " to see if it fixes getUnit bug"); - Town.goToTown(goTo); - } - - return false; - } - - Misc.poll(() => me.gameReady, 2000, 250); - this.lastInteractedNPC.set(npc); - - if (task === "Heal") { - Config.DebugMode && console.debug("Checking if we are frozen"); - if (me.getState(sdk.states.Frozen)) { - console.log("We are frozen, lets unfreeze real quick with some thawing pots"); - Town.buyPots(2, sdk.items.ThawingPotion, true, true, npc); - } - } - - return npc; - }, - - // Go to a town healer - heal: function () { - if (!this.needHealing()) return true; - return !!(this.initNPC("Heal", "heal")); - }, - - // Check if healing is needed, based on character config - needHealing: function () { - if (me.hpPercent <= Config.HealHP || me.mpPercent <= Config.HealMP) return true; - - // Status effects - return (Config.HealStatus - && [ - sdk.states.Poison, - sdk.states.AmplifyDamage, - sdk.states.Frozen, - sdk.states.Weaken, - sdk.states.Decrepify, - sdk.states.LowerResist - ].some((state) => me.getState(state))); - }, - - buyPotions: function () { - // Ain't got money fo' dat shyt - if (me.gold < 1000) return false; - - this.clearBelt(); - const buffer = { hp: 0, mp: 0 }; - const beltSize = Storage.BeltSize(); - let [needPots, needBuffer, specialCheck] = [false, true, false]; - let col = this.checkColumns(beltSize); - - const getNeededBuffer = () => { - [buffer.hp, buffer.mp] = [0, 0]; - me.getItemsEx().filter(function (p) { - return p.isInInventory && [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion].includes(p.itemType); - }).forEach(function (p) { - switch (p.itemType) { - case sdk.items.type.HealingPotion: - return (buffer.hp++); - case sdk.items.type.ManaPotion: - return (buffer.mp++); - } - return false; - }); - }; - - // HP/MP Buffer - (Config.HPBuffer > 0 || Config.MPBuffer > 0) && getNeededBuffer(); - - // Check if we need to buy potions based on Config.MinColumn - if (Config.BeltColumn.some((c, i) => ["hp", "mp"].includes(c) && col[i] > (beltSize - Math.min(Config.MinColumn[i], beltSize)))) { - needPots = true; - } - - // Check if we need any potions for buffers - if (buffer.mp < Config.MPBuffer || buffer.hp < Config.HPBuffer) { - if (Config.BeltColumn.some((c, i) => col[i] >= beltSize && (!needPots || c === "rv"))) { - specialCheck = true; - } - } - - // We have enough potions in inventory - (buffer.mp >= Config.MPBuffer && buffer.hp >= Config.HPBuffer) && (needBuffer = false); - - // No columns to fill - if (!needPots && !needBuffer) return true; - // todo: buy the cheaper potions if we are low on gold or don't need the higher ones i.e have low mana/health pool - // why buy potion that heals 225 (greater mana) if we only have sub 100 mana - me.normal && me.highestAct >= 4 && me.act < 4 && this.goToTown(4); - - let npc = this.initNPC("Shop", "buyPotions"); - if (!npc) return false; - - // special check, sometimes our rejuv slot is empty but we do still need buffer. Check if we can buy something to slot there - if (specialCheck && Config.BeltColumn.some((c, i) => c === "rv" && col[i] >= beltSize)) { - let pots = [sdk.items.ThawingPotion, sdk.items.AntidotePotion, sdk.items.StaminaPotion]; - Config.BeltColumn.forEach((c, i) => { - if (c === "rv" && col[i] >= beltSize && pots.length) { - let usePot = pots[0]; - let pot = npc.getItem(usePot); - if (pot) { - Storage.Inventory.CanFit(pot) && Packet.buyItem(pot, false); - pot = me.getItemsEx(usePot, sdk.items.mode.inStorage).filter(i => i.isInInventory).first(); - !!pot && Packet.placeInBelt(pot, i); - pots.shift(); - } else { - needBuffer = false; // we weren't able to find any pots to buy - } - } - }); - } - - for (let i = 0; i < 4; i += 1) { - if (col[i] > 0) { - let useShift = this.shiftCheck(col, beltSize); - let pot = this.getPotion(npc, Config.BeltColumn[i]); - - if (pot) { - //print("ÿc2column ÿc0" + i + "ÿc2 needs ÿc0" + col[i] + " ÿc2potions"); - // Shift+buy will trigger if there's no empty columns or if only the current column is empty - if (useShift) { - pot.buy(true); - } else { - for (let j = 0; j < col[i]; j += 1) { - pot.buy(false); - } - } - } - } - - col = this.checkColumns(beltSize); // Re-initialize columns (needed because 1 shift-buy can fill multiple columns) - } - - // re-check - !needBuffer && (Config.HPBuffer > 0 || Config.MPBuffer > 0) && getNeededBuffer(); - - if (needBuffer && buffer.hp < Config.HPBuffer) { - for (let i = 0; i < Config.HPBuffer - buffer.hp; i += 1) { - let pot = this.getPotion(npc, "hp"); - !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false); - } - } - - if (needBuffer && buffer.mp < Config.MPBuffer) { - for (let i = 0; i < Config.MPBuffer - buffer.mp; i += 1) { - let pot = this.getPotion(npc, "mp"); - !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false); - } - } - - return true; - }, - - // Check when to shift-buy potions - shiftCheck: function (col, beltSize) { - let fillType; - - for (let i = 0; i < col.length; i += 1) { - // Set type based on non-empty column - if (!fillType && col[i] > 0 && col[i] < beltSize) { - fillType = Config.BeltColumn[i]; - } - - if (col[i] >= beltSize) { - switch (Config.BeltColumn[i]) { - case "hp": - !fillType && (fillType = "hp"); - if (fillType !== "hp") return false; - - break; - case "mp": - !fillType && (fillType = "mp"); - if (fillType !== "mp") return false; - - break; - case "rv": // Empty rejuv column = can't shift-buy - return false; - } - } - } - - return true; - }, - - // Return column status (needed potions in each column) - checkColumns: function (beltSize) { - let col = [beltSize, beltSize, beltSize, beltSize]; - let pot = me.getItem(-1, sdk.items.mode.inBelt); - - // No potions - if (!pot) return col; - - do { - col[pot.x % 4] -= 1; - } while (pot.getNext()); - - return col; - }, - - // Get the highest potion from current npc - getPotion: function (npc, type, highestPot = 5) { - if (!type) return false; - - if (type === "hp" || type === "mp") { - for (let i = highestPot; i > 0; i -= 1) { - let result = npc.getItem(type + i); - - if (result) { - return result; - } - } - } - - return false; - }, - - fillTome: function (classid) { - if (me.gold < 450) return false; - if (this.checkScrolls(classid) >= 13) return true; - - let npc = this.initNPC("Shop", "fillTome"); - if (!npc) return false; - - delay(500); - - if (classid === sdk.items.TomeofTownPortal && !me.findItem(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory)) { - let tome = npc.getItem(sdk.items.TomeofTownPortal); - - if (tome && Storage.Inventory.CanFit(tome)) { - try { - tome.buy(); - } catch (e1) { - print(e1); - // Couldn't buy the tome, don't spam the scrolls - return false; - } - } else { - return false; - } - } - - let scroll = npc.getItem(classid === sdk.items.TomeofTownPortal ? sdk.items.ScrollofTownPortal : sdk.items.ScrollofIdentify); - if (!scroll) return false; - - try { - scroll.buy(true); - } catch (e2) { - print(e2.message); - - return false; - } - - return true; - }, - - checkScrolls: function (id) { - let tome = me.findItem(id, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (!tome) { - switch (id) { - case sdk.items.TomeofIdentify: - case "ibk": - return Config.FieldID.Enabled ? 0 : 20; // Ignore missing ID tome if we aren't using field ID - case sdk.items.TomeofTownPortal: - case "tbk": - return 0; // Force TP tome check - } - } - - return tome.getStat(sdk.stats.Quantity); - }, - - identify: function () { - me.cancelUIFlags(); - - if (this.cainID()) return true; - - let list = (Storage.Inventory.Compare(Config.Inventory) || []); - if (list.length === 0) return false; - - // Avoid unnecessary NPC visits - // Only unid items or sellable junk (low level) should trigger a NPC visit - if (!list.some(item => { - let identified = item.identified; - return ((!identified || Config.LowGold > 0) && ([Pickit.Result.UNID, Pickit.Result.TRASH].includes(Pickit.checkItem(item).result)/* || (!identified && AutoEquip.hasTier(item)) */)); - })) { - return false; - } - - let npc = this.initNPC("Shop", "identify"); - if (!npc) return false; - - let tome = me.findItem(sdk.items.TomeofIdentify, sdk.items.mode.inStorage, sdk.storage.Inventory); - !!tome && tome.getStat(sdk.stats.Quantity) < list.length && this.fillTome(sdk.items.TomeofIdentify); - - MainLoop: - while (list.length > 0) { - let item = list.shift(); - - if (!item.identified && item.isInInventory && !this.ignoredItemTypes.includes(item.itemType)) { - let result = Pickit.checkItem(item); - - // Force ID for unid items matching autoEquip criteria - //result.result === 1 && !item.identified && Item.hasTier(item) && (result.result = -1); - - switch (result.result) { - // Items for gold, will sell magics, etc. w/o id, but at low levels - // magics are often not worth iding. - case Pickit.Result.TRASH: - Misc.itemLogger("Sold", item); - item.sell(); - - break; - case Pickit.Result.UNID: - let idTool = tome ? tome : Town.getIdTool(); - - if (idTool) { - this.identifyItem(item, idTool); - } else { - let scroll = npc.getItem(sdk.items.ScrollofIdentify); - - if (scroll) { - if (!Storage.Inventory.CanFit(scroll)) { - let tpTome = me.findItem(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (tpTome) { - tpTome.sell(); - delay(500); - } - } - - delay(500); - - Storage.Inventory.CanFit(scroll) && scroll.buy(); - } - - scroll = me.findItem(sdk.items.ScrollofIdentify, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (!scroll) { - break MainLoop; - } - - this.identifyItem(item, scroll); - } - - result = Pickit.checkItem(item); - - // should autoequip even be checked by default? - //!Item.autoEquipCheck(item) && (result.result = 0); - - switch (result.result) { - case Pickit.Result.WANTED: - // Couldn't id autoEquip item. Don't log it. - // if (result.result === 1 && Config.AutoEquip && !item.indentifed && Item.autoEquipCheck(item)) { - // break; - // } - - Misc.itemLogger("Kept", item); - Misc.logItem("Kept", item, result.line); - - break; - case Pickit.Result.UNID: - case Pickit.Result.RUNEWORD: // (doesn't trigger normally) - break; - case Pickit.Result.CUBING: - Misc.itemLogger("Kept", item, "Cubing-Town"); - Cubing.update(); - - break; - case Pickit.Result.CRAFTING: - Misc.itemLogger("Kept", item, "CraftSys-Town"); - CraftingSystem.update(item); - - break; - default: - Misc.itemLogger("Sold", item); - item.sell(); - - let timer = getTickCount() - this.sellTimer; // shop speedup test - - if (timer > 0 && timer < 500) { - delay(timer); - } - - break; - } - - break; - } - } - } - - this.fillTome(sdk.items.TomeofTownPortal); // Check for TP tome in case it got sold for ID scrolls - - return true; - }, - - cainID: function () { - // Not enabled or Check if we may use Cain - minimum gold - if (!Config.CainID.Enable || me.gold < Config.CainID.MinGold) return false; - - // Check if we're already in a shop. It would be pointless to go to Cain if so. - let npc = getInteractedNPC(); - if (npc && npc.name.toLowerCase() === this.tasks[me.act - 1].Shop) return false; - - me.cancel(); - this.stash(false); - - let unids = this.getUnids(); - - if (unids) { - // Check if we may use Cain - number of unid items - if (unids.length < Config.CainID.MinUnids) return false; - - // Check if we may use Cain - kept unid items - for (let i = 0; i < unids.length; i += 1) { - if (Pickit.checkItem(unids[i]).result > 0) return false; - } - - let cain = this.initNPC("CainID", "cainID"); - if (!cain) return false; - - for (let i = 0; i < unids.length; i += 1) { - let result = Pickit.checkItem(unids[i]); - - //!Item.autoEquipCheck(unids[i]) && (result = 0); - - switch (result.result) { - case Pickit.Result.UNWANTED: - Misc.itemLogger("Dropped", unids[i], "cainID"); - unids[i].drop(); - - break; - case Pickit.Result.WANTED: - Misc.itemLogger("Kept", unids[i]); - Misc.logItem("Kept", unids[i], result.line); - - break; - default: - break; - } - } - } - - return true; - }, - - // Identify items while in the field if we have a id tome - fieldID: function () { - let list = this.getUnids(); - if (!list) return false; - - let tome = me.findItem(sdk.items.TomeofIdentify, sdk.items.mode.inStorage, sdk.storage.Inventory); - if (!tome || tome.getStat(sdk.stats.Quantity) < list.length) return false; - - while (list.length > 0) { - let item = list.shift(); - let result = Pickit.checkItem(item); - - // Force ID for unid items matching autoEquip criteria - //result.result === 1 && !item.getFlag(sdk.items.flags.Identified) && Item.hasTier(item) && (result.result = -1); - - // unid item that should be identified - if (result.result === Pickit.Result.UNID) { - this.identifyItem(item, tome, Config.FieldID.PacketID); - delay(me.ping + 1); - result = Pickit.checkItem(item); - - //!Item.autoEquipCheck(item) && (result.result = 0); - - switch (result.result) { - case Pickit.Result.UNWANTED: - Misc.itemLogger("Dropped", item, "fieldID"); - - if (Config.DroppedItemsAnnounce.Enable && Config.DroppedItemsAnnounce.Quality.includes(item.quality)) { - say("Dropped: [" + Pickit.itemQualityToName(item.quality).charAt(0).toUpperCase() + Pickit.itemQualityToName(item.quality).slice(1) + "] " + item.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "").trim()); - - if (Config.DroppedItemsAnnounce.LogToOOG && Config.DroppedItemsAnnounce.OOGQuality.includes(item.quality)) { - Misc.logItem("Field Dropped", item, result.line); - } - } - - item.drop(); - - break; - case Pickit.Result.WANTED: - Misc.itemLogger("Field Kept", item); - Misc.logItem("Field Kept", item, result.line); - - break; - default: - break; - } - } - } - - delay(200); - me.cancel(); - - return true; - }, - - getUnids: function () { - let list = []; - let item = me.getItem(-1, sdk.items.mode.inStorage); - - if (!item) return false; - - do { - if (item.isInInventory && !item.identified) { - list.push(copyUnit(item)); - } - } while (item.getNext()); - - if (!list.length) return false; - - return list; - }, - - identifyItem: function (unit, tome, packetID = false) { - if (Config.PacketShopping || packetID) return Packet.identifyItem(unit, tome); - if (!unit || unit.identified) return false; - - this.sellTimer = getTickCount(); // shop speedup test - - CursorLoop: - for (let i = 0; i < 3; i += 1) { - clickItem(sdk.clicktypes.click.item.Right, tome); - - let tick = getTickCount(); - - while (getTickCount() - tick < 500) { - if (getCursorType() === sdk.cursortype.Identify) { - break CursorLoop; - } - - delay(10); - } - } - - if (getCursorType() !== sdk.cursortype.Identify) return false; - - delay(270); - - for (let i = 0; i < 3; i += 1) { - if (getCursorType() === sdk.cursortype.Identify) { - clickItem(sdk.clicktypes.click.item.Left, unit); - } - - let tick = getTickCount(); - - while (getTickCount() - tick < 500) { - if (unit.identified) { - delay(50); - - return true; - } - - delay(10); - } - - delay(300); - } - - return false; - }, - - shopItems: function () { - if (!Config.MiniShopBot) return true; - - let npc = getInteractedNPC(); - if (!npc || !npc.itemcount) return false; - - let items = npc.getItemsEx().filter((item) => Town.ignoredItemTypes.indexOf(item.itemType) === -1); - if (!items.length) return false; - - print("ÿc4MiniShopBotÿc0: Scanning " + npc.itemcount + " items."); - - for (let i = 0; i < items.length; i += 1) { - let result = Pickit.checkItem(items[i]); - - if (result.result === Pickit.Result.WANTED/* && Item.autoEquipCheck(items[i]) */) { - try { - if (Storage.Inventory.CanFit(items[i]) && me.gold >= items[i].getItemCost(sdk.items.cost.ToBuy)) { - Misc.itemLogger("Shopped", items[i]); - Misc.logItem("Shopped", items[i], result.line); - items[i].buy(); - } - } catch (e) { - print(e); - } - } - - delay(2); - } - - return true; - }, - - gambleIds: [], - - gamble: function () { - if (!this.needGamble() || Config.GambleItems.length === 0) return true; - if (this.gambleIds.length === 0) { - // change text to classid - for (let i = 0; i < Config.GambleItems.length; i += 1) { - if (isNaN(Config.GambleItems[i])) { - if (NTIPAliasClassID.hasOwnProperty(Config.GambleItems[i].replace(/\s+/g, "").toLowerCase())) { - this.gambleIds.push(NTIPAliasClassID[Config.GambleItems[i].replace(/\s+/g, "").toLowerCase()]); - } else { - Misc.errorReport("ÿc1Invalid gamble entry:ÿc0 " + Config.GambleItems[i]); - } - } else { - this.gambleIds.push(Config.GambleItems[i]); - } - } - } - - if (this.gambleIds.length === 0) return true; - - // avoid Alkor - me.act === 3 && this.goToTown(2); - let npc = this.initNPC("Gamble", "gamble"); - - if (!npc) return false; - - let list = []; - let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory); - - while (items && items.length > 0) { - list.push(items.shift().gid); - } - - while (me.gold >= Config.GambleGoldStop) { - !getInteractedNPC() && npc.startTrade("Gamble"); - - let item = npc.getItem(); - items = []; - - if (item) { - do { - if (this.gambleIds.includes(item.classid)) { - items.push(copyUnit(item)); - } - } while (item.getNext()); - - for (let i = 0; i < items.length; i += 1) { - if (!Storage.Inventory.CanFit(items[i])) { - return false; - } - - //me.overhead("Buy: " + items[i].name); - items[i].buy(false, true); - let newItem = this.getGambledItem(list); - - if (newItem) { - let result = Pickit.checkItem(newItem); - - //!Item.autoEquipCheck(newItem) && (result = 0); - - switch (result.result) { - case Pickit.Result.WANTED: - Misc.itemLogger("Gambled", newItem); - Misc.logItem("Gambled", newItem, result.line); - list.push(newItem.gid); - - break; - case Pickit.Result.CUBING: - list.push(newItem.gid); - Cubing.update(); - - break; - case Pickit.Result.CRAFTING: - CraftingSystem.update(newItem); - - break; - default: - Misc.itemLogger("Sold", newItem, "Gambling"); - me.overhead("Sell: " + newItem.name); - newItem.sell(); - - if (!Config.PacketShopping) { - delay(500); - } - - break; - } - } - } - } - - me.cancel(); - } - - return true; - }, - - needGamble: function () { - return Config.Gamble && me.gold >= Config.GambleGoldStart; - }, - - getGambledItem: function (list = []) { - let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory); - - for (let i = 0; i < items.length; i += 1) { - if (list.indexOf(items[i].gid) === -1) { - for (let j = 0; j < 3; j += 1) { - if (items[i].identified) { - break; - } - - delay(100); - } - - return items[i]; - } - } - - return false; - }, - - buyPots: function (quantity = 0, type = undefined, drink = false, force = false, npc = null) { - if (!quantity || !type) return false; - - // convert to classid if isn't one - typeof type === "string" && (type = (sdk.items[type.capitalize(true) + "Potion"] || false)); - if (!type) return false; - - // todo - change act in a3 if we are next to the wp as it's faster than going all the way to Alkor - // todo - compare distance Ormus -> Alkor compared to Ormus -> WP -> Akara - let potDealer = ["Akara", "Lysander", "Alkor", "Jamella", "Malah"][me.act - 1]; - - switch (type) { - case sdk.items.ThawingPotion: - // Don't buy if already at max res - if (!force && me.coldRes >= 75) return true; - console.info(null, "Current cold resistance: " + me.coldRes); - - break; - case sdk.items.AntidotePotion: - // Don't buy if already at max res - if (!force && me.poisonRes >= 75) return true; - console.info(null, "Current poison resistance: " + me.poisonRes); - - break; - case sdk.items.StaminaPotion: - // Don't buy if teleport or vigor - if (!force && (Skill.canUse(sdk.skills.Vigor) || Pather.canTeleport())) return true; - - break; - } - - npc = !!npc ? npc : Town.lastInteractedNPC.get(); - - try { - if (!!npc && npc.name.toLowerCase() === NPC[potDealer] && !getUIFlag(sdk.uiflags.Shop)) { - if (!npc.startTrade("Shop")) throw new Error("Failed to open " + npc.name + " trade menu"); - } else { - me.cancelUIFlags(); - npc = null; - Town.lastInteractedNPC.reset(); - - Town.move(NPC[potDealer]); - npc = Game.getNPC(NPC[potDealer]); - - if (!npc || !npc.openMenu() || !npc.startTrade("Shop")) throw new Error("Failed to open " + npc.name + " trade menu"); - Town.lastInteractedNPC.set(npc); - } - } catch (e) { - console.error(e); - - return false; - } - - let pot = npc.getItem(type); - if (!pot) { - console.warn("Couldn't find " + type + " from " + npc.name); - return false; - } - let name = (pot.name || ""); - - console.info(null, "Buying " + quantity + " " + name + "s"); - - for (let pots = 0; pots < quantity; pots++) { - if (!!pot && Storage.Inventory.CanFit(pot)) { - Packet.buyItem(pot, false); - } - } - - me.cancelUIFlags(); - drink && Town.drinkPots(type); - - return true; - }, - - drinkPots: function (type = undefined, log = true) { - // convert to classid if isn't one - typeof type === "string" && (type = (sdk.items[type.capitalize(true) + "Potion"] || false)); - - let name = ""; - let quantity = 0; - let chugs = me.getItemsEx(type).filter(pot => pot.isInInventory); - - if (chugs.length > 0) { - name = chugs.first().name; - let pingDelay = me.getPingDelay(); - - chugs.forEach(function (pot) { - if (!!pot && pot.use()) { - quantity++; - } - }); - - log && name && console.info(null, "Drank " + quantity + " " + name + "s. Timer [" + Time.format(quantity * 30 * 1000) + "]"); - } else { - console.warn("couldn't find my pots"); - } - - return { - potName: name, - quantity: quantity - }; - }, - - buyKeys: function () { - if (!this.wantKeys()) return true; - - // avoid Hratli - me.act === 3 && this.goToTown(Pather.accessToAct(4) ? 4 : 2); - - let npc = this.initNPC("Key", "buyKeys"); - if (!npc) return false; - - let key = npc.getItem("key"); - if (!key) return false; - - try { - key.buy(true); - } catch (e) { - console.error(e); - - return false; - } - - return true; - }, - - checkKeys: function () { - if (!Config.OpenChests.Enabled || me.assassin || me.gold < 540 || (!me.getItem("key") && !Storage.Inventory.CanFit({sizex: 1, sizey: 1}))) { - return 12; - } - - let count = 0; - let key = me.findItems(sdk.items.Key, sdk.items.mode.inStorage, sdk.storage.Inventory); - - if (key) { - for (let i = 0; i < key.length; i += 1) { - count += key[i].getStat(sdk.stats.Quantity); - } - } - - return count; - }, - - needKeys: function () { - return this.checkKeys() <= 0; - }, - - wantKeys: function () { - return this.checkKeys() <= 6; - }, - - repairIngredientCheck: function (item) { - if (!Config.CubeRepair) return false; - - let needRal = 0; - let needOrt = 0; - let have = 0; - let items = this.getItemsForRepair(Config.RepairPercent, false); - - if (items && items.length) { - while (items.length > 0) { - switch (items.shift().itemType) { - case sdk.items.type.Shield: - case sdk.items.type.Armor: - case sdk.items.type.Boots: - case sdk.items.type.Gloves: - case sdk.items.type.Belt: - case sdk.items.type.VoodooHeads: - case sdk.items.type.AuricShields: - case sdk.items.type.PrimalHelm: - case sdk.items.type.Pelt: - case sdk.items.type.Circlet: - needRal += 1; - - break; - default: - needOrt += 1; - - break; - } - } - } - - switch (item.classid) { - case sdk.items.runes.Ral: - needRal && (have = me.findItems(sdk.items.runes.Ral).length); - - return (!have || have < needRal); - case sdk.items.runes.Ort: - needOrt && (have = me.findItems(sdk.items.runes.Ort).length); - - return (!have || have < needOrt); - } - - return false; - }, - - cubeRepair: function () { - if (!Config.CubeRepair || !me.cube) return false; - - let items = this.getItemsForRepair(Config.RepairPercent, false) - .sort((a, b) => a.durabilityPercent - b.durabilityPercent); - - while (items.length > 0) { - this.cubeRepairItem(items.shift()); - } - - return true; - }, - - cubeRepairItem: function (item) { - if (!item.isInStorage) return false; - - let rune, cubeItems; - let bodyLoc = item.bodylocation; - - switch (item.itemType) { - case sdk.items.type.Shield: - case sdk.items.type.Armor: - case sdk.items.type.Boots: - case sdk.items.type.Gloves: - case sdk.items.type.Belt: - case sdk.items.type.VoodooHeads: - case sdk.items.type.AuricShields: - case sdk.items.type.PrimalHelm: - case sdk.items.type.Pelt: - case sdk.items.type.Circlet: - rune = me.getItem(sdk.items.runes.Ral); - - break; - default: - rune = me.getItem(sdk.items.runes.Ort); - - break; - } - - if (rune && Town.openStash() && Cubing.openCube() && Cubing.emptyCube()) { - for (let i = 0; i < 100; i += 1) { - if (!me.itemoncursor) { - if (Storage.Cube.MoveTo(item) && Storage.Cube.MoveTo(rune)) { - transmute(); - delay(1000 + me.ping); - } - - cubeItems = me.findItems(-1, -1, sdk.storage.Cube); // Get cube contents - - // We expect only one item in cube - cubeItems.length === 1 && cubeItems[0].toCursor(); - } - - if (me.itemoncursor) { - for (let i = 0; i < 3; i += 1) { - clickItem(sdk.clicktypes.click.item.Left, bodyLoc); - delay(me.ping * 2 + 500); - - if (cubeItems[0].bodylocation === bodyLoc) { - print(cubeItems[0].fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "").trim() + " successfully repaired and equipped."); - D2Bot.printToConsole(cubeItems[0].fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "").trim() + " successfully repaired and equipped.", sdk.colors.D2Bot.Green); - - return true; - } - } - } - - delay(200); - } - - Misc.errorReport("Failed to put repaired item back on."); - D2Bot.stop(); - } - - return false; - }, - - repair: function (force = false) { - if (this.cubeRepair()) return true; - - let npc; - let repairAction = this.needRepair(); - force && repairAction.indexOf("repair") === -1 && repairAction.push("repair"); - - if (!repairAction || !repairAction.length) return true; - - for (let i = 0; i < repairAction.length; i += 1) { - switch (repairAction[i]) { - case "repair": - me.act === 3 && this.goToTown(Pather.accessToAct(4) ? 4 : 2); - npc = this.initNPC("Repair", "repair"); - if (!npc) return false; - me.repair(); - - break; - case "buyQuiver": - let bowCheck = Attack.usingBow(); - - if (bowCheck) { - let quiver = bowCheck === "bow" ? "aqv" : "cqv"; - let myQuiver = me.getItem(quiver, sdk.items.mode.Equipped); - !!myQuiver && myQuiver.drop(); - - npc = this.initNPC("Repair", "repair"); - if (!npc) return false; - - quiver = npc.getItem(quiver); - !!quiver && quiver.buy(); - } - - break; - } - } - - return true; - }, - - needRepair: function () { - let quiver, repairAction = []; - let canAfford = me.gold >= me.getRepairCost(); - - // Arrow/Bolt check - let bowCheck = Attack.usingBow(); - - if (bowCheck) { - switch (bowCheck) { - case "bow": - quiver = me.getItem("aqv", sdk.items.mode.Equipped); // Equipped arrow quiver - - break; - case "crossbow": - quiver = me.getItem("cqv", sdk.items.mode.Equipped); // Equipped bolt quiver - - break; - } - - if (!quiver) { // Out of arrows/bolts - repairAction.push("buyQuiver"); - } else { - let quantity = quiver.getStat(sdk.stats.Quantity); - - if (typeof quantity === "number" && quantity * 100 / getBaseStat("items", quiver.classid, "maxstack") <= Config.RepairPercent) { - repairAction.push("buyQuiver"); - } - } - } - - // Repair durability/quantity/charges - if (canAfford) { - if (this.getItemsForRepair(Config.RepairPercent, true).length > 0) { - repairAction.push("repair"); - } - } else { - console.warn("Can't afford repairs."); - } - - return repairAction; - }, - - getItemsForRepair: function (repairPercent, chargedItems) { - let itemList = []; - let item = me.getItem(-1, sdk.items.mode.Equipped); - - if (item) { - do { - // Skip ethereal items - if (!item.ethereal) { - // Skip indestructible items - if (!item.getStat(sdk.stats.Indestructible)) { - switch (item.itemType) { - // Quantity check - case sdk.items.type.ThrowingKnife: - case sdk.items.type.ThrowingAxe: - case sdk.items.type.Javelin: - case sdk.items.type.AmazonJavelin: - let quantity = item.getStat(sdk.stats.Quantity); - - // Stat 254 = increased stack size - if (typeof quantity === "number" && quantity * 100 / (getBaseStat("items", item.classid, "maxstack") + item.getStat(sdk.stats.ExtraStack)) <= repairPercent) { - itemList.push(copyUnit(item)); - } - - break; - // Durability check - default: - if (item.durabilityPercent <= repairPercent) { - itemList.push(copyUnit(item)); - } - - break; - } - } - - if (chargedItems) { - // Charged item check - let charge = item.getStat(-2)[sdk.stats.ChargedSkill]; - - if (typeof (charge) === "object") { - if (charge instanceof Array) { - for (let i = 0; i < charge.length; i += 1) { - if (charge[i] !== undefined && charge[i].hasOwnProperty("charges") && charge[i].charges * 100 / charge[i].maxcharges <= repairPercent) { - itemList.push(copyUnit(item)); - } - } - } else if (charge.charges * 100 / charge.maxcharges <= repairPercent) { - itemList.push(copyUnit(item)); - } - } - } - } - } while (item.getNext()); - } - - return itemList; - }, - - reviveMerc: function () { - if (!this.needMerc()) return true; - let preArea = me.area; - - // avoid Aheara - me.act === 3 && this.goToTown(Pather.accessToAct(4) ? 4 : 2); - - let npc = this.initNPC("Merc", "reviveMerc"); - if (!npc) return false; - - MainLoop: - for (let i = 0; i < 3; i += 1) { - let dialog = getDialogLines(); - - for (let lines = 0; lines < dialog.length; lines += 1) { - if (dialog[lines].text.match(":", "gi")) { - dialog[lines].handler(); - delay(Math.max(750, me.ping * 2)); - } - - // "You do not have enough gold for that." - if (dialog[lines].text.match(getLocaleString(sdk.locale.dialog.youDoNotHaveEnoughGoldForThat), "gi")) { - return false; - } - } - - let tick = getTickCount(); - - while (getTickCount() - tick < 2000) { - if (!!me.getMerc()) { - delay(Math.max(750, me.ping * 2)); - - break MainLoop; - } - - delay(200); - } - } - - Attack.checkInfinity(); - - if (!!me.getMerc()) { - // Cast BO on merc so he doesn't just die again. Only do this is you are a barb or actually have a cta. Otherwise its just a waste of time. - if (Config.MercWatch && Precast.needOutOfTownCast()) { - console.log("MercWatch precast"); - Precast.doRandomPrecast(true, preArea); - } - - return true; - } - - return false; - }, - - needMerc: function () { - if (me.classic || !Config.UseMerc || me.gold < me.mercrevivecost || me.mercrevivecost === 0) return false; - - Misc.poll(() => me.gameReady, 1000, 100); - // me.getMerc() might return null if called right after taking a portal, that's why there's retry attempts - for (let i = 0; i < 3; i += 1) { - let merc = me.getMerc(); - - if (!!merc && !merc.dead) { - return false; - } - - delay(100); - } - - // In case we never had a merc and Config.UseMerc is still set to true for some odd reason - return true; - }, - - /** - * @param {ItemUnit} item - */ - canStash: function (item) { - if (Town.ignoreType(item.itemType) - || [sdk.items.quest.HoradricStaff, sdk.items.quest.KhalimsWill].includes(item.classid) - || !Town.canStashGem(item)) { - return false; - } - /** - * @todo add sorting here first if we can't fit the item - */ - return Storage.Stash.CanFit(item); - }, - - /** - * get ordered list of gems in inventory to use with Gem Hunter Script - * @returns {ItemUnit[]} ordered list of relevant gems - */ - getGemsInInv: function () { - let GemList = Config.GemHunter.GemList; - return me.getItemsEx() - .filter((p) => p.isInInventory && GemList.includes(p.classid)) - .sort((a, b) => GemList.indexOf(a.classid) - GemList.indexOf(b.classid)); - }, - - /** - * get ordered list of gems in stash to use with Gem Hunter Script - * @returns {ItemUnit[]} ordered list of relevant gems - */ - getGemsInStash: function () { - let GemList = Config.GemHunter.GemList; - return me.getItemsEx() - .filter((p) => p.isInStash && GemList.includes(p.classid)) - .sort((a, b) => GemList.indexOf(a.classid) - GemList.indexOf(b.classid)); - }, - - /** - * gem check for use with Gem Hunter Script - * @param {ItemUnit} item - * @returns {boolean} if we should stash this gem - */ - canStashGem: function (item) { - // we aren't using the gem hunter script or we aren't scanning for the gem shrines while moving - // for now we are only going to keep a gem in our invo while the script is active - if (Loader.scriptName() !== "GemHunter") return true; - // not in our list - if (Config.GemHunter.GemList.indexOf(item.classid) === -1) return true; - - let GemList = Config.GemHunter.GemList; - let gemsInStash = Town.getGemsInStash(); - let bestGeminStash = gemsInStash.length > 0 ? gemsInStash.first().classid : -1; - let gemsInInvo = Town.getGemsInInv(); - let bestGeminInv = gemsInInvo.length > 0 ? gemsInInvo.first().classid : -1; - - return ( - (GemList.indexOf(bestGeminStash) < GemList.indexOf(bestGeminInv)) // better one in stash - || (GemList.indexOf(bestGeminInv) < GemList.indexOf(item.classid)) // better one in inv - || (gemsInInvo.filter((p) => p.classid === item.classid).length > 1)); // another one in inv - }, - - /** - * move best gem from stash to inventory, if none in inventrory - * to use with Gem Hunter Script - * @returns {boolean} if any gem has been moved - */ - getGem: function () { - if (Loader.scriptName() === "GemHunter") { - if (this.getGemsInInv().length === 0 && this.getGemsInStash().length > 0) { - let gem = this.getGemsInStash().first(); - Storage.Inventory.MoveTo(gem) && Misc.itemLogger("Inventoried", gem); - return true; - } - } - return false; - }, - - stash: function (stashGold = true) { - if (!this.needStash()) return true; - - me.cancelUIFlags(); - - let items = Storage.Inventory.Compare(Config.Inventory); - - if (items) { - for (let i = 0; i < items.length; i += 1) { - if (this.canStash(items[i])) { - let result = false; - let pickResult = Pickit.checkItem(items[i]).result; - - switch (true) { - case pickResult > Pickit.Result.UNWANTED && pickResult < Pickit.Result.TRASH: - case Cubing.keepItem(items[i]): - case Runewords.keepItem(items[i]): - case CraftingSystem.keepItem(items[i]): - result = true; - - break; - default: - break; - } - - if (result) { - Storage.Stash.MoveTo(items[i]) && Misc.itemLogger("Stashed", items[i]); - } - } - } - } - - // Stash gold - if (stashGold) { - if (me.getStat(sdk.stats.Gold) >= Config.StashGold && me.getStat(sdk.stats.GoldBank) < 25e5 && this.openStash()) { - gold(me.getStat(sdk.stats.Gold), 3); - delay(1000); // allow UI to initialize - me.cancel(); - } - } - - return true; - }, - - needStash: function () { - if (Config.StashGold && me.getStat(sdk.stats.Gold) >= Config.StashGold && me.getStat(sdk.stats.GoldBank) < 25e5) { - return true; - } - - let items = Storage.Inventory.Compare(Config.Inventory); - - for (let i = 0; i < items.length; i += 1) { - if (Storage.Stash.CanFit(items[i])) { - return true; - } - } - - return false; - }, - - openStash: function () { - if (getUIFlag(sdk.uiflags.Cube) && !Cubing.closeCube()) return false; - if (getUIFlag(sdk.uiflags.Stash)) return true; - - for (let i = 0; i < 5; i += 1) { - me.cancel(); - - if (this.move("stash")) { - let stash = Game.getObject(sdk.objects.Stash); - - if (stash) { - let pingDelay = me.getPingDelay(); - - if (Skill.useTK(stash)) { - // Fix for out of range telek - i > 0 && stash.distance > (23 - (i * 2)) && Pather.walkTo(stash.x, stash.y, (23 - (i * 2))); - Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, stash); - } else { - Misc.click(0, 0, stash); - } - - let tick = getTickCount(); - - while (getTickCount() - tick < 5000) { - if (getUIFlag(sdk.uiflags.Stash)) { - // allow UI to initialize - delay(100 + pingDelay * 2); - - return true; - } - - delay(100); - } - } - } - - Packet.flash(me.gid); - } - - return false; - }, - - getCorpse: function () { - let corpse, corpseList = []; - let timer = getTickCount(); - - // No equipped items - high chance of dying in last game, force retries - if (!me.getItem(-1, sdk.items.mode.Equipped)) { - corpse = Misc.poll(() => Game.getPlayer(me.name, sdk.player.mode.Dead), 2500, 500); - } else { - corpse = Game.getPlayer(me.name, sdk.player.mode.Dead); - } - - if (!corpse) return true; - - do { - if (corpse.dead && corpse.name === me.name && (getDistance(me.x, me.y, corpse.x, corpse.y) <= 20 || me.inTown)) { - corpseList.push(copyUnit(corpse)); - } - } while (corpse.getNext()); - - while (corpseList.length > 0) { - if (me.dead) return false; - - let gid = corpseList[0].gid; - - Pather.moveToUnit(corpseList[0]); - Misc.click(0, 0, corpseList[0]); - delay(500); - - if (getTickCount() - timer > 3000) { - let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 4); - !!coord && Pather.moveTo(coord.x, coord.y); - } - - if (getTickCount() - timer > 30000) { - D2Bot.printToConsole("Failed to get corpse, stopping.", sdk.colors.D2Bot.Red); - D2Bot.stop(); - } - - !Game.getPlayer(-1, -1, gid) && corpseList.shift(); - } - - me.classic && this.checkShard(); - - return true; - }, - - checkShard: function () { - let shard; - let check = {left: false, right: false}; - let item = me.getItem("bld", sdk.items.mode.inStorage); - - if (item) { - do { - if (item.isInInventory && item.unique) { - shard = copyUnit(item); - - break; - } - } while (item.getNext()); - } - - if (!shard) return true; - - item = me.getItem(-1, sdk.items.mode.Equipped); - - if (item) { - do { - item.bodylocation === sdk.body.RightArm && (check.right = true); - item.bodylocation === sdk.body.LeftArm && (check.left = true); - } while (item.getNext()); - } - - if (!check.right) { - shard.toCursor(); - - while (me.itemoncursor) { - clickItem(sdk.clicktypes.click.item.Left, sdk.body.RightArm); - delay(500); - } - } else if (!check.left) { - shard.toCursor(); - - while (me.itemoncursor) { - clickItem(sdk.clicktypes.click.item.Left, sdk.body.LeftArm); - delay(500); - } - } - - return true; - }, - - clearBelt: function () { - let item = me.getItem(-1, sdk.items.mode.inBelt); - let clearList = []; - - if (item) { - do { - switch (item.itemType) { - case sdk.items.type.HealingPotion: - if (Config.BeltColumn[item.x % 4] !== "hp") { - clearList.push(copyUnit(item)); - } - - break; - case sdk.items.type.ManaPotion: - if (Config.BeltColumn[item.x % 4] !== "mp") { - clearList.push(copyUnit(item)); - } - - break; - case sdk.items.type.RejuvPotion: - if (Config.BeltColumn[item.x % 4] !== "rv") { - clearList.push(copyUnit(item)); - } - - break; - case sdk.items.type.StaminaPotion: - case sdk.items.type.AntidotePotion: - case sdk.items.type.ThawingPotion: - clearList.push(copyUnit(item)); - } - } while (item.getNext()); - - while (clearList.length > 0) { - clearList.shift().interact(); - delay(200); - } - } - - return true; - }, - - clearScrolls: function () { - let scrolls = me.getItemsEx().filter((scroll) => scroll.isInInventory && scroll.itemType === sdk.items.type.Scroll); - let tpTome = scrolls.some(function (scroll) { - return scroll.classid === sdk.items.ScrollofTownPortal; - }) ? me.findItem(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory) : false; - let idTome = scrolls.some(function (scroll) { - return scroll.classid === sdk.items.ScrollofIdentify; - }) ? me.findItem(sdk.items.TomeofIdentify, sdk.items.mode.inStorage, sdk.storage.Inventory) : false; - let currQuantity; - - for (let i = 0; !!scrolls && i < scrolls.length; i++) { - switch (scrolls[i].classid) { - case sdk.items.ScrollofTownPortal: - if (tpTome && tpTome.getStat(sdk.stats.Quantity) < 20) { - currQuantity = tpTome.getStat(sdk.stats.Quantity); - if (scrolls[i].toCursor()) { - clickItemAndWait(sdk.clicktypes.click.item.Left, tpTome.x, tpTome.y, tpTome.location); - - if (tpTome.getStat(sdk.stats.Quantity) > currQuantity) { - console.info(null, "Placed scroll in tp tome"); - - continue; - } - } - } - - break; - case sdk.items.ScrollofIdentify: - if (idTome && idTome.getStat(sdk.stats.Quantity) < 20) { - currQuantity = idTome.getStat(sdk.stats.Quantity); - if (scrolls[i].toCursor()) { - clickItemAndWait(sdk.clicktypes.click.item.Left, idTome.x, idTome.y, idTome.location); - - if (idTome.getStat(sdk.stats.Quantity) > currQuantity) { - console.info(null, "Placed scroll in id tome"); - - continue; - } - } - } - - if (Config.FieldID.Enabled && !idTome) { - // don't toss scrolls if we need them for field id but don't have a tome yet - low level chars - continue; - } - - break; - } - - // Might as well sell the item if already in shop - if (getUIFlag(sdk.uiflags.Shop) || (Config.PacketShopping && getInteractedNPC() && getInteractedNPC().itemcount > 0)) { - console.info(null, "Sell " + scrolls[i].name); - Misc.itemLogger("Sold", scrolls[i]); - scrolls[i].sell(); - } else { - Misc.itemLogger("Dropped", scrolls[i], "clearScrolls"); - scrolls[i].drop(); - } - } - - return true; - }, - - clearInventory: function () { - console.info(true); - console.time("clearInventory"); - this.checkQuestItems(); // only golden bird quest for now - - // If we are at an npc already, open the window otherwise moving potions around fails - if (getUIFlag(sdk.uiflags.NPCMenu) && !getUIFlag(sdk.uiflags.Shop)) { - try { - !!getInteractedNPC() && Misc.useMenu(sdk.menu.Trade); - } catch (e) { - console.error(e); - me.cancelUIFlags(); - } - } - - // Remove potions in the wrong slot of our belt - this.clearBelt(); - - // Return potions from inventory to belt - let beltSize = Storage.BeltSize(); - // belt 4x4 locations - /** - * 12 13 14 15 - * 8 9 10 11 - * 4 5 6 7 - * 0 1 2 3 - */ - let beltMax = (beltSize * 4); - let beltCapRef = [(0 + beltMax), (1 + beltMax), (2 + beltMax), (3 + beltMax)]; - let potsInInventory = me.getItemsEx() - .filter((p) => p.isInInventory && [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion].includes(p.itemType)) - .sort((a, b) => a.itemType - b.itemType); - - Config.DebugMode && potsInInventory.length > 0 && console.debug("clearInventory: start pots clean-up"); - // Start interating over all the pots we have in our inventory - potsInInventory.forEach(function (p) { - let moved = false; - // get free space in each slot of our belt - let freeSpace = Town.checkColumns(beltSize); - for (let i = 0; i < 4 && !moved; i++) { - // checking that current potion matches what we want in our belt - if (freeSpace[i] > 0 && p.code && p.code.startsWith(Config.BeltColumn[i])) { - // Pick up the potion and put it in belt if the column is empty, and we don't have any other columns empty - // prevents shift-clicking potion into wrong column - if (freeSpace[i] === beltSize || freeSpace.some((spot) => spot === beltSize)) { - let x = freeSpace[i] === beltSize ? i : (beltCapRef[i] - (freeSpace[i] * 4)); - Packet.placeInBelt(p, x); - } else { - clickItemAndWait(sdk.clicktypes.click.item.ShiftLeft, p.x, p.y, p.location); - } - Misc.poll(() => !me.itemoncursor, 300, 30); - moved = Town.checkColumns(beltSize)[i] === freeSpace[i] - 1; - } - Cubing.cursorCheck(); - } - }); - - // Cleanup remaining potions - Config.DebugMode && console.debug("clearInventory: start clean-up remaining pots"); - let sellOrDrop = []; - potsInInventory = me.getItemsEx() - .filter((p) => p.isInInventory && [ - sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion, - sdk.items.type.ThawingPotion, sdk.items.type.AntidotePotion, sdk.items.type.StaminaPotion - ].includes(p.itemType)); - - if (potsInInventory.length > 0) { - let hp = [], mp = [], rv = [], specials = []; - potsInInventory.forEach(function (p) { - if (!p || p === undefined) return false; - - switch (p.itemType) { - case sdk.items.type.HealingPotion: - return (hp.push(copyUnit(p))); - case sdk.items.type.ManaPotion: - return (mp.push(copyUnit(p))); - case sdk.items.type.RejuvPotion: - return (rv.push(copyUnit(p))); - case sdk.items.type.ThawingPotion: - case sdk.items.type.AntidotePotion: - case sdk.items.type.StaminaPotion: - return (specials.push(copyUnit(p))); - } - - return false; - }); - - // Cleanup healing potions - while (hp.length > Config.HPBuffer) { - sellOrDrop.push(hp.shift()); - } - - // Cleanup mana potions - while (mp.length > Config.MPBuffer) { - sellOrDrop.push(mp.shift()); - } - - // Cleanup rejuv potions - while (rv.length > Config.RejuvBuffer) { - sellOrDrop.push(rv.shift()); - } - - // Clean up special pots - while (specials.length) { - specials.shift().interact(); - delay(200); - } - } - - // Any leftover items from a failed ID (crashed game, disconnect etc.) - Config.DebugMode && console.debug("clearInventory: start invo clean-up"); - let ignoreTypes = [ - sdk.items.type.Book, sdk.items.type.Key, sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion - ]; - let items = (Storage.Inventory.Compare(Config.Inventory) || []) - .filter(function (item) { - if (!item) return false; - // Don't drop tomes, keys or potions or quest-items - // Don't throw cubing/runeword/crafting ingredients - if (ignoreTypes.indexOf(item.itemType) === -1 && item.sellable && !Cubing.keepItem(item) && !Runewords.keepItem(item) && !CraftingSystem.keepItem(item)) { - return true; - } - return false; - }); - - // add leftovers from potion cleanup - items = (items.length > 0 ? items.concat(sellOrDrop) : sellOrDrop.slice(0)); - - if (items.length > 0) { - let sell = [], drop = []; - // lets see if we have any items to sell - items.forEach(function (item) { - let result = Pickit.checkItem(item).result; - switch (result) { - case Pickit.Result.UNWANTED: - return drop.push(item); - case Pickit.Result.TRASH: - return sell.push(item); - } - return false; - }); - // we have items to sell, might as well sell the dropable items as well - if (sell.length) { - Config.DebugMode && console.debug("PreSellLen: " + sell.length + " PreDropLen: " + drop.length); - drop.filter(function (item) { - // add to sellable array - if (item.sellable && sell.push(item)) return false; - return true; - }); - Config.DebugMode && console.debug("AfterSellLen: " + sell.length + " AfterDropLen: " + drop.length); - // should there be multiple attempts to interact with npc or if we fail should we move everything from the sell list to the drop list? - Town.initNPC("Shop", "clearInventory"); - // now lets sell them items - sell.forEach(function (item) { - let sold = false; // so we know to delay or not - try { - console.info(null, "Sell :: " + item.name); - Misc.itemLogger("Sold", item); - item.sell() && (sold = true); - } catch (e) { - console.error(e); - } - sold && delay(250); // would a rand delay be better? - }); - // now lets see if we need to drop anything, so lets exit the shop - me.cancelUIFlags(); - } - - if (drop.length) { - drop.forEach(function (item) { - let drop = false; // so we know to delay or not - try { - console.info(null, "Drop :: " + item.name); - Misc.itemLogger("Dropped", item, "clearInventory"); - item.drop() && (drop = true); - } catch (e) { - console.error(e); - } - drop && delay(50); - }); - } - } - - console.info(false, null, "clearInventory"); - - return true; - }, - - act: [{}, {}, {}, {}, {}], - - initialize: function () { - //print("Initialize town " + me.act); - - switch (me.act) { - case 1: - let wp = Game.getPresetObject(sdk.areas.RogueEncampment, sdk.objects.A1Waypoint); - let fireUnit = Game.getPresetObject(sdk.areas.RogueEncampment, sdk.objects.A1TownFire); - if (!fireUnit) return false; - - let fire = [fireUnit.roomx * 5 + fireUnit.x, fireUnit.roomy * 5 + fireUnit.y]; - - this.act[0].spot = {}; - this.act[0].spot.stash = [fire[0] - 7, fire[1] - 12]; - this.act[0].spot[NPC.Warriv] = [fire[0] - 5, fire[1] - 2]; - this.act[0].spot[NPC.Cain] = [fire[0] + 6, fire[1] - 5]; - this.act[0].spot[NPC.Kashya] = [fire[0] + 14, fire[1] - 4]; - this.act[0].spot[NPC.Akara] = [fire[0] + 56, fire[1] - 30]; - this.act[0].spot[NPC.Charsi] = [fire[0] - 39, fire[1] - 25]; - this.act[0].spot[NPC.Gheed] = [fire[0] - 34, fire[1] + 36]; - this.act[0].spot.portalspot = [fire[0] + 10, fire[1] + 18]; - this.act[0].spot.waypoint = [wp.roomx * 5 + wp.x, wp.roomy * 5 + wp.y]; - this.act[0].initialized = true; - - break; - case 2: - this.act[1].spot = {}; - this.act[1].spot[NPC.Fara] = [5124, 5082]; - this.act[1].spot[NPC.Cain] = [5124, 5082]; - this.act[1].spot[NPC.Lysander] = [5118, 5104]; - this.act[1].spot[NPC.Greiz] = [5033, 5053]; - this.act[1].spot[NPC.Elzix] = [5032, 5102]; - this.act[1].spot.palace = [5088, 5153]; - this.act[1].spot.sewers = [5221, 5181]; - this.act[1].spot[NPC.Meshif] = [5205, 5058]; - this.act[1].spot[NPC.Drognan] = [5097, 5035]; - this.act[1].spot[NPC.Atma] = [5137, 5060]; - this.act[1].spot[NPC.Warriv] = [5152, 5201]; - this.act[1].spot.portalspot = [5168, 5060]; - this.act[1].spot.stash = [5124, 5076]; - this.act[1].spot.waypoint = [5070, 5083]; - this.act[1].initialized = true; - - break; - case 3: - this.act[2].spot = {}; - this.act[2].spot[NPC.Meshif] = [5118, 5168]; - this.act[2].spot[NPC.Hratli] = [5223, 5048, 5127, 5172]; - this.act[2].spot[NPC.Ormus] = [5129, 5093]; - this.act[2].spot[NPC.Asheara] = [5043, 5093]; - this.act[2].spot[NPC.Alkor] = [5083, 5016]; - this.act[2].spot[NPC.Cain] = [5148, 5066]; - this.act[2].spot.stash = [5144, 5059]; - this.act[2].spot.portalspot = [5150, 5063]; - this.act[2].spot.waypoint = [5158, 5050]; - this.act[2].initialized = true; - - break; - case 4: - this.act[3].spot = {}; - this.act[3].spot[NPC.Cain] = [5027, 5027]; - this.act[3].spot[NPC.Halbu] = [5089, 5031]; - this.act[3].spot[NPC.Tyrael] = [5027, 5027]; - this.act[3].spot[NPC.Jamella] = [5088, 5054]; - this.act[3].spot.stash = [5022, 5040]; - this.act[3].spot.portalspot = [5045, 5042]; - this.act[3].spot.waypoint = [5043, 5018]; - this.act[3].initialized = true; - - break; - case 5: - this.act[4].spot = {}; - this.act[4].spot.portalspot = [5098, 5019]; - this.act[4].spot.stash = [5129, 5061]; - this.act[4].spot[NPC.Larzuk] = [5141, 5045]; - this.act[4].spot[NPC.Malah] = [5078, 5029]; - this.act[4].spot[NPC.Cain] = [5119, 5061]; - this.act[4].spot[NPC.Qual_Kehk] = [5066, 5083]; - this.act[4].spot[NPC.Anya] = [5112, 5120]; - this.act[4].spot.portal = [5118, 5120]; - this.act[4].spot.waypoint = [5113, 5068]; - this.act[4].spot[NPC.Nihlathak] = [5071, 5111]; - this.act[4].initialized = true; - - break; - } - - return true; - }, - - getDistance: function (spot = "") { - !me.inTown && this.goToTown(); - !this.act[me.act - 1].initialized && this.initialize(); - - // Act 5 wp->portalspot override - ActMap.cpp crash - if (me.act === 5 && spot === "portalspot" && getDistance(me.x, me.y, 5113, 5068) <= 8) { - return [5098, 5018].distance; - } - - if (typeof (this.act[me.act - 1].spot[spot]) === "object") { - return this.act[me.act - 1].spot[spot].distance; - } else { - return Infinity; - } - }, - - move: function (spot = "", allowTK = true) { - !me.inTown && this.goToTown(); - !this.act[me.act - 1].initialized && this.initialize(); - - // act 5 static paths, ActMap.cpp seems to have issues with A5 - // should other towns have static paths? - if (me.act === 5) { - let path = []; - let returnWhenDone = false; - - // Act 5 wp->portalspot override - ActMap.cpp crash - if (spot === "portalspot" && getDistance(me.x, me.y, 5113, 5068) <= 8) { - path = [5113, 5068, 5108, 5051, 5106, 5046, 5104, 5041, 5102, 5027, 5098, 5018]; - returnWhenDone = true; - } - - if (["stash", "waypoint"].includes(spot)) { - // malah -> stash/wp - if (getDistance(me.x, me.y, 5081, 5031) <= 10) { - path = [5089, 5029, 5093, 5021, 5101, 5027, 5107, 5043, 5108, 5052]; - } else if (getDistance(me.x, me.y, 5099, 5020) <= 13) { - // portalspot -> stash/wp - path = [5102, 5031, 5107, 5042, 5108, 5052]; - } - } - - if (path.length) { - for (let i = 0; i < path.length; i += 2) { - Pather.walkTo(path[i], path[i + 1]); - } - - if (returnWhenDone) return true; - } - } - - for (let i = 0; i < 3; i += 1) { - i === 2 && (allowTK = false); - if (this.moveToSpot(spot, allowTK)) { - return true; - } - - Packet.flash(me.gid); - } - - return false; - }, - - moveToSpot: function (spot = "", allowTK = true) { - let townSpot; - let longRange = (!Skill.haveTK && spot === "waypoint"); - let tkRange = (Skill.haveTK && allowTK && ["stash", "portalspot", "waypoint"].includes(spot)); - - if (!this.act[me.act - 1].hasOwnProperty("spot") || !this.act[me.act - 1].spot.hasOwnProperty(spot)) { - return false; - } - - if (typeof (this.act[me.act - 1].spot[spot]) === "object") { - townSpot = this.act[me.act - 1].spot[spot]; - } else { - return false; - } - - if (longRange) { - let path = getPath(me.area, townSpot[0], townSpot[1], me.x, me.y, 1, 8); - - if (path && path[1]) { - townSpot = [path[1].x, path[1].y]; - } - } - - for (let i = 0; i < townSpot.length; i += 2) { - //console.debug("moveToSpot: " + spot + " from " + me.x + ", " + me.y); - - if (tkRange) { - Pather.moveNear(townSpot[0], townSpot[1], 19); - } else if (getDistance(me, townSpot[i], townSpot[i + 1]) > 2) { - Pather.moveTo(townSpot[i], townSpot[i + 1], 3, false, true); - } - - switch (spot) { - case "stash": - if (!!Game.getObject(sdk.objects.Stash)) { - return true; - } - - break; - case "palace": - if (!!Game.getNPC(NPC.Jerhyn)) { - return true; - } - - break; - case "portalspot": - case "sewers": - if (tkRange && spot === "portalspot" && getDistance(me, townSpot[0], townSpot[1]) < 21) { - return true; - } - - if (getDistance(me, townSpot[i], townSpot[i + 1]) < 10) { - return true; - } - - break; - case "waypoint": - let wp = Game.getObject("waypoint"); - if (!!wp) { - !Skill.haveTK && wp.distance > 5 && Pather.moveToUnit(wp); - return true; - } - - break; - default: - if (!!Game.getNPC(spot)) { - return true; - } - - break; - } - } - - return false; - }, - - goToTown: function (act = 0, wpmenu = false) { - if (!me.inTown) { - try { - if (!Pather.makePortal(true)) { - console.warn("Town.goToTown: Failed to make TP"); - } - if (!me.inTown && !Pather.usePortal(null, me.name)) { - console.warn("Town.goToTown: Failed to take TP"); - if (!me.inTown && !Pather.usePortal(sdk.areas.townOf(me.area))) throw new Error("Town.goToTown: Failed to take TP"); - } - } catch (e) { - let tpTool = Town.getTpTool(); - if (!tpTool && Misc.getPlayerCount() <= 1) { - Misc.errorReport(new Error("Town.goToTown: Failed to go to town and no tps available. Restart.")); - scriptBroadcast("quit"); - } else { - if (!Misc.poll(() => { - if (me.inTown) return true; - let p = Game.getObject("portal"); - console.debug(p); - !!p && Misc.click(0, 0, p) && delay(100); - Misc.poll(() => me.idle, 1000, 100); - console.debug("inTown? " + me.inTown); - return me.inTown; - }, 700, 100)) { - Misc.errorReport(new Error("Town.goToTown: Failed to go to town. Quiting.")); - scriptBroadcast("quit"); - } - } - } - } - - if (!act) return true; - if (act < 1 || act > 5) throw new Error("Town.goToTown: Invalid act"); - if (act > me.highestAct) return false; - - if (act !== me.act) { - try { - Pather.useWaypoint(sdk.areas.townOfAct(act), wpmenu); - } catch (WPError) { - throw new Error("Town.goToTown: Failed use WP"); - } - } - - return true; - }, - - visitTown: function (repair = false) { - console.info(true); - - if (me.inTown) { - this.doChores(); - this.move("stash"); - - return true; - } - - if (!this.canTpToTown()) return false; - - let preArea = me.area; - let preAct = me.act; - - // not an essential function -> handle thrown errors - try { - this.goToTown(); - } catch (e) { - return false; - } - - this.doChores(repair); - - me.act !== preAct && this.goToTown(preAct); - this.move("portalspot"); - - if (!Pather.usePortal(null, me.name)) { - try { - Pather.usePortal(preArea, me.name); - } catch (e) { - throw new Error("Town.visitTown: Failed to go back from town"); - } - } - - Config.PublicMode && Pather.makePortal(); - console.info(false, "CurrentArea: " + Pather.getAreaName(me.area)); - - return true; - } -}; diff --git a/d2bs/kolbot/libs/common/Util.js b/d2bs/kolbot/libs/common/Util.js deleted file mode 100644 index 68556c5cd..000000000 --- a/d2bs/kolbot/libs/common/Util.js +++ /dev/null @@ -1,231 +0,0 @@ -/** -* @filename Util.js -* @author Jaenster, theBGuy -* @desc utility functions for kolbot -* -*/ - -!isIncluded("Polyfill.js") && include("Polyfill.js"); -// torn on if these include functions should be here or in polyfill - not exactly polyfill functions but sorta? -const includeIfNotIncluded = function (file = "") { - if (!isIncluded(file)) { - if (!include(file)) { - console.error("Failed to include " + file); - console.trace(); - } - } - return true; -}; - -const includeCommonLibs = function () { - let files = dopen("libs/common/").getFiles(); - if (!files.length) throw new Error("Failed to find my files"); - if (!files.includes("Pather.js")) { - console.warn("Incorrect Files?", files); - // something went wrong? - while (!files.includes("Pather.js")) { - files = dopen("libs/common/").getFiles(); - delay(50); - } - } - - files.filter(file => file.endsWith(".js") && !file.match("auto", "gi") && !file.match("util.js", "gi")) - .forEach(function (x) { - if (!includeIfNotIncluded("common/" + x)) { - throw new Error("Failed to include common/" + x); - } - }); -}; - -const includeOOGLibs = function () { - let files = dopen("libs/oog/").getFiles(); - if (!files.length) throw new Error("Failed to find my files"); - if (!files.includes("DataFile.js")) { - console.warn("Incorrect Files?", files); - // something went wrong? - while (!files.includes("DataFile.js")) { - files = dopen("libs/oog/").getFiles(); - delay(50); - } - } - - files.filter(file => file.endsWith(".js")) - .forEach(function (x) { - if (!includeIfNotIncluded("oog/" + x)) { - throw new Error("Failed to include oog/" + x); - } - }); -}; - -/** - * @param args - * @returns Unit[] - */ -const getUnits = function (...args) { - let units = [], unit = getUnit.apply(null, args); - - if (!unit) { - return []; - } - do { - units.push(copyUnit(unit)); - } while (unit.getNext()); - return units; -}; - -const clickItemAndWait = function (...args) { - let timeout = getTickCount(), timedOut; - let before = !me.itemoncursor; - - clickItem.apply(undefined, args); - delay(Math.max(me.ping * 2, 250)); - - - while (true) { // Wait until item is picked up. - delay(3); - - if (before !== !!me.itemoncursor || (timedOut = getTickCount() - timeout > Math.min(1000, 100 + (me.ping * 4)))) { - break; // quit the loop of item on cursor has changed - } - } - - delay(Math.max(me.ping, 50)); - - // return item if we didnt timeout - return !timedOut; -}; - -/** - * @description clickMap doesn't return if we sucessfully clicked a unit just that a click was sent, this checks and returns that a units mode has changed - * as a result of us clicking it. - * @returns boolean - */ -const clickUnitAndWait = function (button, shift, unit) { - if (typeof (unit) !== "object") throw new Error("clickUnitAndWait: Third arg must be a Unit."); - - let before = unit.mode; - - me.blockMouse = true; - clickMap(button, shift, unit); - delay(Math.max(me.ping * 2, 250)); - clickMap(button + 2, shift, unit); - me.blockMouse = false; - - let waitTick = getTickCount(); - let timeOut = Math.min(1000, 100 + (me.ping * 4)); - - while (getTickCount() - waitTick < timeOut) { - delay(30); - - // quit the loop if mode has changed - if (before !== unit.mode) { - break; - } - } - - delay(Math.max(me.ping + 1, 50)); - - return (before !== unit.mode); -}; - -// helper functions in case you find it annoying like me to write while (getTickCount() - tick > 3 * 60 * 1000) which is 3 minutes -// instead we can do while (getTickCount() - tick > Time.minutes(5)) -const Time = { - seconds: function (seconds = 0) { - if (typeof seconds !== "number") return 0; - return (seconds * 1000); - }, - minutes: function (minutes = 0) { - if (typeof minutes !== "number") return 0; - return (minutes * 60000); - }, - format: function (ms = 0) { - return (new Date(ms).toISOString().slice(11, -5)); - } -}; - -const Game = { - getDistance: function (...args) { - switch (args.length) { - case 0: - return Infinity; - case 1: - // getDistance(unit) - returns distance that unit is from me - if (typeof args[0] !== "object") return Infinity; - if (!args[0].hasOwnProperty("x")) return Infinity; - return Math.sqrt(Math.pow((me.x - args[0].x), 2) + Math.pow((me.y - args[0].y), 2)); - case 2: - // getDistance(x, y) - returns distance x, y is from me - // getDistance(unitA, unitB) - returns distace unitA is from unitB - if (typeof args[0] === "number" && typeof args[1] === "number") { - return Math.sqrt(Math.pow((me.x - args[0]), 2) + Math.pow((me.y - args[1]), 2)); - } else if (typeof args[0] === "object" && typeof args[1] === "object") { - if (!args[1].hasOwnProperty("x")) return Infinity; - return Math.sqrt(Math.pow((args[0].x - args[1].x), 2) + Math.pow((args[0].y - args[1].y), 2)); - } - return Infinity; - case 3: - // getDistance(unit, x, y) - returns distance x, y is from unit - if (typeof args[2] !== "number") return Infinity; - if (!args[0].hasOwnProperty("x")) return Infinity; - return Math.sqrt(Math.pow((args[0].x - args[1]), 2) + Math.pow((args[0].y - args[2]), 2)); - case 4: - // getDistance(x1, y1, x2, y2) - if (typeof args[0] !== "number" || typeof args[3] !== "number") return Infinity; - return Math.sqrt(Math.pow((args[0] - args[2]), 2) + Math.pow((args[1] - args[3]), 2)); - default: - return Infinity; - } - }, - getCursorUnit: function () { - return getUnit(100); - }, - getSelectedUnit: function () { - return getUnit(101); - }, - getPlayer: function (id, mode, gid) { - return getUnit(sdk.unittype.Player, id, mode, gid); - }, - getMonster: function (id, mode, gid) { - return getUnit(sdk.unittype.Monster, id, mode, gid); - }, - getNPC: function (id, mode, gid) { - return getUnit(sdk.unittype.NPC, id, mode, gid); - }, - getObject: function (id, mode, gid) { - return getUnit(sdk.unittype.Object, id, mode, gid); - }, - getMissile: function (id, mode, gid) { - return getUnit(sdk.unittype.Missile, id, mode, gid); - }, - getItem: function (id, mode, gid) { - return getUnit(sdk.unittype.Item, id, mode, gid); - }, - getStairs: function (id, mode, gid) { - return getUnit(sdk.unittype.Stairs, id, mode, gid); - }, - getPresetMonster: function (area, id) { - !area && (area = me.area); - return getPresetUnit(area, sdk.unittype.Monster, id); - }, - getPresetMonsters: function (area, id) { - !area && (area = me.area); - return getPresetUnits(area, sdk.unittype.Monster, id); - }, - getPresetObject: function (area, id) { - !area && (area = me.area); - return getPresetUnit(area, sdk.unittype.Object, id); - }, - getPresetObjects: function (area, id) { - !area && (area = me.area); - return getPresetUnits(area, sdk.unittype.Object, id); - }, - getPresetStair: function (area, id) { - !area && (area = me.area); - return getPresetUnit(area, sdk.unittype.Stairs, id); - }, - getPresetStairs: function (area, id) { - !area && (area = me.area); - return getPresetUnits(area, sdk.unittype.Stairs, id); - }, -}; diff --git a/d2bs/kolbot/libs/config/Amazon.js b/d2bs/kolbot/libs/config/Amazon.js index ec22a34a7..ec7cce355 100644 --- a/d2bs/kolbot/libs/config/Amazon.js +++ b/d2bs/kolbot/libs/config/Amazon.js @@ -15,720 +15,787 @@ * Javascript statements need to end with a semi-colon; Good: Scripts.Corpsefire = false; Bad: Scripts.Corpsefire = false */ -function LoadConfig() { - /* Sequence config - * Set to true if you want to run it, set to false if not. - * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. - */ - - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - // ## Team MF - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal - Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed - Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto - Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - /* - * Unless stated otherwise, leader's character name isn't needed on order to run. - * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - */ - - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - - // ##### MFHelper ##### // - // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 - // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited - Scripts.MFHelper = false; - - // ###################### // - /* ##### Pure Leech ##### */ - // ###################### // - - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - - // ########################## // - /* ##### Helper SCRIPTS ##### */ - // ########################## // - - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 35; - * GOOD: Config.AttackSkill[1] = sdk.skills.LightningFury; - * BAD: Config.AttackSkill[1] = -35; - * BAD: Config.AttackSkill[1] = "LightningFury"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill. - Config.UseInnerSight = true; // Use inner sight as a precast - Config.UseSlowMissiles = true; // Use slow missiles as a precast - Config.UseDecoy = true; // Use decoy with merc stomp - Config.SummonValkyrie = true; // Summon Valkyrie - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // Make Perfect Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // Make Perfect Topaz - //Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // Make Perfect Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // Make Perfect Emerald - //Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // Make Perfect Ruby - //Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // Make Perfect Diamond - //Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // Make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - //Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - //Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - //Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling - - //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); - - //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; +function LoadConfig () { + /* Sequence config + * Set to true if you want to run it, set to false if not. + * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. + */ + + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal + Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed + Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto + Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + /* + * Unless stated otherwise, leader's character name isn't needed on order to run. + * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + */ + + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + + // ##### MFHelper ##### // + // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 + // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited + Scripts.MFHelper = false; + + // ###################### // + /* ##### Pure Leech ##### */ + // ###################### // + + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + + // ########################## // + /* ##### Helper SCRIPTS ##### */ + // ########################## // + + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.RunDuriel = false; // Run duriel for extra chance at TwistedEssenceofSuffering + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /* Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + //Config.PickitFiles.push("kolton.nip"); + //Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 35; + * GOOD: Config.AttackSkill[1] = sdk.skills.LightningFury; + * BAD: Config.AttackSkill[1] = -35; + * BAD: Config.AttackSkill[1] = "LightningFury"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = []; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill. + Config.UseInnerSight = true; // Use inner sight as a precast + Config.UseSlowMissiles = true; // Use slow missiles as a precast + Config.UseDecoy = true; // Use decoy with merc stomp + Config.SummonValkyrie = true; // Summon Valkyrie + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet + //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces + + // The gems not used by other recipes will be used for magic item rerolling. + + //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem + //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + + //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling + + //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); + + //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * This will initiate muling when your character finds Ber, Jah, or SOJ. + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/config/Assassin.js b/d2bs/kolbot/libs/config/Assassin.js index a412c858a..fc39912dc 100644 --- a/d2bs/kolbot/libs/config/Assassin.js +++ b/d2bs/kolbot/libs/config/Assassin.js @@ -15,726 +15,793 @@ * Javascript statements need to end with a semi-colon; Good: Scripts.Corpsefire = false; Bad: Scripts.Corpsefire = false */ -function LoadConfig() { - /* Sequence config - * Set to true if you want to run it, set to false if not. - * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. - */ - - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - // ## Team MF - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal - Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed - Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto - Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - /* - * Unless stated otherwise, leader's character name isn't needed on order to run. - * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - */ - - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - - // ##### MFHelper ##### // - // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 - // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited - Scripts.MFHelper = false; - - // ###################### // - /* ##### Pure Leech ##### */ - // ###################### // - - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - - // ########################## // - /* ##### Helper SCRIPTS ##### */ - // ########################## // - - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 251; - * GOOD: Config.AttackSkill[1] = sdk.skills.FireBlast; - * BAD: Config.AttackSkill[1] = -251; - * BAD: Config.AttackSkill[1] = "FireBlast"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - Config.UseTraps = true; // Set to true to use traps - Config.Traps = [271, 271, 271, 276, 276]; // Skill IDs for traps to be cast on all mosters except act bosses. - Config.BossTraps = [271, 271, 271, 271, 271]; // Skill IDs for traps to be cast on act bosses. - - Config.SummonShadow = "Master"; // 0 = don't summon, 1 or "Warrior" = summon Shadow Warrior, 2 or "Master" = summon Shadow Master - Config.UseFade = true; // Set to true to use Fade prebuff. - Config.UseBoS = false; // Set to true to use Burst of Speed prebuff. TODO: Casting in town + UseFade compatibility - Config.UseVenom = false; // Set to true to use Venom prebuff. Set to false if you don't have the skill and have Arachnid Mesh - it will cause connection drop otherwise. - Config.UseBladeShield = false; // Set to true to use blade shield armor - Config.UseCloakofShadows = true; // Set to true to use Cloak of Shadows while fighting. Useful for blinding regular monsters/minions. - Config.AggressiveCloak = false; // Move into Cloak range or cast if already close - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // Make Perfect Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // Make Perfect Topaz - //Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // Make Perfect Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // Make Perfect Emerald - //Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // Make Perfect Ruby - //Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // Make Perfect Diamond - //Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // Make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - //Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - //Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - //Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling - - //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); - - //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; +function LoadConfig () { + /* Sequence config + * Set to true if you want to run it, set to false if not. + * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. + */ + + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal + Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed + Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto + Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + /* + * Unless stated otherwise, leader's character name isn't needed on order to run. + * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + */ + + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + + // ##### MFHelper ##### // + // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 + // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited + Scripts.MFHelper = false; + + // ###################### // + /* ##### Pure Leech ##### */ + // ###################### // + + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + + // ########################## // + /* ##### Helper SCRIPTS ##### */ + // ########################## // + + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.RunDuriel = false; // Run duriel for extra chance at TwistedEssenceofSuffering + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /* Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + //Config.PickitFiles.push("kolton.nip"); + //Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 251; + * GOOD: Config.AttackSkill[1] = sdk.skills.FireBlast; + * BAD: Config.AttackSkill[1] = -251; + * BAD: Config.AttackSkill[1] = "FireBlast"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = []; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + Config.UseTraps = true; // Set to true to use traps + Config.Traps = [271, 271, 271, 276, 276]; // Skill IDs for traps to be cast on all mosters except act bosses. + Config.BossTraps = [271, 271, 271, 271, 271]; // Skill IDs for traps to be cast on act bosses. + + Config.SummonShadow = "Master"; // 0 = don't summon, 1 or "Warrior" = summon Shadow Warrior, 2 or "Master" = summon Shadow Master + Config.UseFade = true; // Set to true to use Fade prebuff. + Config.UseBoS = false; // Set to true to use Burst of Speed prebuff. TODO: Casting in town + UseFade compatibility + Config.UseVenom = false; // Set to true to use Venom prebuff. Set to false if you don't have the skill and have Arachnid Mesh - it will cause connection drop otherwise. + Config.UseBladeShield = false; // Set to true to use blade shield armor + Config.UseCloakofShadows = true; // Set to true to use Cloak of Shadows while fighting. Useful for blinding regular monsters/minions. + Config.AggressiveCloak = false; // Move into Cloak range or cast if already close + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet + //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces + + // The gems not used by other recipes will be used for magic item rerolling. + + //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem + //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + + //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling + + //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); + + //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * This will initiate muling when your character finds Ber, Jah, or SOJ. + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/config/Barbarian.js b/d2bs/kolbot/libs/config/Barbarian.js index 2c658cbe3..3d3f942a8 100644 --- a/d2bs/kolbot/libs/config/Barbarian.js +++ b/d2bs/kolbot/libs/config/Barbarian.js @@ -15,715 +15,782 @@ * Javascript statements need to end with a semi-colon; Good: Scripts.Corpsefire = false; Bad: Scripts.Corpsefire = false */ -function LoadConfig() { - /* Sequence config - * Set to true if you want to run it, set to false if not. - * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. - */ - - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - // ## Team MF - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal - Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed - Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto - Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - /* - * Unless stated otherwise, leader's character name isn't needed on order to run. - * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - */ - - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - - // ##### MFHelper ##### // - // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 - // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited - Scripts.MFHelper = false; - - // ###################### // - /* ##### Pure Leech ##### */ - // ###################### // - - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - - // ########################## // - /* ##### Helper SCRIPTS ##### */ - // ########################## // - - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 151; - * GOOD: Config.AttackSkill[1] = sdk.skills.Whirlwind; - * BAD: Config.AttackSkill[1] = -151; - * BAD: Config.AttackSkill[1] = "Whirlwind"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill for bosses. - Config.AttackSkill[2] = -1; // Backup/Immune skill for bosses. - Config.AttackSkill[3] = -1; // Primary skill for others. - Config.AttackSkill[4] = -1; // Backup/Immune skill for others. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - Config.FindItem = false; // Use Find Item skill on corpses after clearing. - Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills - Config.UseWarcries = true; // use battle orders, battle command, and shout if we have them - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // Make Perfect Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // Make Perfect Topaz - //Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // Make Perfect Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // Make Perfect Emerald - //Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // Make Perfect Ruby - //Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // Make Perfect Diamond - //Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // Make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - //Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - //Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - //Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling - - //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); - - //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; +function LoadConfig () { + /* Sequence config + * Set to true if you want to run it, set to false if not. + * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. + */ + + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal + Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed + Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto + Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + /* + * Unless stated otherwise, leader's character name isn't needed on order to run. + * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + */ + + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + + // ##### MFHelper ##### // + // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 + // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited + Scripts.MFHelper = false; + + // ###################### // + /* ##### Pure Leech ##### */ + // ###################### // + + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + + // ########################## // + /* ##### Helper SCRIPTS ##### */ + // ########################## // + + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.RunDuriel = false; // Run duriel for extra chance at TwistedEssenceofSuffering + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /* Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + //Config.PickitFiles.push("kolton.nip"); + //Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 151; + * GOOD: Config.AttackSkill[1] = sdk.skills.Whirlwind; + * BAD: Config.AttackSkill[1] = -151; + * BAD: Config.AttackSkill[1] = "Whirlwind"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill for bosses. + Config.AttackSkill[2] = -1; // Backup/Immune skill for bosses. + Config.AttackSkill[3] = -1; // Primary skill for others. + Config.AttackSkill[4] = -1; // Backup/Immune skill for others. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Low mana skill. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = []; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + Config.FindItem = false; // Use Find Item skill on corpses after clearing. + Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills + Config.UseWarcries = true; // use battle orders, battle command, and shout if we have them + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet + //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces + + // The gems not used by other recipes will be used for magic item rerolling. + + //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem + //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + + //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling + + //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); + + //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * This will initiate muling when your character finds Ber, Jah, or SOJ. + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/config/Builds/Class.Build.js b/d2bs/kolbot/libs/config/Builds/Class.Build.js index dfc927c01..519d07360 100644 --- a/d2bs/kolbot/libs/config/Builds/Class.Build.js +++ b/d2bs/kolbot/libs/config/Builds/Class.Build.js @@ -4,7 +4,7 @@ * * Instructions: See /d2bs/kolbot/libs/config/Builds/README.txt * -* Skill IDs: See /d2bs/kolbot/sdk/skills.txt for a list of skill IDs. +* Skill IDs: See /d2bs/kolbot/sdk/txt/skills.txt for a list of skill IDs. * * Stat IDs: * @@ -16,901 +16,900 @@ */ js_strict(true); -if (!isIncluded("common/Cubing.js")) { include("common/Cubing.js"); }; -if (!isIncluded("common/Prototypes.js")) { include("common/Prototypes.js"); }; -if (!isIncluded("common/Runewords.js")) { include("common/Runewords.js"); }; -if (!isIncluded("common/Town.js")) { include("common/Town.js"); }; - -var AutoBuildTemplate = { - - 1: { - //SkillPoints: [-1], // This doesn't matter. We don't have skill points to spend at lvl 1 - //StatPoints: [-1,-1,-1,-1,-1], // This doesn't matter. We don't have stat points to spend at lvl 1 - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 2: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 3: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 4: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 5: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 6: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 7: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 8: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 9: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 10: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 11: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 12: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 13: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 14: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 15: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 16: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 17: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 18: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 19: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 20: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 21: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 22: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 23: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 24: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 25: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 26: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 27: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 28: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 29: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 30: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 31: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 32: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 33: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 34: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 35: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 36: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 37: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 38: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 39: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 40: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 41: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 42: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 43: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 44: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 45: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 46: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 47: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 48: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 49: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 50: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 51: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 52: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 53: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 54: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 55: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 56: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 57: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 58: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 59: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 60: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 61: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 62: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 63: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 64: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 65: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 66: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 67: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 68: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 69: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 70: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 71: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 72: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 73: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 74: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 75: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 76: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 77: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 78: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 79: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 80: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 81: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 82: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 83: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 84: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 85: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 86: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 87: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 88: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 89: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 90: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 91: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 92: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 93: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 94: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 95: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 96: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 97: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 98: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, - - 99: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - } -}; \ No newline at end of file +if (!isIncluded("core/Cubing.js")) { include("core/Cubing.js"); } +if (!isIncluded("core/Prototypes.js")) { include("core/Prototypes.js"); } +if (!isIncluded("core/Runewords.js")) { include("core/Runewords.js"); } +if (!isIncluded("core/Town.js")) { include("core/Town.js"); } + +const AutoBuildTemplate = { + 1: { + //SkillPoints: [-1], // This doesn't matter. We don't have skill points to spend at lvl 1 + //StatPoints: [-1,-1,-1,-1,-1], // This doesn't matter. We don't have stat points to spend at lvl 1 + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 2: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 3: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 4: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 5: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 6: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 7: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 8: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 9: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 10: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 11: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 12: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 13: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 14: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 15: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 16: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 17: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 18: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 19: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 20: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 21: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 22: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 23: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 24: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 25: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 26: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 27: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 28: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 29: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 30: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 31: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 32: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 33: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 34: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 35: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 36: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 37: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 38: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 39: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 40: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 41: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 42: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 43: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 44: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 45: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 46: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 47: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 48: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 49: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 50: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 51: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 52: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 53: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 54: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 55: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 56: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 57: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 58: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 59: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 60: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 61: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 62: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 63: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 64: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 65: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 66: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 67: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 68: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 69: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 70: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 71: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 72: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 73: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 74: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 75: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 76: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 77: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 78: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 79: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 80: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 81: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 82: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 83: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 84: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 85: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 86: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 87: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 88: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 89: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 90: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 91: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 92: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 93: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 94: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 95: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 96: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 97: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 98: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, + + 99: { + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + } +}; diff --git a/d2bs/kolbot/libs/config/Builds/Sorceress.ExampleBuild.js b/d2bs/kolbot/libs/config/Builds/Sorceress.ExampleBuild.js index 3d39b9c1e..e5a8dc50a 100644 --- a/d2bs/kolbot/libs/config/Builds/Sorceress.ExampleBuild.js +++ b/d2bs/kolbot/libs/config/Builds/Sorceress.ExampleBuild.js @@ -4,7 +4,7 @@ * * Instructions: See /d2bs/kolbot/libs/config/Builds/README.txt * -* Skill IDs: See /d2bs/kolbot/sdk/skills.txt for a list of skill IDs. +* Skill IDs: See /d2bs/kolbot/sdk/txt/skills.txt for a list of skill IDs. * * Stat IDs: * @@ -16,924 +16,923 @@ */ js_strict(true); -if (!isIncluded("common/Cubing.js")) { include("common/Cubing.js"); }; -if (!isIncluded("common/Prototypes.js")) { include("common/Prototypes.js"); }; -if (!isIncluded("common/Runewords.js")) { include("common/Runewords.js"); }; -if (!isIncluded("common/Town.js")) { include("common/Town.js"); }; - -var AutoBuildTemplate = { +include("core/Cubing.js"); +include("core/Prototypes.js"); +include("core/Runewords.js"); +include("core/Town.js"); +const AutoBuildTemplate = { 1: { - //SkillPoints: [-1], // This doesn't matter. We don't have skill points to spend at lvl 1 - //StatPoints: [-1,-1,-1,-1,-1], // This doesn't matter. We don't have stat points to spend at lvl 1 - Update: function () { - - Scripts.ClearAnyArea = true; // We are only level 1 so we will start by clearing Blood Moor - Config.ClearAnyArea.AreaList = [2]; - Config.ClearType = 0; // Monster spectype to kill in level clear scripts (0 = all) - - // Config.PickitFiles.push("level/1.nip"); // File "level/1.nip" is not included, it's just an example. - - Config.OpenChests = true; // Open chests. Controls key buying. - Config.LogExperience = false; // Print experience statistics in the manager. - Config.StashGold = 200; // Minimum amount of gold to stash. - Config.AttackSkill = [0, 36, -1, 36, 36, 0, 0]; // At level 1 we start with a +1 Fire Bolt staff - Config.LowManaSkill = [0, 0]; - Config.PublicMode = 1; - Config.ScanShrines = [15, 13, 12, 14, 7, 6, 2]; - Config.BeltColumn = ["hp", "hp", "hp", "mp"]; // Keep tons of health potions! - } - }, + //SkillPoints: [-1], // This doesn't matter. We don't have skill points to spend at lvl 1 + //StatPoints: [-1,-1,-1,-1,-1], // This doesn't matter. We don't have stat points to spend at lvl 1 + Update: function () { + + Scripts.ClearAnyArea = true; // We are only level 1 so we will start by clearing Blood Moor + Config.ClearAnyArea.AreaList = [2]; + Config.ClearType = 0; // Monster spectype to kill in level clear scripts (0 = all) + + // Config.PickitFiles.push("level/1.nip"); // File "level/1.nip" is not included, it's just an example. + + Config.OpenChests = true; // Open chests. Controls key buying. + Config.LogExperience = false; // Print experience statistics in the manager. + Config.StashGold = 200; // Minimum amount of gold to stash. + Config.AttackSkill = [0, 36, -1, 36, 36, 0, 0]; // At level 1 we start with a +1 Fire Bolt staff + Config.LowManaSkill = [0, 0]; + Config.PublicMode = 1; + Config.ScanShrines = [15, 13, 12, 14, 7, 6, 2]; + Config.BeltColumn = ["hp", "hp", "hp", "mp"]; // Keep tons of health potions! + } + }, 2: { - SkillPoints: [36], // Fire Bolt + 1 - StatPoints: [0, 3, 3, 3, 3], // Strength + 1 , Vitality + 4 - Update: function () { - // Config.PickitFiles.splice(Config.PickitFiles.indexOf("level/1.nip"), 1); // Will remove index "level/1.nip" from Config.PickitFiles - // Config.PickitFiles.push("level/2.nip"); - Config.BeltColumn = ["hp", "hp", "mp", "mp"]; - Config.MinColumn = [1, 1, 1, 1]; - } - }, + SkillPoints: [36], // Fire Bolt + 1 + StatPoints: [0, 3, 3, 3, 3], // Strength + 1 , Vitality + 4 + Update: function () { + // Config.PickitFiles.splice(Config.PickitFiles.indexOf("level/1.nip"), 1); // Will remove index "level/1.nip" from Config.PickitFiles + // Config.PickitFiles.push("level/2.nip"); + Config.BeltColumn = ["hp", "hp", "mp", "mp"]; + Config.MinColumn = [1, 1, 1, 1]; + } + }, 3: { - SkillPoints: [39], // Ice Bolt + 1 - StatPoints: [0, 0, 3, 3, 3], // Strength + 2 , Vitality + 3 - Update: function () { - Config.AttackSkill = [39, 36, -1, 36, 0, 0, 0]; // Ice Bolt and Fire Bolt - } - }, + SkillPoints: [39], // Ice Bolt + 1 + StatPoints: [0, 0, 3, 3, 3], // Strength + 2 , Vitality + 3 + Update: function () { + Config.AttackSkill = [39, 36, -1, 36, 0, 0, 0]; // Ice Bolt and Fire Bolt + } + }, 4: { - SkillPoints: [37], // Warmth + 1 - StatPoints: [0, 0, 0, 3, 3], // Strength + 3 , Vitality + 2 - Update: function () { - Scripts.Corpsefire = true; // Lets try Corpsefire now that we're level 4 - Config.Corpsefire.ClearDen = true; + SkillPoints: [37], // Warmth + 1 + StatPoints: [0, 0, 0, 3, 3], // Strength + 3 , Vitality + 2 + Update: function () { + Scripts.Corpsefire = true; // Lets try Corpsefire now that we're level 4 + Config.Corpsefire.ClearDen = true; - Scripts.ClearAnyArea = false; // Don't want to clear Blood Moor anymore (See lvl 1 above) - Config.ClearAnyArea.AreaList = []; + Scripts.ClearAnyArea = false; // Don't want to clear Blood Moor anymore (See lvl 1 above) + Config.ClearAnyArea.AreaList = []; - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; // Start keeping rejuvs since we have +1 Warmth - Config.MinColumn = [1, 1, 1, 0]; - } - }, + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; // Start keeping rejuvs since we have +1 Warmth + Config.MinColumn = [1, 1, 1, 0]; + } + }, 5: { - SkillPoints: [38], // Charged Bolt + 1 - StatPoints: [0, 0, 0, 0, 3], // Strength + 4 , Vitality + 1 - Update: function () { + SkillPoints: [38], // Charged Bolt + 1 + StatPoints: [0, 0, 0, 0, 3], // Strength + 4 , Vitality + 1 + Update: function () { - Scripts.ClearAnyArea = true; // Now we'll try enabling it again Cold Plains and Stony Field - Config.ClearAnyArea.AreaList = [3, 4]; + Scripts.ClearAnyArea = true; // Now we'll try enabling it again Cold Plains and Stony Field + Config.ClearAnyArea.AreaList = [3, 4]; - Config.ScanShrines = [15, 13, 12]; - Config.AttackSkill = [39, 36, -1, 38, 0, 39, 0]; // All the bolts! - } - }, + Config.ScanShrines = [15, 13, 12]; + Config.AttackSkill = [39, 36, -1, 38, 0, 39, 0]; // All the bolts! + } + }, 6: { - SkillPoints: [36], // Fire Bolt + 1 - StatPoints: [0, 0, 3, 3, 2], // Strength + 2 , Vitality + 2, Dexterity + 1 - Update: function () { - Config.AttackSkill = [39, 36, -1, 36, 0, 38, 0]; // All the bolts! - } - }, + SkillPoints: [36], // Fire Bolt + 1 + StatPoints: [0, 0, 3, 3, 2], // Strength + 2 , Vitality + 2, Dexterity + 1 + Update: function () { + Config.AttackSkill = [39, 36, -1, 36, 0, 38, 0]; // All the bolts! + } + }, 7: { - SkillPoints: [-1], // TODO - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], // TODO + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 8: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 9: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 10: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 11: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 12: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 13: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 14: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 15: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 16: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 17: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 18: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 19: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 20: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 21: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 22: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 23: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 24: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 25: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 26: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 27: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 28: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 29: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 30: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 31: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 32: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 33: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 34: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 35: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 36: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 37: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 38: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 39: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 40: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 41: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 42: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 43: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 44: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 45: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 46: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 47: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 48: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 49: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 50: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 51: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 52: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 53: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 54: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 55: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 56: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 57: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 58: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 59: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 60: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 61: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 62: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 63: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 64: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 65: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 66: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 67: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 68: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 69: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 70: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 71: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 72: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 73: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 74: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 75: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 76: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 77: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 78: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 79: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 80: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 81: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 82: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 83: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 84: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 85: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 86: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 87: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 88: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 89: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 90: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 91: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 92: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 93: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 94: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 95: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 96: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 97: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 98: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - }, + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + }, 99: { - SkillPoints: [-1], - StatPoints: [-1,-1,-1,-1,-1], - Update: function () { - Config.AttackSkill = [-1,-1,-1,-1,-1,-1,-1]; - Config.LowManaSkill = [-1,-1]; - } - } -}; \ No newline at end of file + SkillPoints: [-1], + StatPoints: [-1, -1, -1, -1, -1], + Update: function () { + Config.AttackSkill = [-1, -1, -1, -1, -1, -1, -1]; + Config.LowManaSkill = [-1, -1]; + } + } +}; diff --git a/d2bs/kolbot/libs/config/Druid.js b/d2bs/kolbot/libs/config/Druid.js index 13e35d275..77274e647 100644 --- a/d2bs/kolbot/libs/config/Druid.js +++ b/d2bs/kolbot/libs/config/Druid.js @@ -15,719 +15,786 @@ * Javascript statements need to end with a semi-colon; Good: Scripts.Corpsefire = false; Bad: Scripts.Corpsefire = false */ -function LoadConfig() { - /* Sequence config - * Set to true if you want to run it, set to false if not. - * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. - */ - - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - // ## Team MF - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal - Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed - Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto - Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - /* - * Unless stated otherwise, leader's character name isn't needed on order to run. - * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - */ - - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - - // ##### MFHelper ##### // - // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 - // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited - Scripts.MFHelper = false; - - // ###################### // - /* ##### Pure Leech ##### */ - // ###################### // - - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - - // ########################## // - /* ##### Helper SCRIPTS ##### */ - // ########################## // - - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 245; - * GOOD: Config.AttackSkill[1] = sdk.skills.Tornado; - * BAD: Config.AttackSkill[1] = -245; - * BAD: Config.AttackSkill[1] = "Tornado"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - Config.SummonRaven = false; - Config.SummonAnimal = "Grizzly"; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly - Config.SummonSpirit = "Oak Sage"; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs" - Config.SummonVine = "Poison Creeper"; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper" - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // Make Perfect Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // Make Perfect Topaz - //Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // Make Perfect Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // Make Perfect Emerald - //Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // Make Perfect Ruby - //Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // Make Perfect Diamond - //Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // Make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - //Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - //Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - //Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling - - //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); - - //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; +function LoadConfig () { + /* Sequence config + * Set to true if you want to run it, set to false if not. + * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. + */ + + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal + Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed + Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto + Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + /* + * Unless stated otherwise, leader's character name isn't needed on order to run. + * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + */ + + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + + // ##### MFHelper ##### // + // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 + // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited + Scripts.MFHelper = false; + + // ###################### // + /* ##### Pure Leech ##### */ + // ###################### // + + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + + // ########################## // + /* ##### Helper SCRIPTS ##### */ + // ########################## // + + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.RunDuriel = false; // Run duriel for extra chance at TwistedEssenceofSuffering + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /* Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + //Config.PickitFiles.push("kolton.nip"); + //Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 245; + * GOOD: Config.AttackSkill[1] = sdk.skills.Tornado; + * BAD: Config.AttackSkill[1] = -245; + * BAD: Config.AttackSkill[1] = "Tornado"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = []; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + Config.SummonRaven = false; + Config.SummonAnimal = "Grizzly"; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly + Config.SummonSpirit = "Oak Sage"; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs" + Config.SummonVine = "Poison Creeper"; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper" + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet + //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces + + // The gems not used by other recipes will be used for magic item rerolling. + + //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem + //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + + //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling + + //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); + + //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * This will initiate muling when your character finds Ber, Jah, or SOJ. + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/config/Necromancer.js b/d2bs/kolbot/libs/config/Necromancer.js index fc30e650b..f652ccc5e 100644 --- a/d2bs/kolbot/libs/config/Necromancer.js +++ b/d2bs/kolbot/libs/config/Necromancer.js @@ -15,740 +15,807 @@ * Javascript statements need to end with a semi-colon; Good: Scripts.Corpsefire = false; Bad: Scripts.Corpsefire = false */ -function LoadConfig() { - /* Sequence config - * Set to true if you want to run it, set to false if not. - * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. - */ - - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - // ## Team MF - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal - Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed - Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto - Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - /* - * Unless stated otherwise, leader's character name isn't needed on order to run. - * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - */ - - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - - // ##### MFHelper ##### // - // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 - // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited - Scripts.MFHelper = false; - - // ###################### // - /* ##### Pure Leech ##### */ - // ###################### // - - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - - // ########################## // - /* ##### Helper SCRIPTS ##### */ - // ########################## // - - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 84; - * GOOD: Config.AttackSkill[1] = sdk.skills.BoneSpear; - * BAD: Config.AttackSkill[1] = -84; - * BAD: Config.AttackSkill[1] = "BoneSpear"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - Config.Curse[0] = 0; // Boss curse. Use skill number or set to 0 to disable. - Config.Curse[1] = 0; // Other monsters curse. Use skill number or set to 0 to disable. - - /* Custom curses for monster - * Can use monster name or classid - * Format: Config.CustomCurse = [["monstername", skillid], [156, skillid]]; - * Optional 3rd parameter for spectype, leave blank to use on all - 0x00 Normal Monster - 0x01 Super Unique - 0x02 Champion - 0x04 Boss - 0x08 Minion - Example: Config.CustomCurse = [["HellBovine", 60], [571, 87], ["SkeletonArcher", 71, 0x00]]; - */ - Config.CustomCurse = []; - - Config.ExplodeCorpses = 0; // Explode corpses. Use skill number or 0 to disable. 74 = Corpse Explosion, 83 = Poison Explosion - Config.Golem = "None"; // Golem. 0 or "None" = don't summon, 1 or "Clay" = Clay Golem, 2 or "Blood" = Blood Golem, 3 or "Fire" = Fire Golem - Config.Skeletons = 0; // Number of skeletons to raise. Set to "max" to auto detect, set to 0 to disable. - Config.SkeletonMages = 0; // Number of skeleton mages to raise. Set to "max" to auto detect, set to 0 to disable. - Config.Revives = 0; // Number of revives to raise. Set to "max" to auto detect, set to 0 to disable. - Config.PoisonNovaDelay = 2; // Delay between two Poison Novas in seconds. - Config.ActiveSummon = false; // Raise dead between each attack. If false, it will raise after clearing a spot. - Config.ReviveUnstackable = true; // Revive monsters that can move freely after you teleport. - Config.IronGolemChicken = 30; // Exit game if Iron Golem's life is less or equal to designated percent. - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // Make Perfect Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // Make Perfect Topaz - //Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // Make Perfect Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // Make Perfect Emerald - //Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // Make Perfect Ruby - //Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // Make Perfect Diamond - //Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // Make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - //Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - //Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - //Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling - - //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); - - //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; +function LoadConfig () { + /* Sequence config + * Set to true if you want to run it, set to false if not. + * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. + */ + + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal + Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed + Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto + Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + /* + * Unless stated otherwise, leader's character name isn't needed on order to run. + * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + */ + + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + + // ##### MFHelper ##### // + // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 + // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited + Scripts.MFHelper = false; + + // ###################### // + /* ##### Pure Leech ##### */ + // ###################### // + + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + + // ########################## // + /* ##### Helper SCRIPTS ##### */ + // ########################## // + + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.RunDuriel = false; // Run duriel for extra chance at TwistedEssenceofSuffering + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /* Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + //Config.PickitFiles.push("kolton.nip"); + //Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 84; + * GOOD: Config.AttackSkill[1] = sdk.skills.BoneSpear; + * BAD: Config.AttackSkill[1] = -84; + * BAD: Config.AttackSkill[1] = "BoneSpear"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = []; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + Config.Curse[0] = 0; // Boss curse. Use skill number or set to 0 to disable. + Config.Curse[1] = 0; // Other monsters curse. Use skill number or set to 0 to disable. + + /* Custom curses for monster + * Can use monster name or classid + * Format: Config.CustomCurse = [["monstername", skillid], [156, skillid]]; + * Optional 3rd parameter for spectype, leave blank to use on all + 0x00 Normal Monster + 0x01 Super Unique + 0x02 Champion + 0x04 Boss + 0x08 Minion + Example: Config.CustomCurse = [["HellBovine", 60], [571, 87], ["SkeletonArcher", 71, 0x00]]; + */ + Config.CustomCurse = []; + + Config.ExplodeCorpses = 0; // Explode corpses. Use skill number or 0 to disable. 74 = Corpse Explosion, 83 = Poison Explosion + Config.Golem = "None"; // Golem. 0 or "None" = don't summon, 1 or "Clay" = Clay Golem, 2 or "Blood" = Blood Golem, 3 or "Fire" = Fire Golem + Config.Skeletons = 0; // Number of skeletons to raise. Set to "max" to auto detect, set to 0 to disable. + Config.SkeletonMages = 0; // Number of skeleton mages to raise. Set to "max" to auto detect, set to 0 to disable. + Config.Revives = 0; // Number of revives to raise. Set to "max" to auto detect, set to 0 to disable. + Config.PoisonNovaDelay = 2; // Delay between two Poison Novas in seconds. + Config.ActiveSummon = false; // Raise dead between each attack. If false, it will raise after clearing a spot. + Config.ReviveUnstackable = true; // Revive monsters that can move freely after you teleport. + Config.IronGolemChicken = 30; // Exit game if Iron Golem's life is less or equal to designated percent. + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet + //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces + + // The gems not used by other recipes will be used for magic item rerolling. + + //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem + //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + + //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling + + //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); + + //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * This will initiate muling when your character finds Ber, Jah, or SOJ. + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/config/Paladin.js b/d2bs/kolbot/libs/config/Paladin.js index 218cd92b2..0af908f99 100644 --- a/d2bs/kolbot/libs/config/Paladin.js +++ b/d2bs/kolbot/libs/config/Paladin.js @@ -15,719 +15,787 @@ * Javascript statements need to end with a semi-colon; Good: Scripts.Corpsefire = false; Bad: Scripts.Corpsefire = false */ -function LoadConfig() { - /* Sequence config - * Set to true if you want to run it, set to false if not. - * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. - */ - - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - // ## Team MF - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal - Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed - Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto - Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - /* - * Unless stated otherwise, leader's character name isn't needed on order to run. - * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - */ - - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - - // ##### MFHelper ##### // - // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 - // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited - Scripts.MFHelper = false; - - // ###################### // - /* ##### Pure Leech ##### */ - // ###################### // - - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - - // ########################## // - /* ##### Helper SCRIPTS ##### */ - // ########################## // - - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 97; - * GOOD: Config.AttackSkill[1] = sdk.skills.Smite; - * BAD: Config.AttackSkill[1] = -97; - * BAD: Config.AttackSkill[1] = "smite"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary aura to bosses - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary aura to others. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary aura. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Low mana skill. - Config.LowManaSkill[1] = -1; // Low mana aura. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - Config.AvoidDolls = false; // Try to attack dolls from a greater distance with hammerdins. - Config.Vigor = true; // Swith to Vigor when running - Config.Charge = true; // Use Charge when running - Config.Redemption = [50, 50]; // Switch to Redemption after clearing an area if under designated life or mana. Format: [lifepercent, manapercent] - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // Make Perfect Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // Make Perfect Topaz - //Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // Make Perfect Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // Make Perfect Emerald - //Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // Make Perfect Ruby - //Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // Make Perfect Diamond - //Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // Make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - //Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - //Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - //Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling - - //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); - - //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; +function LoadConfig () { + /* Sequence config + * Set to true if you want to run it, set to false if not. + * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. + */ + + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal + Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed + Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto + Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + /* + * Unless stated otherwise, leader's character name isn't needed on order to run. + * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + */ + + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + + // ##### MFHelper ##### // + // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 + // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited + Scripts.MFHelper = false; + + // ###################### // + /* ##### Pure Leech ##### */ + // ###################### // + + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + + // ########################## // + /* ##### Helper SCRIPTS ##### */ + // ########################## // + + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.RunDuriel = false; // Run duriel for extra chance at TwistedEssenceofSuffering + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /* Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + //Config.PickitFiles.push("kolton.nip"); + //Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 97; + * GOOD: Config.AttackSkill[1] = sdk.skills.Smite; + * BAD: Config.AttackSkill[1] = -97; + * BAD: Config.AttackSkill[1] = "smite"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary aura to bosses + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary aura to others. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary aura. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Low mana skill. + Config.LowManaSkill[1] = -1; // Low mana aura. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = []; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + Config.AvoidDolls = false; // Try to attack dolls from a greater distance with hammerdins. + Config.Vigor = true; // Swith to Vigor when running + Config.RunningAura = -1; // Aura to use when running, DO NOT use in conjunction with Config.Vigor it will be ignored + Config.Charge = true; // Use Charge when running + Config.Redemption = [50, 50]; // Switch to Redemption after clearing an area if under designated life or mana. Format: [lifepercent, manapercent] + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet + //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces + + // The gems not used by other recipes will be used for magic item rerolling. + + //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem + //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + + //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling + + //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); + + //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * This will initiate muling when your character finds Ber, Jah, or SOJ. + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/config/Sorceress.js b/d2bs/kolbot/libs/config/Sorceress.js index f2cff764d..4525a9427 100644 --- a/d2bs/kolbot/libs/config/Sorceress.js +++ b/d2bs/kolbot/libs/config/Sorceress.js @@ -15,721 +15,787 @@ * Javascript statements need to end with a semi-colon; Good: Scripts.Corpsefire = false; Bad: Scripts.Corpsefire = false */ -function LoadConfig() { - /* Sequence config - * Set to true if you want to run it, set to false if not. - * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. - */ - - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - // ## Team MF - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal - Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed - Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto - Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - /* - * Unless stated otherwise, leader's character name isn't needed on order to run. - * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - */ - - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - - // ##### MFHelper ##### // - // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 - // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited - Scripts.MFHelper = false; - - // ###################### // - /* ##### Pure Leech ##### */ - // ###################### // - - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - - // ########################## // - /* ##### Helper SCRIPTS ##### */ - // ########################## // - - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 56; - * GOOD: Config.AttackSkill[1] = sdk.skills.Meteor; - * BAD: Config.AttackSkill[1] = -56; - * BAD: Config.AttackSkill[1] = "meteor"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - Config.CastStatic = 60; // Cast static until the target is at designated life percent. 100 = disabled. - Config.StaticList = []; // List of monster NAMES or CLASSIDS to static. Example: Config.StaticList = ["Andariel", 243]; - Config.UseTelekinesis = true; // Use telekinesis on units that allow it. Example: Shrines, Waypoints, Chests, and Portals - Config.UseEnergyShield = false; // set to true to use energy shield if its available - Config.UseColdArmor = true; // use armor skills, uses skill ids or set to true to let the bot decide based on skill level or false to disable completely - // (40 / sdk.skills.FrozenArmor)(50 / sdk.skills.ShiverArmor)(60 / sdk.skills.ChillingArmor) - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // Make Perfect Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // Make Perfect Topaz - //Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // Make Perfect Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // Make Perfect Emerald - //Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // Make Perfect Ruby - //Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // Make Perfect Diamond - //Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // Make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - //Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - //Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - //Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling - - //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); - - //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; +function LoadConfig () { + /* Sequence config + * Set to true if you want to run it, set to false if not. + * If you want to change the order of the scripts, just change the order of their lines by using cut and paste. + */ + + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = true; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters (for example BO barb + sorc) + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + Config.Cows.DontMakePortal = false; // if set to true, will go to act 1 stash and wait for 3 minutes for someone to make the cow portal + Config.Cows.JustMakePortal = false; // if set to true just opens cow portal but doesn't clear - useful to ensure maker never gets king killed + Config.Cows.KillKing = false; // MAKE SURE YOUR MAKER DOESN"T HAVE THIS SET TO TRUE!!!! + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Config.Hephasto.ClearRiver = false; // Clear river after killing Hephasto + Config.Hephasto.ClearType = 0xF; // 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + /* + * Unless stated otherwise, leader's character name isn't needed on order to run. + * Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + */ + + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + + // ##### MFHelper ##### // + // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true and Config.PublicMode > 0 + // NOTE: MFHelper ends when Config.Leader starts Diablo or Baal. Use one of the specific helper scripts as they are better suited + Scripts.MFHelper = false; + + // ###################### // + /* ##### Pure Leech ##### */ + // ###################### // + + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + + // ########################## // + /* ##### Helper SCRIPTS ##### */ + // ########################## // + + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /* Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + //Config.PickitFiles.push("kolton.nip"); + //Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 56; + * GOOD: Config.AttackSkill[1] = sdk.skills.Meteor; + * BAD: Config.AttackSkill[1] = -56; + * BAD: Config.AttackSkill[1] = "meteor"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = []; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + Config.CastStatic = 60; // Cast static until the target is at designated life percent. 100 = disabled. + Config.StaticList = []; // List of monster NAMES or CLASSIDS to static. Example: Config.StaticList = ["Andariel", 243]; + Config.UseTelekinesis = true; // Use telekinesis on units that allow it. Example: Shrines, Waypoints, Chests, and Portals + Config.UseEnergyShield = false; // set to true to use energy shield if its available + Config.UseColdArmor = true; // use armor skills, uses skill ids or set to true to let the bot decide based on skill level or false to disable completely + // (40 / sdk.skills.FrozenArmor)(50 / sdk.skills.ShiverArmor)(60 / sdk.skills.ChillingArmor) + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Armet + //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Vambraces + + // The gems not used by other recipes will be used for magic item rerolling. + + //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem + //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + + //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = false; // Set to true to enable runeword making/rerolling + + //Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + //Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); + + //Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + //Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + //Config.KeepRunewords.push("[type] == shield || [type] == auricshields # [fcr] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * This will initiate muling when your character finds Ber, Jah, or SOJ. + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/config/Templates/chestlist.md b/d2bs/kolbot/libs/config/Templates/chestlist.md new file mode 100644 index 000000000..33b066acf --- /dev/null +++ b/d2bs/kolbot/libs/config/Templates/chestlist.md @@ -0,0 +1,22 @@ +| Act | ID | Area Name | --- | Act | ID | Area Name | +| --- | --- | ----------- | --- | --- | --- |------------ | +| `1` | | | --- | `2` | | +| | 15 | Hole Level 2 | | | 55 | Stony Tomb | +| | 13 | Cave Level 2 | | | 65 | Ancient Tunnels | +| | 16 | Pit Level 2 | | | 66-72 | Tal Tombs | +| | 14 | Passage Level 2 | | | | | +| | 25 | Tower Level 5 | | | | | +| | 18 | Crypt | | | | | +| | 19 | Mausoleum | | | | | +| | | +| | | | | | | +| Act | ID | Area Name | --- | Act | ID | Area Name | +| `3` | | | --- | `5` | | +| | 84 | Spider Cave | | | 115 | Glacial Trail | +| | 85 | Spider Cavern | | | 116 | Drifter Cavern| +| | 77 | Great Marsh | | | 119 | Frozen Cellar | +| | 90 | Swampy Pit Lvl3 | | | 125 | Abbadon | +| | 79 | Lower Kurast | | | 126 | Pit of Archeon| +| | 80 | Kurast Bazaar | | | 127 | Infernal Pit | +| | 92 | Sewers Level 1 | | | | | +| | 93 | Sewers Level 2 | | | | | \ No newline at end of file diff --git a/d2bs/kolbot/libs/config/Templates/chestlist.txt b/d2bs/kolbot/libs/config/Templates/chestlist.txt deleted file mode 100644 index b71d3ffd0..000000000 --- a/d2bs/kolbot/libs/config/Templates/chestlist.txt +++ /dev/null @@ -1,28 +0,0 @@ -ID Act1 -15 - Hole Level 2 -13 - Cave Level 2 -16 - Pit Level 2 -14 - Passage Level 2 -25 - Tower Level 5 -18 - Crypta -19 - Mausoleum - Act2 -55 - stony Tomb -65 - Ancient Tunnels -66-72 - Tal Tombs - Act3 -84 - SpiderCave -85 - SpiderCavern -77 - Marsh -90 - Smapy Grave -79 - lower Kurast -80 - Kurast Basar -92 - Sewers Level 1 -93 - Sewers Level 2 - Act5 -115 - Glacial Trail -116 - Drifter Cavern -119 - FrozenCellar -125 - Abbadon -126 - Archeon -127 - Infernal \ No newline at end of file diff --git a/d2bs/kolbot/libs/config/_BaseConfigFile.js b/d2bs/kolbot/libs/config/_BaseConfigFile.js index 2f757fd65..fb34dfa24 100644 --- a/d2bs/kolbot/libs/config/_BaseConfigFile.js +++ b/d2bs/kolbot/libs/config/_BaseConfigFile.js @@ -4,895 +4,1016 @@ * - copy the desired script/config section and paste it to your character configuration file, named Class.CharName.js */ - // User addon script. Read the description in libs/bots/UserAddon.js - Scripts.UserAddon = false; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! - - // Battle orders script - Use this for 2+ characters - Scripts.BattleOrders = false; - Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO - Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. - Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. - Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails - Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot - Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) - - Scripts.BoBarbHelper = false; // specific HC script with BoBarb on the Bo area during whole game | set it only in barbarian config - Config.BoBarbHelper.Mode = -1; // 0 = give BO, -1 = disabled - Config.BoBarbHelper.Wp = 35; // 35 = Catacombs level 2 - - // ## Team MF system - Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. - Scripts.MFHelper = false; // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true - Config.BreakClearLevel = false; // Stop clearing the current area if the leader goes to another area - - // ############################# // - /* ##### LEECHING SETTINGS ##### */ - // ############################# // - - // leader's character name isn't needed on order to run. Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) - Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) - Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; - Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). - Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. - - // ############################# // - /* ##### BOSS/AREA SCRIPTS ##### */ - // ############################# // - - // *** act 1 *** - Scripts.Corpsefire = false; - Config.Corpsefire.ClearDen = false; - Scripts.Bishibosh = false; - Scripts.Mausoleum = false; - Config.Mausoleum.KillBishibosh = false; - Config.Mausoleum.KillBloodRaven = false; - Config.Mausoleum.ClearCrypt = false; - Scripts.Rakanishu = false; - Config.Rakanishu.KillGriswold = true; - Scripts.UndergroundPassage = false; - Scripts.Coldcrow = false; - Scripts.Tristram = false; - Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Pit = false; - Config.Pit.ClearPit1 = true; - Scripts.Treehead = false; - Scripts.Smith = false; - Scripts.BoneAsh = false; - Scripts.Countess = false; - Config.Countess.KillGhosts = false; - Scripts.Andariel = false; - Scripts.Cows = false; - - // *** act 2 *** - Scripts.Radament = false; - Scripts.CreepingFeature = false; - Scripts.Coldworm = false; - Config.Coldworm.KillBeetleburst = false; - Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels - Scripts.AncientTunnels = false; - Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City - Config.AncientTunnels.KillDarkElder = false; - Scripts.Summoner = false; - Config.Summoner.FireEye = false; - Scripts.Tombs = false; - Config.Tombs.KillDuriel = false; - Scripts.Duriel = false; - - // *** act 3 *** - Scripts.Stormtree = false; - Scripts.BattlemaidSarina = false; - Scripts.KurastTemples = false; - Scripts.Icehawk = false; - Scripts.Endugu = false; - Scripts.Travincal = false; - Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. - Scripts.Mephisto = false; - Config.Mephisto.MoatTrick = false; - Config.Mephisto.KillCouncil = false; - Config.Mephisto.TakeRedPortal = true; - - // *** act 4 *** - Scripts.OuterSteppes = false; - Scripts.Izual = false; - Scripts.Hephasto = false; - Scripts.Diablo = false; - Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals - Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers - Config.Diablo.Entrance = true; // Start from entrance - Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. - Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. - Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path - Config.Diablo.SealWarning = "Leave the seals alone!"; - Config.Diablo.EntranceTP = "Entrance TP up"; - Config.Diablo.StarTP = "Star TP up"; - Config.Diablo.DiabloMsg = "Diablo"; - Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - - // *** act 5 *** - Scripts.Pindleskin = false; - Config.Pindleskin.UseWaypoint = false; - Config.Pindleskin.KillNihlathak = true; - Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. - Scripts.Nihlathak = false; - Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. - Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal - Scripts.Eldritch = false; - Config.Eldritch.OpenChest = true; - Config.Eldritch.KillShenk = true; - Config.Eldritch.KillDacFarren = true; - Scripts.Eyeback = false; - Scripts.SharpTooth = false; - Scripts.ThreshSocket = false; - Scripts.Abaddon = false; - Scripts.Frozenstein = false; - Config.Frozenstein.ClearFrozenRiver = true; - Scripts.Bonesaw = false; - Config.Bonesaw.ClearDrifterCavern = false; - Scripts.Snapchip = false; - Config.Snapchip.ClearIcyCellar = true; - Scripts.Worldstone = false; - Scripts.Baal = false; - Config.Baal.HotTPMessage = "Hot TP!"; - Config.Baal.SafeTPMessage = "Safe TP!"; - Config.Baal.BaalMessage = "Baal!"; - Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. - Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. - - // ############################ // - /* ##### LEECHING SCRIPTS ##### */ - // ############################ // - - Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. - Config.TristramLeech.Helper = false; // If set to true the character will help attack. - Scripts.TravincalLeech = false; // Enters portal at back of Travincal. - Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. - Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader - Config.Wakka.Wait = 1; // Minutes to wait for leader - Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached - Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next - Config.SkipIfBaal = true; // end script it leader is in throne of destruction - Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. - Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. - Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. - Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals - Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. - Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. - Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. - Config.DiabloHelper.OpenSeals = false; // Open seals as the helper - Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast - Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear - Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear - Scripts.AutoBaal = false; // Baal leecher with auto leader assignment - Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found - Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot - Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot - Scripts.BaalHelper = false; - Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne - Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. - Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. - Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. - - // Baal Assistant by YourGreatestMember - Scripts.BaalAssistant = false; // Used to leech or help in baal runs. - Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... - Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne - Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne - Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. - Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. - Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage - Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. - Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) - Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) - Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) - Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. - Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. - Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. - Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. - Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList - - // AutoChaos by noah- https://gist.github.com/noah-/2685fbeccc72fd595bbe89116aea272e, an anonymous Team CS script without explicit in-game communication. It requires at least 1 Sorceress, 1 Barbarian, and 1 Paladin (intended for Classic CS) - Scripts.AutoChaos = false; - Config.AutoChaos.Taxi = false; - Config.AutoChaos.FindShrine = false; // set true to search for shrine only - Config.AutoChaos.Glitcher = false; // set true for low level EXP glitcher (unimplemented) - Config.AutoChaos.SealOrder = [1, 2, 3]; // order in which the taxi will go through cs, 1: vizier, 2: seis, 3: infector - Config.AutoChaos.PreAttack = [0, 0, 0]; // preattack count at each seal, useful for clearing tp's for safer entry, enter values in the following order: [/vizier/, /seis/, /infector/] - Config.AutoChaos.Diablo = 0; // -1 = go to town during diablo, 0 = kill to death, x > 0 = kill to x% - Config.AutoChaos.UseShrine = false; // true = get shrine from act 1 (requires another character running FindShrine) - Config.AutoChaos.Leech = false; // true = hide during diablo, false = stay at star - Config.AutoChaos.Ranged = false; // true = ranged character, false = melee character - Config.AutoChaos.BO = false; // true = don't enter seals after boing at river, false = normal character that fights - Config.AutoChaos.SealPrecast = false; // true = does precast sequence at every seal, false = does not precast at seal - Config.AutoChaos.SealDelay = 0; // number of seconds to wait before entering hot tp - - // ########################### // - /* ##### SPECIAL SCRIPTS ##### */ - // ########################### // - - // ##### ONCE SCRIPTS ##### // - Scripts.WPGetter = false; // Get missing waypoints - Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) - Config.Questing.StopProfile = false; // set to true to shut down profile after completion - - // ##### CONTROL SCRIPTS ##### // - Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js - Scripts.ControlBot = false; - Config.ControlBot.Bo = true; // Bo player at waypoint - Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can - Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. - Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command - Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions - Config.ControlBot.Wps.GiveWps = true; // Give wps on command - Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal - Config.ControlBot.EndMessage = ""; // Message before quitting - Config.ControlBot.GameLength = 20; // Game length in minutes - - // ##### ORG/TORCH ##### // - Scripts.GetKeys = false; // Hunt for T/H/D keys - Scripts.OrgTorch = false; - Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches - Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info - Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on - Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) - Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. - Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area - Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes - Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area - Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes - - // ##### AUTO-RUSH ##### // - // RUSHER USES FOLLOWER ENTRY SCRIPT - Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js - Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). - Config.Rusher.Cain = false; // Do cain quest. - Config.Rusher.Radament = false; // Do Radament quest. - Config.Rusher.LamEsen = false; // Do Lam Esen quest. - Config.Rusher.Izual = false; // Do Izual quest. - Config.Rusher.Shenk = false; // Do Shenk quest. - Config.Rusher.Anya = false; // Do Anya quest. - Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) - Config.Rusher.GiveWps = false; // Give all Wps - Config.Rusher.LastRun = ""; // End rush after this run. - // RUSHEE USES LEADER ENTRY SCRIPT - Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader - Config.Rushee.Quester = false; // Enter portals and get quest items. - Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare - - // ##### MANUAL RUSH ##### // - Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key - - // ##### MISC SCRIPTS ##### // - Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js - Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js - Scripts.IPHunter = false; - Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] - Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found - Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. - // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] - // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. - Config.ShopBot.ShopNPC = NPC.Anya; - // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt - Config.ShopBot.ScanIDs = []; - Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. - Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. - - // ##### EXTRA SCRIPTS ##### // - Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) - Scripts.ChestMania = false; // Open chests in configured areas. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - // List of act 1 areas to open chests in - Config.ChestMania.Act1 = [ - sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum - ]; - // List of act 2 areas to open chests in - Config.ChestMania.Act2 = [ - sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, - sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 - ]; - // List of act 3 areas to open chests in - Config.ChestMania.Act3 = [ - sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, - sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 - ]; - // List of act 4 areas to open chests in - Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; - // List of act 5 areas to open chests in - Config.ChestMania.Act5 = [ - sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit - ]; - Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. - Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/areas.txt - - Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked - // List of are ids to hunt in. See sdk/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js - Config.GemHunter.AreaList = [ - sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, - sdk.areas.BlackMarsh, sdk.areas.TamoeHighland - ]; - // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types - Config.GemHunter.GemList = [ - sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, - sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull - ]; - - // ############################ // - /* #### CHARACTER SETTINGS #### */ - // ############################ // - - // If Config.Leader is set, the bot will only accept invites from leader. - // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.AutoMap = false; // Set to true to open automap at the beginning of the game. - Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact - Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. - Config.MaxGameTime = 0; // Maximum game time in seconds. Quit game when limit is reached. - Config.LogExperience = false; // Print experience statistics in the manager. - - // Chicken settings - Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - Config.TownCheck = false; // Go to town if out of potions - Config.StashGold = 100000; // Minimum amount of gold to stash. - Config.MiniShopBot = true; // Scan items in NPC shops. - Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. - Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. - Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. - - // Item identification settings - Config.CainID.Enable = false; // Identify items at Cain - Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. - Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. - Config.FieldID.Enabled = false; // Identify items while in the field - Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) - Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked - Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs - Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See NTItemAlias.dbl for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "hp", "mp", "rv"]; - - /* Minimum amount of potions from left to right. - * If we have less, go to vendor to purchase more. - * Set rejuvenation columns to 0, because they can't be bought. - */ - Config.MinColumn = [3, 3, 3, 0]; - - // ############################ // - /* #### INVENTORY SETTINGS #### */ - // ############################ // - /* - * Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - // ########################### // - /* ##### PICKIT SETTINGS ##### */ - // ########################### // - // Default folder is kolbot/pickit. - // Item name and classids located in NTItemAlias.dbl or modules/sdk.js - - //Config.PickitFiles.push("kolton.nip"); - //Config.PickitFiles.push("LLD.nip"); - Config.PickRange = 40; // Pick radius - Config.FastPick = false; // Check and pick items between attacks - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.OpenChests.Range = 15; // radius to scan for chests while pathing - Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/chests.txt for full list of chest names - - // ########################### // - /* ##### PUBLIC SETTINGS ##### */ - // ########################### // - - // ##### CHAT SETTINGS ##### // - Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script - - // LocalChat messages will only be visible on clients running on the same PC - // Highly recommened for online play - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // DClone config - Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth - Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled - Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. - Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // always attempt to kill these bosses despite immunities and mods - Config.SkipException = []; - // vizier, de seis, infector - // Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; - - // ########################### // - /* ##### ATTACK SETTINGS ##### */ - // ########################### // - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. - * GOOD: Config.AttackSkill[1] = 151; - * GOOD: Config.AttackSkill[1] = sdk.skills.Whirlwind; - * BAD: Config.AttackSkill[1] = -151; - * BAD: Config.AttackSkill[1] = "Whirlwind"; - */ - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Example: "Baal": [38, -1] to use charged bolt on Baal - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - // Weapon slot settings - Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // ############################ // - /* ###### CLEAR SETTINGS ###### */ - // ############################ // - - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.ClearPath = { - Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - Range: 30, // Range to clear while traveling - Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - }; - - // ############################ // - /* ###### CLASS SETTINGS ###### */ - // ############################ // - - /* ### AMAZON ### */ - Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill. - Config.UseInnerSight = true; // Use inner sight as a precast - Config.UseSlowMissiles = true; // Use slow missiles as a precast - Config.UseDecoy = true; // Use decoy with merc stomp - Config.SummonValkyrie = true; // Summon Valkyrie - - /* ### ASSASSIN ### */ - Config.UseTraps = true; // Set to true to use traps - Config.Traps = [271, 271, 271, 276, 276]; // Skill IDs for traps to be cast on all mosters except act bosses. - Config.BossTraps = [271, 271, 271, 271, 271]; // Skill IDs for traps to be cast on act bosses. - Config.SummonShadow = "Master"; // 0 = don't summon, 1 or "Warrior" = summon Shadow Warrior, 2 or "Master" = summon Shadow Master - Config.UseFade = true; // Set to true to use Fade prebuff. - Config.UseBoS = false; // Set to true to use Burst of Speed prebuff. TODO: Casting in town + UseFade compatibility - Config.UseVenom = false; // Set to true to use Venom prebuff. Set to false if you don't have the skill and have Arachnid Mesh - it will cause connection drop otherwise. - Config.UseBladeShield = false; // Set to true to use blade shield armor - Config.UseCloakofShadows = true; // Set to true to use Cloak of Shadows while fighting. Useful for blinding regular monsters/minions. - Config.AggressiveCloak = false; // Move into Cloak range or cast if already close - - /* ### BARBARIAN ### */ - Config.FindItem = false; // Use Find Item skill on corpses after clearing. - Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills - Config.UseWarcries = true; // use battle orders, battle command, and shout if we have them - - /* ### DRUID ### */ - Config.SummonRaven = false; - Config.SummonAnimal = "Grizzly"; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly - Config.SummonSpirit = "Oak Sage"; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs" - Config.SummonVine = "Poison Creeper"; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper" - - /* ### NECROMANCER ### */ - Config.Curse[0] = 0; // Boss curse. Use skill number or set to 0 to disable. - Config.Curse[1] = 0; // Other monsters curse. Use skill number or set to 0 to disable. - - /* Custom curses for monster - * Can use monster name or classid - * Format: Config.CustomCurse = [["monstername", skillid], [156, skillid]]; - * Optional 3rd parameter for spectype, leave blank to use on all - 0x00 Normal Monster - 0x01 Super Unique - 0x02 Champion - 0x04 Boss - 0x08 Minion - Example: Config.CustomCurse = [["HellBovine", 60], [571, 87], ["SkeletonArcher", 71, 0x00]]; - */ - Config.CustomCurse = []; - - Config.ExplodeCorpses = 0; // Explode corpses. Use skill number or 0 to disable. 74 = Corpse Explosion, 83 = Poison Explosion - Config.Golem = "None"; // Golem. 0 or "None" = don't summon, 1 or "Clay" = Clay Golem, 2 or "Blood" = Blood Golem, 3 or "Fire" = Fire Golem - Config.Skeletons = 0; // Number of skeletons to raise. Set to "max" to auto detect, set to 0 to disable. - Config.SkeletonMages = 0; // Number of skeleton mages to raise. Set to "max" to auto detect, set to 0 to disable. - Config.Revives = 0; // Number of revives to raise. Set to "max" to auto detect, set to 0 to disable. - Config.PoisonNovaDelay = 2; // Delay between two Poison Novas in seconds. - Config.ActiveSummon = false; // Raise dead between each attack. If false, it will raise after clearing a spot. - Config.ReviveUnstackable = true; // Revive monsters that can move freely after you teleport. - Config.IronGolemChicken = 30; // Exit game if Iron Golem's life is less or equal to designated percent. - - /* ### PALADIN ### */ - Config.AvoidDolls = false; // Try to attack dolls from a greater distance with hammerdins. - Config.Vigor = true; // Swith to Vigor when running - Config.Charge = true; // Use Charge when running - Config.Redemption = [50, 50]; // Switch to Redemption after clearing an area if under designated life or mana. Format: [lifepercent, manapercent] - - /* ### SORCERESS ### */ - Config.CastStatic = 60; // Cast static until the target is at designated life percent. 100 = disabled. - Config.StaticList = []; // List of monster NAMES or CLASSIDS to static. Example: Config.StaticList = ["Andariel", 243]; - Config.UseTelekinesis = true; // Use telekinesis on units that allow it. Example: Shrines, Waypoints, Chests, and Portals - Config.UseEnergyShield = false; // set to true to use energy shield if its available - Config.UseColdArmor = true; // use armor skills, uses skill ids or set to true to let the bot decide based on skill level or false to disable completely - // (40 / sdk.skills.FrozenArmor)(50 / sdk.skills.ShiverArmor)(60 / sdk.skills.ChillingArmor) - - // ########################### // - /* ##### Gamble SETTINGS ##### */ - // ########################### // - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; - - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); - - // ########################### // - /* ##### CUBING SETTINGS ##### */ - // ########################### // - /* All recipe names are available in Templates/Cubing.txt. For item names/classids check NTItemAlias.dbl - * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). Etherealness is optional and only applies to some recipes. - */ - Config.Cubing = false; // Set to true to enable cubing. - Config.ShowCubingInfo = true; // Show cubing messages on console - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Gem, "Chipped Amethyst"]); // make FlawedAmethyst - //Config.Recipes.push([Recipe.Gem, "Chipped Topaz"]); // make Flawed Topaz - //Config.Recipes.push([Recipe.Gem, "Chipped Sapphire"]); // make Flawed Sapphire - //Config.Recipes.push([Recipe.Gem, "Chipped Emerald"]); // make Flawed Emerald - //Config.Recipes.push([Recipe.Gem, "Chipped Ruby"]); // make Flawed Ruby - //Config.Recipes.push([Recipe.Gem, "Chipped Diamond"]); // make Flawed Diamond - //Config.Recipes.push([Recipe.Gem, "Chipped Skull"]); // make Flawed Skull - - //Config.Recipes.push([Recipe.Gem, "Flawed Amethyst"]); // make Amethyst - //Config.Recipes.push([Recipe.Gem, "Flawed Topaz"]); // make Topaz - //Config.Recipes.push([Recipe.Gem, "Flawed Sapphire"]); // make Sapphire - //Config.Recipes.push([Recipe.Gem, "Flawed Emerald"]); // make Emerald - //Config.Recipes.push([Recipe.Gem, "Flawed Ruby"]); // make Ruby - //Config.Recipes.push([Recipe.Gem, "Flawed Diamond"]); // make Diamond - //Config.Recipes.push([Recipe.Gem, "Flawed Skull"]); // make Skull - - Config.Recipes.push([Recipe.Gem, "Amethyst"]); // make Flawless Amethyst - Config.Recipes.push([Recipe.Gem, "Topaz"]); // make Flawless Topaz - Config.Recipes.push([Recipe.Gem, "Sapphire"]); // make Flawless Sapphire - Config.Recipes.push([Recipe.Gem, "Emerald"]); // make Flawless Emerald - Config.Recipes.push([Recipe.Gem, "Ruby"]); // make Flawless Ruby - Config.Recipes.push([Recipe.Gem, "Diamond"]); // make Flawless Diamond - Config.Recipes.push([Recipe.Gem, "Skull"]); // make Flawless Skull - - Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // make Perfect Amethyst - Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // make Perfect Topaz - Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // make Perfect Sapphire - Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // make Perfect Emerald - Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // make Perfect Ruby - Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // make Perfect Diamond - Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // make Perfect Skull - - //Config.Recipes.push([Recipe.Token]); // Make Token of Absolution - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Rune, "El Rune"]); // Upgrade El to Eld - //Config.Recipes.push([Recipe.Rune, "Eld Rune"]); // Upgrade Eld to Tir - //Config.Recipes.push([Recipe.Rune, "Tir Rune"]); // Upgrade Tir to Nef - //Config.Recipes.push([Recipe.Rune, "Nef Rune"]); // Upgrade Nef to Eth - //Config.Recipes.push([Recipe.Rune, "Eth Rune"]); // Upgrade Eth to Ith - //Config.Recipes.push([Recipe.Rune, "Ith Rune"]); // Upgrade Ith to Tal - //Config.Recipes.push([Recipe.Rune, "Tal Rune"]); // Upgrade Tal to Ral - //Config.Recipes.push([Recipe.Rune, "Ral Rune"]); // Upgrade Ral to Ort - //Config.Recipes.push([Recipe.Rune, "Ort Rune"]); // Upgrade Ort to Thul - - //Config.Recipes.push([Recipe.Rune, "Thul Rune"]); // Upgrade Thul to Amn - //Config.Recipes.push([Recipe.Rune, "Amn Rune"]); // Upgrade Amn to Sol - //Config.Recipes.push([Recipe.Rune, "Sol Rune"]); // Upgrade Sol to Shael - //Config.Recipes.push([Recipe.Rune, "Shael Rune"]); // Upgrade Shael to Dol - //Config.Recipes.push([Recipe.Rune, "Dol Rune"]); // Upgrade Dol to Hel - //Config.Recipes.push([Recipe.Rune, "Hel Rune"]); // Upgrade Hel to Io - //Config.Recipes.push([Recipe.Rune, "Io Rune"]); // Upgrade Io to Lum - //Config.Recipes.push([Recipe.Rune, "Lum Rune"]); // Upgrade Lum to Ko - //Config.Recipes.push([Recipe.Rune, "Ko Rune"]); // Upgrade Ko to Fal - //Config.Recipes.push([Recipe.Rune, "Fal Rune"]); // Upgrade Fal to Lem - //Config.Recipes.push([Recipe.Rune, "Lem Rune"]); // Upgrade Lem to Pul - - Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Pul to Um - //Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Um to Mal - Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Mal to Ist - //Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Ist to Gul - Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Gul to Vex - - // Ingredients for the following recipes will be auto-picked, for classids check libs/NTItemAlias.dbl - - //Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Helm - //Config.Recipes.push([Recipe.Blood.Boots, "Mirrored Boots"]); // Craft Blood Boots - //Config.Recipes.push([Recipe.Blood.Gloves, "Vampirebone Gloves"]); // Craft Blood Gloves - //Config.Recipes.push([Recipe.Blood.Belt, "Mithril Coil"]); // Craft Blood Belt - //Config.Recipes.push([Recipe.Blood.Shield, "Blade Barrier"]); // Craft Blood Shield - //Config.Recipes.push([Recipe.Blood.Body, "Hellforge Plate"]); // Craft Blood Armor - //Config.Recipes.push([Recipe.Blood.Amulet]); // Craft Blood Amulet - //Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring - //Config.Recipes.push([Recipe.Blood.Weapon, "Berserker Axe"]); // Craft Blood Weapon - - //Config.Recipes.push([Recipe.Caster.Helm, "Demonhead Mask"]); // Craft Caster Helm - //Config.Recipes.push([Recipe.Caster.Boots, "Wyrmhide Boots"]); // Craft Caster Boots - //Config.Recipes.push([Recipe.Caster.Gloves, "Bramble Mitts"]); // Craft Caster Gloves - //Config.Recipes.push([Recipe.Caster.Belt, "Vampirefang Belt"]); // Craft Caster Belt - //Config.Recipes.push([Recipe.Caster.Shield, "Luna"]); // Craft Caster Shield - //Config.Recipes.push([Recipe.Caster.Body, "Archon Plate"]); // Craft Caster Armor - //Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet - //Config.Recipes.push([Recipe.Caster.Ring]); // Craft Caster Ring - //Config.Recipes.push([Recipe.Caster.Weapon, "Seraph Rod"]); // Craft Caster Weapon - - //Config.Recipes.push([Recipe.HitPower.Helm, "Giant Conch"]); // Craft Hit Power Helm - //Config.Recipes.push([Recipe.HitPower.Boots, "Boneweave Boots"]); // Craft Hit Power Boots - //Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Gloves - //Config.Recipes.push([Recipe.HitPower.Belt, "Troll Belt"]); // Craft Hit Power Belt - //Config.Recipes.push([Recipe.HitPower.Shield, "Ward"]); // Craft Hit Power Shield - //Config.Recipes.push([Recipe.HitPower.Body, "Kraken Shell"]); // Craft Hit Power Armor - //Config.Recipes.push([Recipe.HitPower.Amulet]); // Craft Hit Power Amulet - //Config.Recipes.push([Recipe.HitPower.Ring]); // Craft Hit Power Ring - //Config.Recipes.push([Recipe.HitPower.Weapon, "Scourge"]); // Craft Hit Power Weapon | "Blunt" = All maces, rods (+50% Undead), excepting orbs - - //Config.Recipes.push([Recipe.Safety.Helm, "Corona"]); // Craft Safety Helm - //Config.Recipes.push([Recipe.Safety.Boots, "Myrmidon Boots"]); // Craft Safety Boots - //Config.Recipes.push([Recipe.Safety.Gloves, "Ogre Gauntlets"]); // Craft Safety Gloves - //Config.Recipes.push([Recipe.Safety.Belt, "Spiderweb Sash"]); // Craft Safety Belt - //Config.Recipes.push([Recipe.Safety.Shield, "Monarch"]); // Craft Safety Shield - //Config.Recipes.push([Recipe.Safety.Body, "Great Hauberk"]); // Craft Safety Armor - //Config.Recipes.push([Recipe.Safety.Amulet]); // Craft Safety Amulet - //Config.Recipes.push([Recipe.Safety.Ring]); // Craft Safety Ring - //Config.Recipes.push([Recipe.Safety.Weapon, "Matriarchal Javelin"]); // Craft Safety Weapon - //Config.Recipes.push([Recipe.Safety.Weapon, "Matriarchal Spear"]); // Craft Safety Weapon - - // The gems not used by other recipes will be used for magic item rerolling. - - //Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem - //Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) - - // the cubing formula: 6 Perfect Skulls + 1 Rare Item = 1 random low quality rare item of the same type - //Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem - - // the cubing formula: 1 Perfect Skull + 1 Rare Item + Stone of Jordan = 1 high quality new rare item of the same type - //Config.Recipes.push([Recipe.Reroll.HighRare, "Diadem"]); // Reroll high rare Diadem - - /* Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. - * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. - */ - //Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher - //Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe - //Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor - //Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate - - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite - //Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite - - // ########################### // - /* #### RUNEWORD SETTINGS #### */ - // ########################### // - /* All recipes are available in Templates/Runewords.txt - * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them - */ - Config.MakeRunewords = true; // Set to true to enable runeword making/rerolling - - Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher - Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe - //Config.Runewords.push([Runeword.Insight, "Great Poleaxe"]); // Make Insight Great Poleaxe - //Config.Runewords.push([Runeword.Insight, "Giant Thresher"]); // Make Insight Giant Thresher - Config.Runewords.push([Runeword.Insight, "Colossus Voulge"]); // Make Insight Colossus Voulge - Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); // medium Insight - //Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17 && [enhanceddamage] >= 260 && [attackrate] >= 250"); // perfect Insight - - Config.Runewords.push([Runeword.Grief, "Phase Blade"]); // Make Grief Phase Blade - //Config.Runewords.push([Runeword.Grief, "Berserker Axe"]); // Make Grief Berserker Axe - Config.KeepRunewords.push("([type] == sword || [type] == axe) # [plusmaxdamage] >= 390"); // medium Grief - //Config.KeepRunewords.push("([type] == sword || [type] == axe) # [itemfasterattackrate] >= 40 && [plusmaxdamage] >= 400"); // perfect Grief and *optional [itempiercepois] >= 25 - - Config.Runewords.push([Runeword.CallToArms, "Crystal Sword"]); // Make CTA Crystal Sword - Config.Runewords.push([Runeword.CallToArms, "Phase Blade"]); // Make CTA Phase Blade - //Config.Runewords.push([Runeword.CallToArms, "Flail"]); // Make CTA Flail - //Config.KeepRunewords.push("[name] == crystalsword || [name] == phaseblade || [name] == flail # [plusskillbattlecommand] >= 3 && [plusskillbattleorders] >=3"); - Config.KeepRunewords.push("[name] == crystalsword || [name] == phaseblade || [name] == flail # [plusskillbattlecommand] >= 6 && [plusskillbattleorders] >=6 && [plusskillbattlecry] >= 4"); // perfect CTA and *optional [enhanceddamage] = 290% - - Config.Runewords.push([Runeword.Spirit, "Crystal Sword"]); // Make Spirit Crystal Sword - Config.Runewords.push([Runeword.Spirit, "Broad Sword"]); // Make Spirit Broad Sword - //Config.Runewords.push([Runeword.Spirit, "Battle Sword"]); // Make Spirit Battle Sword - //Config.Runewords.push([Runeword.Spirit, "Phase Blade"]); // Make Spirit Phase Blade - Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch - Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe - Config.Runewords.push([Runeword.Spirit, "Kurast Shield"]); // Make Spirit Kurast Shield - //Config.Runewords.push([Runeword.Spirit, "Vortex Shield"]); // Make Spirit Vortex Shield - Config.KeepRunewords.push("[type] == sword || [type] == shield || [type] == auricshields # [fcr] == 35"); // middle spirit - //Config.KeepRunewords.push("[type] == sword || [type] == shield || [type] == auricshields # [fcr] == 35 && [maxmana] >= 112 && [itemabsorbmagic] >=8"); // perfect spirit - - //Config.Runewords.push([Runeword.Prudence, "Sacred Armor", Roll.Eth]); // Make ethereal Prudence Sacred Armor - //Config.KeepRunewords.push("[type] == Armor # [enhanceddefense] == 170 && [fireresist] == 35"); - - // #################################### // - /* #### ADVANCED AUTOMULE SETTINGS #### */ - // #################################### // - /* - * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. - * Force - Items listed here will be muled even if they are ingredients for cubing. - * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. - * - * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. - * Example : - * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; - * This will initiate muling when your character finds Ber, Jah, or SOJ. - * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; - * This will mule perfect gems/skull during muling. - * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; - * This will exclude muling of runes from tal through sol, and any essences. - */ - Config.AutoMule.Trigger = []; - Config.AutoMule.Force = []; - Config.AutoMule.Exclude = []; - - // ############################### // - /* #### ITEM LOGGING SETTINGS #### */ - // ############################### // - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - - // ######################################## // - /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ - // ######################################## // - /* - * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + // User addon script. Read the description in libs/scripts/UserAddon.js + Scripts.UserAddon = false; // !!!YOU MUST SET THIS TO FALSE IF YOU WANT TO RUN BOSS/AREA SCRIPTS!!! + + // Battle orders script - Use this for 2+ characters + Scripts.BattleOrders = false; + Config.BattleOrders.Mode = 0; // 0 = give BO, 1 = get BO + Config.BattleOrders.Idle = false; // Idle until the player that received BO leaves. + Config.BattleOrders.Getters = []; // List of players to wait for before casting Battle Orders (mode 0). All players must be in the same area as the BOer. + Config.BattleOrders.QuitOnFailure = false; // Quit the game if BO fails + Config.BattleOrders.SkipIfTardy = true; // Proceed with scripts if other players already moved on from BO spot + Config.BattleOrders.Wait = 10; // Duration to wait for players to join game in seconds (default: 10) + + Scripts.BoBarbHelper = false; // specific HC script with BoBarb on the Bo area during whole game | set it only in barbarian config + Config.BoBarbHelper.Mode = -1; // 0 = give BO, -1 = disabled + Config.BoBarbHelper.Wp = 35; // 35 = Catacombs level 2 + + Scripts.GetFade = false; // Get fade in River of Flames - only works if we are wearing an item with ctc Fade + + // ## Team MF system + Config.MFLeader = false; // Set to true if you have one or more MFHelpers. Opens TP and gives commands when doing normal MF runs. + Scripts.MFHelper = false; // Run the same MF run as the MFLeader. Leader must have Config.MFLeader = true + Config.BreakClearLevel = false; // Stop clearing the current area if the leader goes to another area + + // ############################# // + /* ##### LEECHING SETTINGS ##### */ + // ############################# // + + // leader's character name isn't needed on order to run. Don't use more scripts of the same type! (Run AutoBaal OR BaalHelper, not both) + Config.Leader = ""; // Leader's ingame character name. Leave blank to try auto-detection (works in AutoBaal, Wakka, MFHelper) + Config.QuitList = [""]; // List of character names to quit with. Example: Config.QuitList = ["MySorc", "MyDin"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = []; // Quit the game with random delay in case of using Config.QuitList. Example: Config.QuitListDelay = [1, 10]; will exit with random delay between 1 and 10 seconds. + + // ############################# // + /* ##### BOSS/AREA SCRIPTS ##### */ + // ############################# // + + // *** act 1 *** + Scripts.Corpsefire = false; + Config.Corpsefire.ClearDen = false; + Scripts.Bishibosh = false; + Scripts.Mausoleum = false; + Config.Mausoleum.KillBishibosh = false; + Config.Mausoleum.KillBloodRaven = false; + Config.Mausoleum.ClearCrypt = false; + Scripts.Rakanishu = false; + Config.Rakanishu.KillGriswold = true; + Scripts.UndergroundPassage = false; + Scripts.Coldcrow = false; + Scripts.Tristram = false; + Config.Tristram.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Tristram.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Pit = false; + Config.Pit.ClearPit1 = true; + Scripts.Treehead = false; + Scripts.Smith = false; + Scripts.BoneAsh = false; + Scripts.Countess = false; + Config.Countess.KillGhosts = false; + Scripts.Andariel = false; + Scripts.Cows = false; + + // *** act 2 *** + Scripts.Radament = false; + Scripts.CreepingFeature = false; + Scripts.Coldworm = false; + Config.Coldworm.KillBeetleburst = false; + Config.Coldworm.ClearMaggotLair = false; // Clear all 3 levels + Scripts.AncientTunnels = false; + Config.AncientTunnels.OpenChest = false; // Open special chest in Lost City + Config.AncientTunnels.KillDarkElder = false; + Scripts.Summoner = false; + Config.Summoner.FireEye = false; + Scripts.Tombs = false; + Config.Tombs.KillDuriel = false; + Scripts.Duriel = false; + + // *** act 3 *** + Scripts.Stormtree = false; + Scripts.BattlemaidSarina = false; + Scripts.KurastTemples = false; + Scripts.Icehawk = false; + Scripts.Endugu = false; + Scripts.Travincal = false; + Config.Travincal.PortalLeech = false; // Set to true to open a portal for leechers. + Scripts.Mephisto = false; + Config.Mephisto.MoatTrick = false; + Config.Mephisto.KillCouncil = false; + Config.Mephisto.TakeRedPortal = true; + + // *** act 4 *** + Scripts.OuterSteppes = false; + Scripts.Izual = false; + Scripts.Hephasto = false; + Scripts.Diablo = false; + Config.Diablo.ClearRadius = 30; // Range cleared while following path to seals + Config.Diablo.WalkClear = false; // Disable teleport while clearing to protect leechers + Config.Diablo.Entrance = true; // Start from entrance + Config.Diablo.JustViz = false; // Intended for classic sorc, kills Vizier only. + Config.Diablo.SealLeader = false; // Clear a safe spot around seals and invite leechers in. Leechers should run SealLeecher script. + Config.Diablo.Fast = false; // Runs diablo fast, focuses on clearing seal bosses rather than clearing path + Config.Diablo.SealWarning = "Leave the seals alone!"; + Config.Diablo.EntranceTP = "Entrance TP up"; + Config.Diablo.StarTP = "Star TP up"; + Config.Diablo.DiabloMsg = "Diablo"; + Config.Diablo.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + + // *** act 5 *** + Scripts.Pindleskin = false; + Config.Pindleskin.UseWaypoint = false; + Config.Pindleskin.KillNihlathak = true; + Config.Pindleskin.ViperQuit = false; // End script if Tomb Vipers are found. + Scripts.Nihlathak = false; + Config.Nihlathak.ViperQuit = false; // End script if Tomb Vipers are found. + Config.Nihlathak.UseWaypoint = false; // Use waypoint to Nith, if false uses anya portal + Scripts.Eldritch = false; + Config.Eldritch.OpenChest = true; + Config.Eldritch.KillShenk = true; + Config.Eldritch.KillDacFarren = true; + Scripts.Eyeback = false; + Scripts.SharpTooth = false; + Scripts.ThreshSocket = false; + Scripts.Abaddon = false; + Scripts.Frozenstein = false; + Config.Frozenstein.ClearFrozenRiver = true; + Scripts.Bonesaw = false; + Config.Bonesaw.ClearDrifterCavern = false; + Scripts.Snapchip = false; + Config.Snapchip.ClearIcyCellar = true; + Scripts.Worldstone = false; + Scripts.Baal = false; + Config.Baal.HotTPMessage = "Hot TP!"; + Config.Baal.SafeTPMessage = "Safe TP!"; + Config.Baal.BaalMessage = "Baal!"; + Config.Baal.SoulQuit = false; // End script if Souls (Burning Souls) are found. + Config.Baal.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.Baal.KillBaal = true; // Kill Baal. Leaves game after wave 5 if false. + + // ############################ // + /* ##### LEECHING SCRIPTS ##### */ + // ############################ // + + Scripts.TristramLeech = false; // Enters Tristram, attempts to stay close to the leader and will try and help kill. + Config.TristramLeech.Helper = false; // If set to true the character will help attack. + Scripts.TravincalLeech = false; // Enters portal at back of Travincal. + Config.TravincalLeech.Helper = true; // If set to true the character will teleport to the stairs and help attack. + Scripts.Wakka = false; // Walking chaos leecher with auto leader assignment, stays at safe distance from the leader + Config.Wakka.Wait = 1; // Minutes to wait for leader + Config.Wakka.StopAtLevel = 99; // Stop wakka when this level is reached + Config.Wakka.StopProfile = false; // when StopAtLevel is reached, set to true to stop the profile, false to end script and move on to next + Config.SkipIfBaal = true; // end script it leader is in throne of destruction + Scripts.SealLeecher = false; // Enter safe portals to Chaos. Leader should run SealLeader. + Scripts.DiabloHelper = false; // Chaos helper, kills monsters and doesn't open seals on its own. + Config.DiabloHelper.Wait = 5; // minutes to wait for a runner to be in Chaos. If Config.Leader is set, it will wait only for the leader. + Config.DiabloHelper.ClearRadius = 30; // Range cleared while following path to seals + Config.DiabloHelper.Entrance = true; // Start from entrance. Set to false to start from star. + Config.DiabloHelper.SkipTP = false; // Don't wait for town portal and directly head to chaos. It will clear monsters around chaos entrance and wait for the runner. + Config.DiabloHelper.SkipIfBaal = false; // End script if there are party members in a Baal run. + Config.DiabloHelper.OpenSeals = false; // Open seals as the helper + Config.DiabloHelper.SafePrecast = true; // take random WP to safely precast + Config.DiabloHelper.SealOrder = ["vizier", "seis", "infector"]; // the order in which to clear the seals. If seals are excluded, they won't be checked unless diablo fails to appear + Config.DiabloHelper.RecheckSeals = false; // Teleport to each seal and double-check that it was opened and boss was killed if Diablo doesn't appear + Scripts.AutoBaal = false; // Baal leecher with auto leader assignment + Config.AutoBaal.FindShrine = false; // false = disabled, 1 = search after hot tp message, 2 = search as soon as leader is found + Config.AutoBaal.LeechSpot = [15115, 5050]; // X, Y coords of Throne Room leech spot + Config.AutoBaal.LongRangeSupport = false; // Cast long distance skills from a safe spot + Scripts.BaalHelper = false; + Config.BaalHelper.Wait = 5; // minutes to wait for a runner to be in Throne + Config.BaalHelper.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalHelper.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.KillBaal = true; // Kill Baal. If set to false, you must configure Config.QuitList or the bot will wait indefinitely. + Config.BaalHelper.SkipTP = false; // Don't wait for a TP, go to WSK3 and wait for someone to go to throne. Anti PK measure. + + // Baal Assistant by YourGreatestMember + Scripts.BaalAssistant = false; // Used to leech or help in baal runs. + Config.BaalAssistant.Wait = 120; // Seconds to wait for a runner to be in the throne / portal wait / safe TP wait / hot TP wait... + Config.BaalAssistant.KillNihlathak = false; // Kill Nihlathak before going to Throne + Config.BaalAssistant.FastChaos = false; // Kill Diablo before going to Throne + Config.BaalAssistant.Helper = true; // Set to true to help attack, set false to to leech. + Config.BaalAssistant.GetShrine = false; // Set to true to get a experience shrine at the start of the run. + Config.BaalAssistant.GetShrineWaitForHotTP = false; // Set to true to get a experience shrine after leader shouts the hot tp message as defined in Config.BaalAssistant.HotTPMessage + Config.BaalAssistant.SkipTP = false; // Set to true to enable the helper to skip the TP and teleport down to the throne room. + Config.BaalAssistant.WaitForSafeTP = false; // Set to true to wait for a safe TP message (defined in SafeTPMessage) + Config.BaalAssistant.DollQuit = false; // Quit on dolls. (Hardcore players?) + Config.BaalAssistant.SoulQuit = false; // Quit on Souls. (Hardcore players?) + Config.BaalAssistant.KillBaal = true; // Set to true to kill baal, if you set to false you MUST configure Config.QuitList or Config.BaalAssistant.NextGameMessage or the bot will wait indefinitely. + Config.BaalAssistant.HotTPMessage = ["Hot"]; // Configure safe TP messages. + Config.BaalAssistant.SafeTPMessage = ["Safe", "Clear"]; // Configure safe TP messages. + Config.BaalAssistant.BaalMessage = ["Baal"]; // Configure baal messages, this is a precautionary measure. + Config.BaalAssistant.NextGameMessage = ["Next Game", "Next", "New Game"]; // Next Game message, this is a precautionary quit command, Reccomended setting up: Config.QuitList + + // AutoChaos by noah- https://gist.github.com/noah-/2685fbeccc72fd595bbe89116aea272e, an anonymous Team CS script without explicit in-game communication. It requires at least 1 Sorceress, 1 Barbarian, and 1 Paladin (intended for Classic CS) + Scripts.AutoChaos = false; + Config.AutoChaos.Taxi = false; + Config.AutoChaos.FindShrine = false; // set true to search for shrine only + Config.AutoChaos.Glitcher = false; // set true for low level EXP glitcher (unimplemented) + Config.AutoChaos.SealOrder = [1, 2, 3]; // order in which the taxi will go through cs, 1: vizier, 2: seis, 3: infector + Config.AutoChaos.PreAttack = [0, 0, 0]; // preattack count at each seal, useful for clearing tp's for safer entry, enter values in the following order: [/vizier/, /seis/, /infector/] + Config.AutoChaos.Diablo = 0; // -1 = go to town during diablo, 0 = kill to death, x > 0 = kill to x% + Config.AutoChaos.UseShrine = false; // true = get shrine from act 1 (requires another character running FindShrine) + Config.AutoChaos.Leech = false; // true = hide during diablo, false = stay at star + Config.AutoChaos.Ranged = false; // true = ranged character, false = melee character + Config.AutoChaos.BO = false; // true = don't enter seals after boing at river, false = normal character that fights + Config.AutoChaos.SealPrecast = false; // true = does precast sequence at every seal, false = does not precast at seal + Config.AutoChaos.SealDelay = 0; // number of seconds to wait before entering hot tp + + // ########################### // + /* ##### SPECIAL SCRIPTS ##### */ + // ########################### // + + // ##### ONCE SCRIPTS ##### // + Scripts.WPGetter = false; // Get missing waypoints + Scripts.Questing = false; // Finish missing quests (skill/stat+shenk+ancients) + Config.Questing.StopProfile = false; // set to true to shut down profile after completion + + // ##### CONTROL SCRIPTS ##### // + Scripts.Follower = false; // Script that follows a manually played leader around like a merc. For a list of commands, see Follower.js + Scripts.ControlBot = false; + Config.ControlBot.Bo = true; // Bo player at waypoint + Config.ControlBot.DropGold = true; // Drop 5k gold on command once per player per game + Config.ControlBot.Cows.MakeCows = true; // allow making cows if we can + Config.ControlBot.Cows.GetLeg = true; // Get Wirt's Leg from Tristram. If set to false, it will check for the leg in town. + Config.ControlBot.Chant.Enchant = true; // enchant player and their minions on command + Config.ControlBot.Chant.AutoEnchant = true; // Automatically enchant nearby players and their minions + Config.ControlBot.Wps.GiveWps = true; // Give wps on command + Config.ControlBot.Wps.SecurePortal = true; // Secure wp before making portal + Config.ControlBot.Rush.Andy = true; // Kill Andy on command + Config.ControlBot.Rush.Bloodraven = true; // Kill Bloodraven on command + Config.ControlBot.Rush.Smith = true; // Kill Smith on command + Config.ControlBot.Rush.Cube = true; // Get cube on command + Config.ControlBot.Rush.Radament = true; // Kill Radament on command + Config.ControlBot.Rush.Staff = true; // Get staff on command + Config.ControlBot.Rush.Amulet = true; // Get amulet on command + Config.ControlBot.Rush.Summoner = true; // Kill Summoner on command + Config.ControlBot.Rush.Duriel = true; // Kill Duriel on command + Config.ControlBot.Rush.Gidbinn = true; // Clear Gidbinn altar on command + Config.ControlBot.Rush.LamEsen = true; // Get LamEsen's tome on command + Config.ControlBot.Rush.Eye = true; // Get Khalim's eye on command + Config.ControlBot.Rush.Heart = true; // Get Khalim's heart on command + Config.ControlBot.Rush.Brain = true; // Get Khalim's brain on command + Config.ControlBot.Rush.Travincal = true; // Kill Travincal on command + Config.ControlBot.Rush.Mephisto = true; // Kill Mephisto on command + Config.ControlBot.Rush.Izual = true; // Kill Izual on command + Config.ControlBot.Rush.Diablo = true; // Kill Diablo on command + Config.ControlBot.Rush.Shenk = true; // Kill Shenk on command + Config.ControlBot.Rush.Anya = true; // Rescue Anya on command + Config.ControlBot.Rush.Ancients = true; // Kill Ancients on command + Config.ControlBot.Rush.Baal = true; // Kill Baal on command + Config.ControlBot.EndMessage = ""; // Message before quitting + Config.ControlBot.GameLength = 20; // Game length in minutes + + // ##### ORG/TORCH ##### // + Scripts.GetKeys = false; // Hunt for T/H/D keys + Scripts.OrgTorch = false; + Config.OrgTorch.MakeTorch = true; // Convert organ sets to torches + Config.OrgTorch.WaitForKeys = true; // Enable Torch System to get keys from other profiles. See libs/TorchSystem.js for more info + Config.OrgTorch.WaitTimeout = 15; // Time in minutes to wait for keys before moving on + Config.OrgTorch.UseSalvation = true; // Use Salvation aura on Mephisto (if possible) + Config.OrgTorch.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + Config.OrgTorch.PreGame.Antidote.At = [sdk.areas.MatronsDen, sdk.areas.UberTristram]; // Chug x antidotes before each area + Config.OrgTorch.PreGame.Antidote.Drink = 10; // Chug x antidotes. Each antidote gives +50 poison res and +10 max poison for 30 seconds. The duration stacks. 10 potions == 5 minutes + Config.OrgTorch.PreGame.Thawing.At = [sdk.areas.FurnaceofPain, sdk.areas.UberTristram]; // Chug x thawing pots before each area + Config.OrgTorch.PreGame.Thawing.Drink = 10; // Chug x thawing pots. Each thawing pot gives +50 cold res and +10 max cold for 30 seconds. The duration stacks. 10 potions == 5 minutes + + // ##### AUTO-RUSH ##### // + // RUSHER USES FOLLOWER ENTRY SCRIPT + Scripts.Rusher = false; // Rush bot. For a list of commands, see Rusher.js + Config.Rusher.WaitPlayerCount = 0; // Wait until game has a certain number of players (0 - don't wait, 8 - wait for full game). + Config.Rusher.Cain = false; // Do cain quest. + Config.Rusher.Radament = false; // Do Radament quest. + Config.Rusher.LamEsen = false; // Do Lam Esen quest. + Config.Rusher.Izual = false; // Do Izual quest. + Config.Rusher.Shenk = false; // Do Shenk quest. + Config.Rusher.Anya = false; // Do Anya quest. + Config.Rusher.HellAncients = false; // Does Ancient's quest in hell (only if quester is level 60+) + Config.Rusher.GiveWps = false; // Give all Wps + Config.Rusher.LastRun = ""; // End rush after this run. + // RUSHEE USES LEADER ENTRY SCRIPT + Scripts.Rushee = false; // Automatic rushee, works with Rusher. Set Rusher's character name as Config.Leader + Config.Rushee.Quester = false; // Enter portals and get quest items. + Config.Rushee.Bumper = false; // Do Ancients and Baal. Minimum levels: 20 - norm, 40 - nightmare + + // ##### MANUAL RUSH ##### // + Scripts.CrushTele = false; // classic rush teleporter. go to area of interest and press "-" numpad key + + // ##### MISC SCRIPTS ##### // + Scripts.Gamble = false; // Gambling system, other characters will mule gold into your game so you can gamble infinitely. See Gambling.js + Scripts.Crafting = false; // Crafting system, other characters will mule crafting ingredients. See CraftingSystem.js + Scripts.IPHunter = false; + Config.IPHunter.IPList = []; // List of IPs to look for. example: [165, 201, 64] + Config.IPHunter.GameLength = 3; // Number of minutes to stay in game if ip wasn't found + Scripts.ShopBot = false; // Shopbot script. Automatically uses shopbot.nip and ignores other pickits. + // Supported NPCs: Akara, Charsi, Gheed, Elzix, Fara, Drognan, Ormus, Asheara, Hratli, Jamella, Halbu, Anya. Multiple NPCs are also supported, example: [NPC.Elzix, NPC.Fara] + // Use common sense when combining NPCs. Shopping in different acts will probably lead to bugs. + Config.ShopBot.ShopNPC = NPC.Anya; + // Put item classid numbers or names to scan (remember to put quotes around names). Leave blank to scan ALL items. See libs/config/templates/ShopBot.txt + Config.ShopBot.ScanIDs = []; + Config.ShopBot.CycleDelay = 0; // Delay between shopping cycles in milliseconds, might help with crashes. + Config.ShopBot.QuitOnMatch = false; // Leave game as soon as an item is shopped. + + // ##### EXTRA SCRIPTS ##### // + Scripts.GhostBusters = false; // Kill ghosts in most areas that contain them (rune hunting) + Scripts.ChestMania = false; // Open chests in configured areas. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + // List of act 1 areas to open chests in + Config.ChestMania.Act1 = [ + sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, + sdk.areas.HoleLvl2, sdk.areas.PitLvl2, sdk.areas.Crypt, sdk.areas.Mausoleum + ]; + // List of act 2 areas to open chests in + Config.ChestMania.Act2 = [ + sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, sdk.areas.AncientTunnels, + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7 + ]; + // List of act 3 areas to open chests in + Config.ChestMania.Act3 = [ + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, + sdk.areas.SpiderCave, sdk.areas.SpiderCavern, sdk.areas.SwampyPitLvl3 + ]; + // List of act 4 areas to open chests in + Config.ChestMania.Act4 = [sdk.areas.RiverofFlame]; + // List of act 5 areas to open chests in + Config.ChestMania.Act5 = [ + sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + Scripts.ClearAnyArea = false; // Clear any area. Uses Config.ClearType to determine which type of monsters to kill. + Config.ClearAnyArea.AreaList = []; // List of area ids to clear. See sdk/txt/areas.txt + Scripts.GetEssences = false; // Hunt for Essences. Useful for cubing tokens without running all the bosses. + Config.GetEssences.RunDuriel = false; // Run duriel for extra chance at TwistedEssenceofSuffering + Config.GetEssences.MoatMeph = true; // Lure Meph and attempt killing from other side of moat + Config.GetEssences.FastDiablo = true; // Runs diablo seals without clearing path + Scripts.GemHunter = false; // Hunt for Gem Shrines. add the upgraded gems to your pickit. Upgraded version of gems will be auto-picked + // List of are ids to hunt in. See sdk/txt/areas.txt or use sdk.areas.AreaName see -> \kolbot\libs\modules\sdk.js + Config.GemHunter.AreaList = [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland + ]; + // Priority List for Gems to keep in inventory. highest priority first. see \kolbot\libs\modules\sdk.js for gem types + Config.GemHunter.GemList = [ + sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Amethyst, + sdk.items.gems.Flawless.Sapphire, sdk.items.gems.Flawless.Topaz, + sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull + ]; + + // ############################ // + /* #### CHARACTER SETTINGS #### */ + // ############################ // + + // If Config.Leader is set, the bot will only accept invites from leader. + // If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.AutoMap = false; // Set to true to open automap at the beginning of the game. + Config.WaypointMenu = true; // open waypoint menu, if set to false will use packets to interact + Config.MinGameTime = 60; // Min game time in seconds. Bot will TP to town and stay in game if the run is completed before. + Config.MaxGameTime = 0; // Maximum game time in minutes. Quit game when limit is reached. + Config.LogExperience = false; // Print experience statistics in the manager. + + // Chicken settings + Config.LifeChicken = 30; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + Config.TownCheck = false; // Go to town if out of potions + Config.StashGold = 100000; // Minimum amount of gold to stash. + Config.MiniShopBot = true; // Scan items in NPC shops. + Config.PacketShopping = false; // Use packets to shop. Improves shopping speed. + Config.CubeRepair = false; // Repair weapons with Ort and armor with Ral rune. Don't use it if you don't understand the risk of losing items. + Config.RepairPercent = 40; // Durability percent of any equipped item that will trigger repairs. + + // Item identification settings + Config.CainID.Enable = false; // Identify items at Cain + Config.CainID.MinGold = 2500000; // Minimum gold (stash + character) to have in order to use Cain. + Config.CainID.MinUnids = 3; // Minimum number of unid items in order to use Cain. + Config.FieldID.Enabled = false; // Identify items while in the field + Config.FieldID.PacketID = true; // use packets to speed up id process (recommended to use this) + Config.FieldID.UsedSpace = 80; // how much space has been used before trying to field id, set to 0 to id after every item picked + Config.DroppedItemsAnnounce.Enable = false; // Announce Dropped Items to in-game newbs + Config.DroppedItemsAnnounce.Quality = []; // Quality of item to announce. See core/GameData/NTItemAlias.js for values. Example: Config.DroppedItemsAnnounce.Quality = [6, 7, 8]; + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + /** + * Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "hp", "mp", "rv"]; + + /** + * Minimum amount of potions from left to right. + * If we have less, go to vendor to purchase more. + * Set rejuvenation columns to 0, because they can't be bought. + */ + Config.MinColumn = [3, 3, 3, 0]; + + // ############################ // + /* #### INVENTORY SETTINGS #### */ + // ############################ // + /* + * Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + // ########################### // + /* ##### PICKIT SETTINGS ##### */ + // ########################### // + // Default folder is kolbot/pickit. + // Item name and classids located in core/GameData/NTItemAlias.js or modules/sdk.js + + // Config.PickitFiles.push("kolton.nip"); + // Config.PickitFiles.push("LLD.nip"); + Config.PickRange = 40; // Pick radius + Config.FastPick = false; // Check and pick items between attacks + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.OpenChests.Range = 15; // radius to scan for chests while pathing + Config.OpenChests.Types = ["chest", "chest3", "armorstand", "weaponrack"]; // which chests to open, use "all" to open all chests. See sdk/txt/chests.txt for full list of chest names + + // ########################### // + /* ##### PUBLIC SETTINGS ##### */ + // ########################### // + + // ##### CHAT SETTINGS ##### // + Config.Silence = false; // Make the bot not say a word. Do not use in combination with LocalChat or MFLeader or any team script + + // LocalChat messages will only be visible on clients running on the same PC + // Highly recommened for online play + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + Config.LocalChat.Enabled = false; // use LocalChat system - sends chat locally instead of through BNET + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 1; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.RandomPrecast = false; // Anti-PK measure, only supported in Baal and BaalHelper and BaalAssisstant at the moment. + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + Config.LastMessage = ""; // Message or array of messages to say at the end of the run. Use $nextgame to say next game - "Next game: $nextgame" (works with lead entry point) + Config.AnnounceGameTimeRemaing = false; // Announce time remaing in game if MinGameTime is set and hasn't been reached + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // DClone config + Config.StopOnDClone = true; // Go to town and idle as soon as Diablo walks the Earth + Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled + Config.KillDclone = false; // Go to Palace Cellar 3 and try to kill Diablo Clone. Pointless if you already have Annihilus. + Config.DCloneQuit = false; // 1 = quit when Diablo walks, 2 = quit on soj sales, 0 = disabled + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // always attempt to kill these bosses despite immunities and mods + Config.SkipException = []; + // vizier, de seis, infector + // Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; + + // ########################### // + /* ##### ATTACK SETTINGS ##### */ + // ########################### // + + /** + * Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt or use sdk.skills.SkillName see -> \kolbot\libs\modules\sdk.js + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. + * GOOD: Config.AttackSkill[1] = 151; + * GOOD: Config.AttackSkill[1] = sdk.skills.Whirlwind; + * BAD: Config.AttackSkill[1] = -151; + * BAD: Config.AttackSkill[1] = "Whirlwind"; + */ + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /** + * ChargeCast config. + * Allows use of charged skills (experimental) + * Summons are unsupported. + * Switchcasting is supported. + */ + Config.ChargeCast.skill = -1; // Skill to use + Config.ChargeCast.spectype = 0x7; // Monster spectype to use skill on. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + + // Config.ChargeCast = { + // skill: sdk.skills.LowerResist, + // spectype: 0x7, + // }; + + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. (disables casting animation for increased d2bs stability) + + /** + * Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Example: "Baal": [38, -1] to use charged bolt on Baal + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + // "Monster Name": [-1, -1] + }; + + /** + * @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} + * Advanced Attack config. Allows custom skills to be used on custom conditions. + * Each entry in the array should be an object with a `check` function and an `attack` array. + * The `check` function determines whether the custom attack should be used on a given monster. + * The `attack` array specifies the skills to use: [timed skill id, untimed skill id]. + * + * Example: + * [ + * { + * check: function (unit) { + * return unit.getEnchant(sdk.enchant.LightningEnchanted); + * }, + * attack: [sdk.skills.Zeal, sdk.skills.Salvation] + * }, + * ] + * + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = [ + // { + // check: function (unit) { + // return unit.getEnchant(sdk.enchant.LightningEnchanted); + // }, + // attack: [sdk.skills.Zeal, sdk.skills.Salvation] + // }, + ]; + + /** + * Advanced PreAttack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [skill id, weapon slot] + * Example: "Baal": [146, 1] to use battle cry on Baal with weapon slot 1 (switches if necessary) + * Multiple entries are separated by commas + */ + Config.CustomPreAttack = { + // "Monster Name": [-1, -1] + }; + // Alternatively, you can use the sdk.monsters.MonsterName and sdk.skills.SkillName enums to avoid typos + // Config.CustomPreAttack[sdk.monsters.Baal] = [sdk.skills.BattleCry, sdk.player.slot.Secondary]; + + // Weapon slot settings + Config.PrimarySlot = -1; // primary weapon slot: -1 = disabled (will try to determine primary slot by using non-cta slot that's not empty), 0 = slot I, 1 = slot II + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // ############################ // + /* ###### CLEAR SETTINGS ###### */ + // ############################ // + + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.ClearPath = { + Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + Range: 30, // Range to clear while traveling + Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + }; + + // ############################ // + /* ###### CLASS SETTINGS ###### */ + // ############################ // + + /* ### AMAZON ### */ + Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill. + Config.UseInnerSight = true; // Use inner sight as a precast + Config.UseSlowMissiles = true; // Use slow missiles as a precast + Config.UseDecoy = true; // Use decoy with merc stomp + Config.SummonValkyrie = true; // Summon Valkyrie + + /* ### ASSASSIN ### */ + Config.UseTraps = true; // Set to true to use traps + Config.Traps = [271, 271, 271, 276, 276]; // Skill IDs for traps to be cast on all mosters except act bosses. + Config.BossTraps = [271, 271, 271, 271, 271]; // Skill IDs for traps to be cast on act bosses. + Config.SummonShadow = "Master"; // 0 = don't summon, 1 or "Warrior" = summon Shadow Warrior, 2 or "Master" = summon Shadow Master + Config.UseFade = true; // Set to true to use Fade prebuff. + Config.UseBoS = false; // Set to true to use Burst of Speed prebuff. TODO: Casting in town + UseFade compatibility + Config.UseVenom = false; // Set to true to use Venom prebuff. Set to false if you don't have the skill and have Arachnid Mesh - it will cause connection drop otherwise. + Config.UseBladeShield = false; // Set to true to use blade shield armor + Config.UseCloakofShadows = true; // Set to true to use Cloak of Shadows while fighting. Useful for blinding regular monsters/minions. + Config.AggressiveCloak = false; // Move into Cloak range or cast if already close + + /* ### BARBARIAN ### */ + Config.FindItem = false; // Use Find Item skill on corpses after clearing. + Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills + Config.UseWarcries = true; // use battle orders, battle command, and shout if we have them + + /* ### DRUID ### */ + Config.SummonRaven = false; + Config.SummonAnimal = "Grizzly"; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly + Config.SummonSpirit = "Oak Sage"; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs" + Config.SummonVine = "Poison Creeper"; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper" + + /* ### NECROMANCER ### */ + Config.Curse[0] = 0; // Boss curse. Use skill number or set to 0 to disable. + Config.Curse[1] = 0; // Other monsters curse. Use skill number or set to 0 to disable. + + /** + * Custom curses for monster + * Can use monster name or classid + * Format: Config.CustomCurse = [["monstername", skillid], [156, skillid]]; + * Optional 3rd parameter for spectype, leave blank to use on all + 0x00 Normal Monster + 0x01 Super Unique + 0x02 Champion + 0x04 Boss + 0x08 Minion + Example: Config.CustomCurse = [["HellBovine", 60], [571, 87], ["SkeletonArcher", 71, 0x00]]; + */ + Config.CustomCurse = []; + + Config.ExplodeCorpses = 0; // Explode corpses. Use skill number or 0 to disable. 74 = Corpse Explosion, 83 = Poison Explosion + Config.Golem = "None"; // Golem. 0 or "None" = don't summon, 1 or "Clay" = Clay Golem, 2 or "Blood" = Blood Golem, 3 or "Fire" = Fire Golem + Config.Skeletons = 0; // Number of skeletons to raise. Set to "max" to auto detect, set to 0 to disable. + Config.SkeletonMages = 0; // Number of skeleton mages to raise. Set to "max" to auto detect, set to 0 to disable. + Config.Revives = 0; // Number of revives to raise. Set to "max" to auto detect, set to 0 to disable. + Config.PoisonNovaDelay = 2; // Delay between two Poison Novas in seconds. + Config.ActiveSummon = false; // Raise dead between each attack. If false, it will raise after clearing a spot. + Config.ReviveUnstackable = true; // Revive monsters that can move freely after you teleport. + Config.IronGolemChicken = 30; // Exit game if Iron Golem's life is less or equal to designated percent. + + /* ### PALADIN ### */ + Config.AvoidDolls = false; // Try to attack dolls from a greater distance with hammerdins. + Config.Vigor = true; // Swith to Vigor when running + Config.RunningAura = sdk.skills.Salvation; // Aura to use when running, DO NOT use in conjunction with Config.Vigor it will be ignored + Config.Charge = true; // Use Charge when running + Config.Redemption = [50, 50]; // Switch to Redemption after clearing an area if under designated life or mana. Format: [lifepercent, manapercent] + + /* ### SORCERESS ### */ + Config.CastStatic = 60; // Cast static until the target is at designated life percent. 100 = disabled. + Config.StaticList = []; // List of monster NAMES or CLASSIDS to static. Example: Config.StaticList = ["Andariel", 243]; + Config.UseTelekinesis = true; // Use telekinesis on units that allow it. Example: Shrines, Waypoints, Chests, and Portals + Config.UseEnergyShield = false; // set to true to use energy shield if its available + Config.UseColdArmor = true; // use armor skills, uses skill ids or set to true to let the bot decide based on skill level or false to disable completely + // (40 / sdk.skills.FrozenArmor)(50 / sdk.skills.ShiverArmor)(60 / sdk.skills.ChillingArmor) + + // ########################### // + /* ##### Gamble SETTINGS ##### */ + // ########################### // + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; + + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); + + // ########################### // + /* ##### CUBING SETTINGS ##### */ + // ########################### // + /* + * All recipe names are available in Templates/Cubing.txt. For item names/classids check core/GameData/NTItemAlias.js + * The format is Config.Recipes.push([recipe_name, item_name_or_classid, etherealness]). + * Etherealness is optional and only applies to some recipes. + */ + Config.Cubing = false; // Set to true to enable cubing. + Config.ShowCubingInfo = true; // Show cubing messages on console + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Gem, "Flawed Amethyst"]); // make Flawed Amethyst + // Config.Recipes.push([Recipe.Gem, "Flawed Topaz"]); // make Flawed Topaz + // Config.Recipes.push([Recipe.Gem, "Flawed Sapphire"]); // make Flawed Sapphire + // Config.Recipes.push([Recipe.Gem, "Flawed Emerald"]); // make Flawed Emerald + // Config.Recipes.push([Recipe.Gem, "Flawed Ruby"]); // make Flawed Ruby + // Config.Recipes.push([Recipe.Gem, "Flawed Diamond"]); // make Flawed Diamond + // Config.Recipes.push([Recipe.Gem, "Flawed Skull"]); // make Flawed Skull + + // Config.Recipes.push([Recipe.Gem, "Amethyst"]); // make Amethyst + // Config.Recipes.push([Recipe.Gem, "Topaz"]); // make Topaz + // Config.Recipes.push([Recipe.Gem, "Sapphire"]); // make Sapphire + // Config.Recipes.push([Recipe.Gem, "Emerald"]); // make Emerald + // Config.Recipes.push([Recipe.Gem, "Ruby"]); // make Ruby + // Config.Recipes.push([Recipe.Gem, "Diamond"]); // make Diamond + // Config.Recipes.push([Recipe.Gem, "Skull"]); // make Skull + + // Config.Recipes.push([Recipe.Gem, "Flawless Amethyst"]); // make Flawless Amethyst + // Config.Recipes.push([Recipe.Gem, "Flawless Topaz"]); // make Flawless Topaz + // Config.Recipes.push([Recipe.Gem, "Flawless Sapphire"]); // make Flawless Sapphire + // Config.Recipes.push([Recipe.Gem, "Flawless Emerald"]); // make Flawless Emerald + // Config.Recipes.push([Recipe.Gem, "Flawless Ruby"]); // make Flawless Ruby + // Config.Recipes.push([Recipe.Gem, "Flawless Diamond"]); // make Flawless Diamond + // Config.Recipes.push([Recipe.Gem, "Flawless Skull"]); // make Flawless Skull + + // Config.Recipes.push([Recipe.Gem, "Perfect Amethyst"]); // Make Perfect Amethyst + // Config.Recipes.push([Recipe.Gem, "Perfect Topaz"]); // Make Perfect Topaz + // Config.Recipes.push([Recipe.Gem, "Perfect Sapphire"]); // Make Perfect Sapphire + // Config.Recipes.push([Recipe.Gem, "Perfect Emerald"]); // Make Perfect Emerald + // Config.Recipes.push([Recipe.Gem, "Perfect Ruby"]); // Make Perfect Ruby + // Config.Recipes.push([Recipe.Gem, "Perfect Diamond"]); // Make Perfect Diamond + // Config.Recipes.push([Recipe.Gem, "Perfect Skull"]); // Make Perfect Skull + + // Config.Recipes.push([Recipe.Token]); // Make Token of Absolution + + // Config.Recipes.push([Recipe.Rejuv]); // Make Rejuv + // Config.Recipes.push([Recipe.FullRejuv]); // Make Full Rejuv + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Rune, "Eld Rune"]); // Upgrade El to Eld + // Config.Recipes.push([Recipe.Rune, "Tir Rune"]); // Upgrade Eld to Tir + // Config.Recipes.push([Recipe.Rune, "Nef Rune"]); // Upgrade Tir to Nef + // Config.Recipes.push([Recipe.Rune, "Eth Rune"]); // Upgrade Nef to Eth + // Config.Recipes.push([Recipe.Rune, "Ith Rune"]); // Upgrade Eth to Ith + // Config.Recipes.push([Recipe.Rune, "Tal Rune"]); // Upgrade Ith to Tal + // Config.Recipes.push([Recipe.Rune, "Ral Rune"]); // Upgrade Tal to Ral + // Config.Recipes.push([Recipe.Rune, "Ort Rune"]); // Upgrade Ral to Ort + + // Config.Recipes.push([Recipe.Rune, "Thul Rune"]); // Upgrade Ort to Thul + // Config.Recipes.push([Recipe.Rune, "Amn Rune"]); // Upgrade Thul to Amn + // Config.Recipes.push([Recipe.Rune, "Sol Rune"]); // Upgrade Amn to Sol + // Config.Recipes.push([Recipe.Rune, "Shael Rune"]); // Upgrade Sol to Shael + // Config.Recipes.push([Recipe.Rune, "Dol Rune"]); // Upgrade Shael to Dol + // Config.Recipes.push([Recipe.Rune, "Hel Rune"]); // Upgrade Dol to Hel + // Config.Recipes.push([Recipe.Rune, "Io Rune"]); // Upgrade Hel to Io + // Config.Recipes.push([Recipe.Rune, "Lum Rune"]); // Upgrade Io to Lum + // Config.Recipes.push([Recipe.Rune, "Ko Rune"]); // Upgrade Lum to Ko + // Config.Recipes.push([Recipe.Rune, "Fal Rune"]); // Upgrade Ko to Fal + // Config.Recipes.push([Recipe.Rune, "Lem Rune"]); // Upgrade Fal to Lem + + // Config.Recipes.push([Recipe.Rune, "Pul Rune"]); // Upgrade Lem to Pul + // Config.Recipes.push([Recipe.Rune, "Um Rune"]); // Upgrade Pul to Um + // Config.Recipes.push([Recipe.Rune, "Mal Rune"]); // Upgrade Um to Mal + // Config.Recipes.push([Recipe.Rune, "Ist Rune"]); // Upgrade Mal to Ist + // Config.Recipes.push([Recipe.Rune, "Gul Rune"]); // Upgrade Ist to Gul + // Config.Recipes.push([Recipe.Rune, "Vex Rune"]); // Upgrade Gul to Vex + + // Ingredients for the following recipes will be auto-picked, for classids check libs/core/GameData/NTItemAlias.js + + // Config.Recipes.push([Recipe.Blood.Helm, "Armet"]); // Craft Blood Helm + // Config.Recipes.push([Recipe.Blood.Boots, "Mirrored Boots"]); // Craft Blood Boots + // Config.Recipes.push([Recipe.Blood.Gloves, "Vampirebone Gloves"]); // Craft Blood Gloves + // Config.Recipes.push([Recipe.Blood.Belt, "Mithril Coil"]); // Craft Blood Belt + // Config.Recipes.push([Recipe.Blood.Shield, "Blade Barrier"]); // Craft Blood Shield + // Config.Recipes.push([Recipe.Blood.Body, "Hellforge Plate"]); // Craft Blood Armor + // Config.Recipes.push([Recipe.Blood.Amulet]); // Craft Blood Amulet + // Config.Recipes.push([Recipe.Blood.Ring]); // Craft Blood Ring + // Config.Recipes.push([Recipe.Blood.Weapon, "Berserker Axe"]); // Craft Blood Weapon + + // Config.Recipes.push([Recipe.Caster.Helm, "Demonhead Mask"]); // Craft Caster Helm + // Config.Recipes.push([Recipe.Caster.Boots, "Wyrmhide Boots"]); // Craft Caster Boots + // Config.Recipes.push([Recipe.Caster.Gloves, "Bramble Mitts"]); // Craft Caster Gloves + // Config.Recipes.push([Recipe.Caster.Belt, "Vampirefang Belt"]); // Craft Caster Belt + // Config.Recipes.push([Recipe.Caster.Shield, "Luna"]); // Craft Caster Shield + // Config.Recipes.push([Recipe.Caster.Body, "Archon Plate"]); // Craft Caster Armor + // Config.Recipes.push([Recipe.Caster.Amulet]); // Craft Caster Amulet + // Config.Recipes.push([Recipe.Caster.Ring]); // Craft Caster Ring + // Config.Recipes.push([Recipe.Caster.Weapon, "Seraph Rod"]); // Craft Caster Weapon + + // Config.Recipes.push([Recipe.HitPower.Helm, "Giant Conch"]); // Craft Hit Power Helm + // Config.Recipes.push([Recipe.HitPower.Boots, "Boneweave Boots"]); // Craft Hit Power Boots + // Config.Recipes.push([Recipe.HitPower.Gloves, "Vambraces"]); // Craft Hit Power Gloves + // Config.Recipes.push([Recipe.HitPower.Belt, "Troll Belt"]); // Craft Hit Power Belt + // Config.Recipes.push([Recipe.HitPower.Shield, "Ward"]); // Craft Hit Power Shield + // Config.Recipes.push([Recipe.HitPower.Body, "Kraken Shell"]); // Craft Hit Power Armor + // Config.Recipes.push([Recipe.HitPower.Amulet]); // Craft Hit Power Amulet + // Config.Recipes.push([Recipe.HitPower.Ring]); // Craft Hit Power Ring + // Config.Recipes.push([Recipe.HitPower.Weapon, "Scourge"]); // Craft Hit Power Weapon | "Blunt" = All maces, rods (+50% Undead), excepting orbs + + // Config.Recipes.push([Recipe.Safety.Helm, "Corona"]); // Craft Safety Helm + // Config.Recipes.push([Recipe.Safety.Boots, "Myrmidon Boots"]); // Craft Safety Boots + // Config.Recipes.push([Recipe.Safety.Gloves, "Ogre Gauntlets"]); // Craft Safety Gloves + // Config.Recipes.push([Recipe.Safety.Belt, "Spiderweb Sash"]); // Craft Safety Belt + // Config.Recipes.push([Recipe.Safety.Shield, "Monarch"]); // Craft Safety Shield + // Config.Recipes.push([Recipe.Safety.Body, "Great Hauberk"]); // Craft Safety Armor + // Config.Recipes.push([Recipe.Safety.Amulet]); // Craft Safety Amulet + // Config.Recipes.push([Recipe.Safety.Ring]); // Craft Safety Ring + // Config.Recipes.push([Recipe.Safety.Weapon, "Matriarchal Javelin"]); // Craft Safety Weapon + // Config.Recipes.push([Recipe.Safety.Weapon, "Matriarchal Spear"]); // Craft Safety Weapon + + // The gems not used by other recipes will be used for magic item rerolling. + + // Config.Recipes.push([Recipe.Reroll.Magic, "Diadem"]); // Reroll magic Diadem (ilvl 91+) + // Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]); // Reroll magic Grand Charm (ilvl 91+) + // Config.Recipes.push([Recipe.Reroll.Charm.Small]); // Reroll magic Small Charm (ilvl 94+) + // Config.Recipes.push([Recipe.Reroll.Charm.Large]); // Reroll magic Large Charm (ilvl 76+) + // Config.Recipes.push([Recipe.Reroll.Charm.Grand]); // Reroll magic Grand Charm (ilvl 77+) + + // the cubing formula: 6 Perfect Skulls + 1 Rare Item = 1 random low quality rare item of the same type + // Config.Recipes.push([Recipe.Reroll.Rare, "Diadem"]); // Reroll rare Diadem + + // the cubing formula: 1 Perfect Skull + 1 Rare Item + Stone of Jordan = 1 high quality new rare item of the same type + // Config.Recipes.push([Recipe.Reroll.HighRare, "Diadem"]); // Reroll high rare Diadem + + /* + * Base item for the following recipes must be in pickit. The rest of the ingredients will be auto-picked. + * Use Roll.Eth, Roll.NonEth or Roll.All to determine what kind of base item to roll - ethereal, non-ethereal or all. + */ + // Config.Recipes.push([Recipe.Socket.Weapon, "Thresher", Roll.Eth]); // Socket ethereal Thresher + // Config.Recipes.push([Recipe.Socket.Weapon, "Cryptic Axe", Roll.Eth]); // Socket ethereal Cryptic Axe + // Config.Recipes.push([Recipe.Socket.Armor, "Sacred Armor", Roll.Eth]); // Socket ethereal Sacred Armor + // Config.Recipes.push([Recipe.Socket.Armor, "Archon Plate", Roll.Eth]); // Socket ethereal Archon Plate + + // Config.Recipes.push([Recipe.Socket.Magic.LowWeapon, "Bone Wand"]); // Socket magic Bone Wand (ilvl < 30) + // Config.Recipes.push([Recipe.Socket.Magic.HighWeapon, "Swirling Crystal"]); // Socket magic Swirling Crystal (ilvl >= 30) + + // Config.Recipes.push([Recipe.Socket.Rare, "Diadem"]); // Socket rare Diadem + + // Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Heavy Gloves", Roll.NonEth]); // Upgrade Bloodfist to Exceptional + // Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]); // Upgrade Magefist to Exceptional + // Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Sharkskin Gloves", Roll.NonEth]); // Upgrade Bloodfist or Grave Palm to Elite + // Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth]); // Upgrade Magefist or Lavagout to Elite + // Config.Recipes.push([Recipe.Unique.Armor.ToElite, "War Boots", Roll.NonEth]); // Upgrade Gore Rider to Elite + + // ########################### // + /* #### RUNEWORD SETTINGS #### */ + // ########################### // + /* + * All recipes are available in Templates/Runewords.txt + * Keep lines follow pickit format and any given runeword is tested vs ALL lines so you don't need to repeat them + */ + Config.MakeRunewords = true; // Set to true to enable runeword making/rerolling + + // Config.Runewords.push([Runeword.Insight, "Thresher", Roll.Eth]); // Make ethereal Insight Thresher + // Config.Runewords.push([Runeword.Insight, "Cryptic Axe", Roll.Eth]); // Make ethereal Insight Cryptic Axe + // Config.Runewords.push([Runeword.Insight, "Great Poleaxe"]); // Make Insight Great Poleaxe + // Config.Runewords.push([Runeword.Insight, "Giant Thresher"]); // Make Insight Giant Thresher + // Config.Runewords.push([Runeword.Insight, "Colossus Voulge"]); // Make Insight Colossus Voulge + // Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17"); // medium Insight + // Config.KeepRunewords.push("[type] == polearm # [meditationaura] == 17 && [enhanceddamage] >= 260 && [attackrate] >= 250"); // perfect Insight + + // Config.Runewords.push([Runeword.Grief, "Phase Blade"]); // Make Grief Phase Blade + // Config.Runewords.push([Runeword.Grief, "Berserker Axe"]); // Make Grief Berserker Axe + // Config.KeepRunewords.push("([type] == sword || [type] == axe) # [plusmaxdamage] >= 390"); // medium Grief + // Config.KeepRunewords.push("([type] == sword || [type] == axe) # [itemfasterattackrate] >= 40 && [plusmaxdamage] >= 400"); // perfect Grief and *optional [itempiercepois] >= 25 + + // Config.Runewords.push([Runeword.CallToArms, "Crystal Sword"]); // Make CTA Crystal Sword + // Config.Runewords.push([Runeword.CallToArms, "Phase Blade"]); // Make CTA Phase Blade + // Config.Runewords.push([Runeword.CallToArms, "Flail"]); // Make CTA Flail + // Config.KeepRunewords.push("[name] == crystalsword || [name] == phaseblade || [name] == flail # [plusskillbattlecommand] >= 3 && [plusskillbattleorders] >=3"); + // Config.KeepRunewords.push("[name] == crystalsword || [name] == phaseblade || [name] == flail # [plusskillbattlecommand] >= 6 && [plusskillbattleorders] >=6 && [plusskillbattlecry] >= 4"); // perfect CTA and *optional [enhanceddamage] = 290% + + // Config.Runewords.push([Runeword.Spirit, "Crystal Sword"]); // Make Spirit Crystal Sword + // Config.Runewords.push([Runeword.Spirit, "Broad Sword"]); // Make Spirit Broad Sword + // Config.Runewords.push([Runeword.Spirit, "Battle Sword"]); // Make Spirit Battle Sword + // Config.Runewords.push([Runeword.Spirit, "Phase Blade"]); // Make Spirit Phase Blade + // Config.Runewords.push([Runeword.Spirit, "Monarch", Roll.NonEth]); // Make Spirit Monarch + // Config.Runewords.push([Runeword.Spirit, "Sacred Targe", Roll.NonEth]); // Make Spirit Sacred Targe + // Config.Runewords.push([Runeword.Spirit, "Kurast Shield"]); // Make Spirit Kurast Shield + // Config.Runewords.push([Runeword.Spirit, "Vortex Shield"]); // Make Spirit Vortex Shield + // Config.KeepRunewords.push("[type] == sword || [type] == shield || [type] == auricshields # [fcr] == 35"); // middle spirit + // Config.KeepRunewords.push("[type] == sword || [type] == shield || [type] == auricshields # [fcr] == 35 && [maxmana] >= 112 && [itemabsorbmagic] >=8"); // perfect spirit + + // Config.Runewords.push([Runeword.Prudence, "Sacred Armor", Roll.Eth]); // Make ethereal Prudence Sacred Armor + // Config.KeepRunewords.push("[type] == Armor # [enhanceddefense] == 170 && [fireresist] == 35"); + + // #################################### // + /* #### ADVANCED AUTOMULE SETTINGS #### */ + // #################################### // + /* + * Trigger - Having an item that is on the list will initiate muling. Useful if you want to mule something immediately upon finding. + * Force - Items listed here will be muled even if they are ingredients for cubing. + * Exclude - Items listed here will be ignored and will not be muled. Items on Trigger or Force lists are prioritized over this list. + * + * List can either be set as string in pickit format and/or as number referring to item classids. Each entries are separated by commas. + * Example : + * Config.AutoMule.Trigger = [639, 640, "[type] == ring && [quality] == unique # [maxmana] == 20"]; + * - This will initiate muling when your character finds Ber, Jah, or SOJ. + * ADVANCED USAGE OF TRIGGER: + * Config.AutoMule.Trigger = [ + * function (item) { + * return ( + * item.classid === sdk.items.quest.KeyofTerror + * && me.getOwned({ classid: sdk.items.quest.KeyofTerror }).length === 3 + * ); + * }, + * ]; + * - This will initiate muling if the item being checked is the Key of Terror and we own 3 of them + * Config.AutoMule.Force = [561, 566, 571, 576, 581, 586, 601]; + * - This will mule perfect gems/skull during muling. + * Config.AutoMule.Exclude = ["[name] >= talrune && [name] <= solrune", "[name] >= 654 && [name] <= 657"]; + * - This will exclude muling of runes from tal through sol, and any essences. + */ + Config.AutoMule.Trigger = []; + Config.AutoMule.Force = []; + Config.AutoMule.Exclude = []; + + // ############################### // + /* #### ITEM LOGGING SETTINGS #### */ + // ############################### // + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + + // ######################################## // + /* #### AUTO BUILD/SKILL/STAT SETTINGS #### */ + // ######################################## // + /* + * AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /** + * AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; diff --git a/d2bs/kolbot/libs/config/_CustomConfig.js b/d2bs/kolbot/libs/config/_CustomConfig.js index 86de0811e..b18c66200 100644 --- a/d2bs/kolbot/libs/config/_CustomConfig.js +++ b/d2bs/kolbot/libs/config/_CustomConfig.js @@ -1,9 +1,9 @@ -var CustomConfig = { - /* Format: +const CustomConfig = { + /* Format: "Config_Filename_Without_Extension": ["array", "of", "profiles"] Multiple entries are separated by commas */ -}; \ No newline at end of file +}; diff --git a/d2bs/kolbot/libs/core/Attack.js b/d2bs/kolbot/libs/core/Attack.js new file mode 100644 index 000000000..8fb487db3 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attack.js @@ -0,0 +1,2303 @@ +/// +/** + * @filename Attack.js + * @author kolton, theBGuy + * @desc handle player attacks + * + */ + + +const Attack = { + infinity: false, + auradin: false, + monsterObjects: [ + sdk.monsters.Turret1, sdk.monsters.Turret2, + sdk.monsters.Turret3, sdk.monsters.MummyGenerator, + sdk.monsters.GargoyleTrap, sdk.monsters.LightningSpire, + sdk.monsters.FireTower, sdk.monsters.BarricadeDoor1, + sdk.monsters.BarricadeDoor2, sdk.monsters.BarricadeWall1, + sdk.monsters.BarricadeWall2, sdk.monsters.CatapultS, + sdk.monsters.CatapultE, sdk.monsters.CatapultSiege, + sdk.monsters.CatapultW, sdk.monsters.BarricadeTower, + sdk.monsters.PrisonDoor, sdk.monsters.DiablosBoneCage, + sdk.monsters.DiablosBoneCage2, sdk.monsters.Hut, + ], + Result: { + FAILED: 0, + SUCCESS: 1, + CANTATTACK: 2, // need to fix the ambiguity between this result and Failed + NEEDMANA: 3, + NOOP: 4, // used for clearing, if we didn't find any monsters to clear it's not exactly a success or fail + }, + /** + * Track bosses killed + * @type {Set} + */ + _killed: new Set(), + + /** + * @param {number | string} id + * @returns {boolean} + */ + haveKilled: function (id) { + return this._killed.has(id); + }, + + // Initialize attacks + init: function () { + if (Config.Wereform) { + include("core/Attacks/wereform.js"); + } else if (Config.CustomClassAttack && FileTools.exists("libs/core/Attacks/" + Config.CustomClassAttack + ".js")) { + console.log("Loading custom attack file"); + include("core/Attacks/" + Config.CustomClassAttack + ".js"); + } else { + include("core/Attacks/" + sdk.player.class.nameOf(me.classid) + ".js"); + } + + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { + showConsole(); + console.warn( + "ÿc1Bad attack config. Don't expect your bot to attack." + "\n" + + "ÿc0AttackSkills: ", Config.AttackSkill + ); + } + + this.getPrimarySlot(); + Skill.init(); + + if (me.expansion) { + Precast.checkCTA(); + this.checkInfinity(); + this.checkAuradin(); + } + }, + + /** + * @description check if slot has items + * @param {0 | 1} slot + * @returns {boolean} If weapon slot has an item equipped + */ + checkSlot: function (slot = me.weaponswitch) { + let item = me.getItem(-1, sdk.items.mode.Equipped); + + if (item) { + do { + if (me.weaponswitch !== slot) { + if (item.bodylocation === sdk.body.RightArmSecondary || item.bodylocation === sdk.body.LeftArmSecondary) { + return true; + } + } else { + if (item.isOnMain) { + return true; + } + } + } while (item.getNext()); + } + + return false; + }, + + /** + * @description Automatically determine primary weapon slot, Weapon slot with items that isn't a CTA + * @returns {0 | 1 | -1} Primary weapon slot + */ + getPrimarySlot: function () { + // determine primary slot if not set + if (Config.PrimarySlot === -1) { + if (me.classic) { + Config.PrimarySlot = sdk.player.slot.Main; + } else { + // Always start on main-hand + me.switchWeapons(sdk.player.slot.Main); + // have cta + if ((Precast.haveCTA > -1) || Precast.checkCTA()) { + // have item on non-cta slot - set non-cta slot as primary + if (this.checkSlot(Precast.haveCTA ^ 1)) { + Config.PrimarySlot = Precast.haveCTA ^ 1; + } else { + // other slot is empty - set cta as primary slot + Config.PrimarySlot = Precast.haveCTA; + } + } else if (!this.checkSlot(sdk.player.slot.Main) && this.checkSlot(sdk.player.slot.Secondary)) { + // only slot II has items + Config.PrimarySlot = sdk.player.slot.Secondary; + } else { + // both slots have items, both are empty, or only slot I has items + Config.PrimarySlot = sdk.player.slot.Main; + } + } + } + + return Config.PrimarySlot; + }, + + /** + * @param {Monster} unit + * @returns {[number, number] | boolean} + * @todo add checking for other options than just name/classid + * - option for based on spectype + * - option for based on enchant/aura + */ + getCustomAttack: function (unit) { + // Check if unit got invalidated + if (!unit || !unit.name || !copyUnit(unit).x) return false; + + for (let el of Config.AdvancedCustomAttack) { + if (el.hasOwnProperty("check") && el.hasOwnProperty("attack")) { + if (typeof el.check === "function" && el.check(unit)) { + return el.attack; + } + } + } + + for (let i in Config.CustomAttack) { + if (Config.CustomAttack.hasOwnProperty(i)) { + // if it contains numbers but is a string, convert to an int + if (typeof i === "string" && i.match(/\d+/g)) { + // @ts-ignore + i = parseInt(i, 10); + } + + switch (typeof i) { + case "string": + if (unit.name.toLowerCase() === i.toLowerCase()) { + return Config.CustomAttack[i]; + } + + break; + case "number": + if (unit.classid === i) { + return Config.CustomAttack[i]; + } + } + } + } + + return false; + }, + + /** + * @param {Monster} unit + * @returns {[number, number] | boolean} + * @todo add checking for other options than just name/classid + * - option for based on spectype + * - option for based on enchant/aura + */ + getCustomPreAttack: function (unit) { + // Check if unit got invalidated + if (!unit || !unit.name || !copyUnit(unit).x) return false; + + for (let i in Config.CustomPreAttack) { + if (Config.CustomPreAttack.hasOwnProperty(i)) { + // if it contains numbers but is a string, convert to an int + if (typeof i === "string" && i.match(/\d+/g)) { + // @ts-ignore + i = parseInt(i, 10); + } + + switch (typeof i) { + case "string": + if (unit.name.toLowerCase() === i.toLowerCase()) { + return Config.CustomPreAttack[i]; + } + + break; + case "number": + if (unit.classid === i) { + return Config.CustomPreAttack[i]; + } + } + } + } + + return false; + }, + + /** + * @description Check if player or his merc are using Infinity, and adjust resistance checks based on that + * @returns {boolean} + */ + checkInfinity: function () { + // don't check if classic or under 63 - not possibile to either equip or have merc use + if (me.classic || me.charlvl < 63) return false; + + // check if we have a merc and they aren't dead + if (Config.UseMerc && me.mercrevivecost === 0) { + let merc = Misc.poll(function () { + return me.getMerc(); + }, 1000, 100); + // only merc who can use it + if (merc && merc.classid === sdk.mercs.Guard) { + Attack.infinity = merc.checkItem({ name: sdk.locale.items.Infinity }).have; + if (Attack.infinity) return true; + } + } + + // Check player infinity - only check if merc doesn't have + if (!Attack.infinity) { + Attack.infinity = me.checkItem({ name: sdk.locale.items.Infinity, equipped: true }).have; + } + + return Attack.infinity; + }, + + /** + * @description Check if player is using Dragon, Dream, HoJ, or Ice, and adjust resistance checks based on that + * @returns {boolean} + */ + checkAuradin: function () { + // dragon lvl 61, dream lvl 65, hoj lvl 67, ice lvl 65 + if (me.charlvl < 61) return false; + Attack.auradin = me.haveSome([ + { name: sdk.locale.items.Dragon, equipped: true }, + { name: sdk.locale.items.Dream, equipped: true }, + { name: sdk.locale.items.HandofJustice, equipped: true }, + { name: sdk.locale.items.Ice, equipped: true }, + ]); + + return Attack.auradin; + }, + + /** + * @description check if we can telestomp a unit + * @param {Unit} unit + * @returns {boolean} + */ + canTeleStomp: function (unit) { + if (!unit || !unit.attackable) return false; + return ( + Config.TeleStomp && Config.UseMerc + && Pather.canTeleport() + && Attack.checkResist(unit, "physical") + && !!me.getMerc() + && Attack.validSpot(unit.x, unit.y) + ); + }, + + /** + * @description Kill a monster based on its classId, can pass a unit as well + * @param {Unit | number} classId + * @returns {boolean} If we managed to kill the unit + */ + kill: function (classId) { + if (!classId || Config.AttackSkill[1] < 0) return false; + let target = (typeof classId === "object" + ? classId + : Misc.poll(() => Game.getMonster(classId), 2000, 100)); + + if (!target) { + if (Attack._killed.has(classId)) { + console.log("ÿc7Killed ÿc0:: " + classId); + return true; + } + console.warn("Attack.kill: Target not found"); + return Attack.clear(10); + } + + /** + * @param {number} gid + * @param {PathNode} loc + * @returns {Unit | boolean} + */ + const findTarget = function (gid, loc) { + let path = getPath(me.area, me.x, me.y, loc.x, loc.y, 1, 5); + if (!path) return false; + + if (path.some(function (node) { + Pather.walkTo(node.x, node.y); + return Game.getMonster(-1, -1, gid); + })) { + return Game.getMonster(-1, -1, gid); + } else { + return false; + } + }; + + const who = (!!target.name ? target.name : classId); + const gid = target.gid; + const primarySlot = Attack.getPrimarySlot(); // for mfswitch + const currentScript = Loader.scriptName(0).toLowerCase(); + + let retry = 0; + let errorInfo = ""; + let attackCount = 0; + + let lastLoc = { x: me.x, y: me.y }; + let tick = getTickCount(); + console.log("ÿc7Kill ÿc0:: " + who); + + if (Config.MFLeader + // mfhelper is disabled for these scripts so announcing is pointless + && !currentScript.includes("diablo") + && !currentScript.includes("baal") + && Pather.makePortal()) { + say("kill " + classId); + } + + try { + while (attackCount < Config.MaxAttackCount && target.attackable && !this.skipCheck(target)) { + // Check if unit got invalidated, happens if necro raises a skeleton from the boss's corpse. + if (!target || !copyUnit(target).x) { + target = Game.getMonster(-1, -1, gid); + !target && (target = findTarget(gid, lastLoc)); + + if (!target) { + console.warn("ÿc1Failed to kill " + who + " (couldn't relocate unit)"); + break; + } + } + + // todo - dodge boss missiles + Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9); + if (Config.MFSwitchPercent && target.hpPercent < Config.MFSwitchPercent) { + me.switchWeapons(primarySlot ^ 1); + } + + if (attackCount > 0 && attackCount % 15 === 0 && Skill.getRange(Config.AttackSkill[1]) < 4) { + Packet.flash(me.gid); + } + + let result = ClassAttack.doAttack(target, attackCount % 15 === 0); + + if (result === this.Result.FAILED) { + if (retry++ > 3) { + errorInfo = " (doAttack failed)"; + + break; + } + + Packet.flash(me.gid); + } else if (result === this.Result.CANTATTACK) { + errorInfo = " (No valid attack skills)"; + + break; + } else if (result === this.Result.NEEDMANA) { + continue; + } else { + retry = 0; + } + + lastLoc = { x: me.x, y: me.y }; + attackCount++; + } + + attackCount === Config.MaxAttackCount && (errorInfo = " (attackCount exceeded: " + attackCount + ")"); + Config.MFSwitchPercent && me.switchWeapons(primarySlot); + ClassAttack.afterAttack(); + Pickit.pickItems(); + + if (!!target && target.attackable) { + console.warn("ÿc1Failed to kill ÿc0" + who + errorInfo); + } else { + if (target.dead && (target.isBoss || target.uniqueid > -1)) { + // a little obnoxious, but we need to track bosses killed and this handles if we are attempting to check by id or name + target.isBoss && Attack._killed.add(target.classid); + target.uniqueid > -1 && Attack._killed.add(target.name); + } + console.log("ÿc7Killed ÿc0:: " + who + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick)); + } + + return (!target || !copyUnit(target).x || target.dead || !target.attackable); + } finally { + // make sure we switch back to primary weapon + if (Config.MFSwitchPercent) { + me.switchWeapons(primarySlot); + } + } + }, + + /** + * @description hurt a unit to a certain percentage of life left + * @param {string | number | Unit} classId + * @param {number} percent + * @returns {boolean} + */ + hurt: function (classId, percent) { + if (!classId || !percent) return false; + const target = (typeof classId === "object" + ? classId + : Misc.poll(function () { + return Game.getMonster(classId); + }, 2000, 100)); + + if (!target) { + console.warn("Attack.hurt: Target not found"); + return false; + } + + let retry = 0, attackCount = 0; + let tick = getTickCount(); + const who = (!!target.name ? target.name : classId); + + while (attackCount < Config.MaxAttackCount && target.attackable && !Attack.skipCheck(target)) { + let result = ClassAttack.doAttack(target, attackCount % 15 === 0); + + if (result === this.Result.FAILED) { + if (retry++ > 3) { + break; + } + + Packet.flash(me.gid); + } else if (result === this.Result.CANTATTACK) { + break; + } else if (result === this.Result.NEEDMANA) { + continue; + } else { + retry = 0; + } + + if (!copyUnit(target).x) { + return true; + } + + attackCount += 1; + + if (target.hpPercent <= percent) { + console.log( + "ÿc7Hurt ÿc0:: " + who + "ÿc7HpPercent: ÿc0" + target.hpPercent + + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick) + ); + break; + } + } + + return true; + }, + + /** + * @description Determine scariness of monster for monster sorting + * @param {Monster} unit + * @returns {number} scariness + */ + getScarinessLevel: function (unit) { + // todo - define summonertype prototype + let scariness = 0; + const ids = [ + sdk.monsters.FallenShaman, sdk.monsters.CarverShaman, sdk.monsters.CarverShaman2, + sdk.monsters.DevilkinShaman, sdk.monsters.DevilkinShaman2, sdk.monsters.DarkShaman1, + sdk.monsters.DarkShaman2, sdk.monsters.WarpedShaman, sdk.monsters.HollowOne, sdk.monsters.Guardian1, + sdk.monsters.Guardian2, sdk.monsters.Unraveler1, sdk.monsters.Unraveler2, + sdk.monsters.Ancient1, sdk.monsters.Ancient2, sdk.monsters.Ancient3, + sdk.monsters.BaalSubjectMummy, sdk.monsters.RatManShaman, sdk.monsters.FetishShaman, + sdk.monsters.FlayerShaman1, sdk.monsters.FlayerShaman2, sdk.monsters.SoulKillerShaman1, + sdk.monsters.SoulKillerShaman2, sdk.monsters.StygianDollShaman1, sdk.monsters.StygianDollShaman2, + sdk.monsters.FleshSpawner1, sdk.monsters.FleshSpawner2, + sdk.monsters.StygianHag, sdk.monsters.Grotesque1, sdk.monsters.Grotesque2 + ]; + + // Only handling monsters for now + if (!unit || unit.type !== sdk.unittype.Monster) return undefined; + // Minion + (unit.isMinion) && (scariness += 1); + // Champion + (unit.isChampion) && (scariness += 2); + // Boss + (unit.isUnique) && (scariness += 4); + // Summoner or the like + ids.includes(unit.classid) && (scariness += 8); + + return scariness; + }, + + /** + * @typedef {Object} ClearOptions + * @property {number} spectype + * @property {number | Unit} bossId + * @property {(a: T, b: T) => number} sortfunc + * @property {boolean} pickit + * @property {(unit: Monster) => boolean} filter + * @property {() => any} callback + */ + + /** + * @description Clear monsters in a section based on range and spectype or clear monsters around a boss monster + * @param {number} range + * @param {Partial} opts + * @returns {boolean} + */ + clearEx: function (range, opts = {}) { + while (!me.gameReady) { + delay(40); + } + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) return false; + + range === undefined && (range = 25); + if (typeof (range) !== "number") throw new Error("Attack.clear: range must be a number."); + + const settings = Object.assign({ + spectype: 0, + bossId: false, + sortfunc: Attack.sortMonsters, + pickit: true, + filter: false, + callback: false, + }, opts); + const { spectype, bossId, sortfunc, pickit, filter, callback } = settings; + + /** @type {Map 999)): + return Game.getMonster(-1, -1, bossId); + default: + return Game.getMonster(bossId); + } + }, 2000, 100); + + if (!boss) { + console.warn("Attack.clear: " + bossId + " not found"); + return Attack.clear(10); + } + + ({ orgx, orgy } = { orgx: boss.x, orgy: boss.y }); + if (Config.MFLeader + && !!bossId + // mfhelper is disabled for these scripts so announcing is pointless + && !Loader.scriptName(0).toLowerCase().includes("diablo") + && !Loader.scriptName(0).toLowerCase().includes("baal") + && Pather.makePortal()) { + say("clear " + (["number", "string"].includes(typeof bossId) ? bossId : bossId.name)); + } + } else { + ({ orgx, orgy } = { orgx: me.x, orgy: me.y }); + } + + let monsterList = []; + let target = Game.getMonster(); + + if (target) { + do { + if (typeof filter === "function" && !filter(target)) continue; + if ((!spectype || (target.spectype & spectype)) && target.attackable && !this.skipCheck(target)) { + // Speed optimization - don't go through monster list until there's at least one within clear range + if (!start && getDistance(target, orgx, orgy) <= range + && (Pather.canTeleport() || !checkCollision(me, target, sdk.collision.WallOrRanged))) { + start = true; + } + + monsterList.push(copyUnit(target)); + } + } while (target.getNext()); + } + + while (start && monsterList.length > 0 && attackCount < Config.MaxAttackCount) { + if (me.dead) return false; + if (typeof callback === "function") callback(); + + boss && (({ orgx, orgy } = { orgx: boss.x, orgy: boss.y })); + monsterList.sort(sortfunc); + target = Game.getMonster(-1, -1, monsterList[0].gid); + + if (target && target.x !== undefined && (getDistance(target, orgx, orgy) <= range + || (this.getScarinessLevel(target) > 7 && target.distance <= range)) + && target.attackable) { + Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9); + tick = getTickCount(); + + if (!logged && boss && boss.gid === target.gid) { + logged = true; + console.log("ÿc7Clear ÿc0:: " + (!!target.name ? target.name : bossId)); + } + // me.overhead("attacking " + target.name + " spectype " + target.spectype + " id " + target.classid); + + let _currMon = attacks.get(target.gid); + const checkAttackSkill = (!!_currMon && _currMon.attacks % 15 === 0); + const result = ClassAttack.doAttack(target, checkAttackSkill); + // let result = ClassAttack.doAttack(target, attackCount % 15 === 0); + + if (result) { + retry = 0; + + if (result === this.Result.CANTATTACK) { + monsterList.shift(); + + continue; + } else if (result === this.Result.NEEDMANA) { + continue; + } + + if (!_currMon) { + _currMon = { attacks: 0, name: target.name }; + attacks.set(target.gid, _currMon); + } + + _currMon.attacks += 1; + attackCount += 1; + const isSpecial = target.isSpecial; + const secAttack = me.barbarian ? (isSpecial ? 2 : 4) : 5; + const checkSkill = Config.AttackSkill[isSpecial ? 1 : 3]; + const hammerCheck = me.paladin && checkSkill === sdk.skills.BlessedHammer; + + if (Config.AttackSkill[secAttack] > -1 + && (!Attack.checkResist(target, checkSkill) || (hammerCheck && !ClassAttack.getHammerPosition(target)))) { + skillCheck = Config.AttackSkill[secAttack]; + } else { + skillCheck = checkSkill; + } + + // Desync/bad position handler + switch (skillCheck) { + case sdk.skills.BlessedHammer: + // Tele in random direction with Blessed Hammer + if (_currMon.attacks > 0 && _currMon.attacks % (isSpecial ? 4 : 2) === 0) { + Pather.randMove(-1, 1, -1, 1, 5); + } + + break; + default: + // Flash with melee skills + if (_currMon.attacks > 0 + && _currMon.attacks % (isSpecial ? 15 : 5) === 0 + && Skill.getRange(skillCheck) < 4) { + Packet.flash(me.gid); + // It'd be helpful to get a position in the opposite direction of the monster move there and then move back + // Pather.moveTo(me.x + (me.x - target.x), me.y + (me.y - target.y)); + console.debug("ÿc1Flashing " + target.name + " " + target.gid + " " + _currMon.attacks); + // Pather.randMove(-1, 1, -1, 1, 3); + + } + + break; + } + + // Skip non-unique monsters after 15 attacks, except in Throne of Destruction + if (!me.inArea(sdk.areas.ThroneofDestruction) && !isSpecial && _currMon.attacks > 15) { + console.log("ÿc1Skipping " + target.name + " " + target.gid + " " + _currMon.attacks); + monsterList.shift(); + } + + /** + * @todo allow for more aggressive horking here + */ + if (target.dead || Config.FastPick || Config.FastFindItem) { + if ((target.isBoss || target.uniqueid > 0) && target.dead) { + // TODO: add uniqueids to sdk + target.isBoss && Attack._killed.add(target.classid); + target.uniqueid > -1 && Attack._killed.add(target.name); + } + if (boss && boss.gid === target.gid && target.dead) { + killedBoss = true; + console.log( + "ÿc7Cleared ÿc0:: " + (!!target.name ? target.name : bossId) + + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick) + ); + } + Config.FastFindItem && pickit && ClassAttack.findItem(); + Pickit.fastPick(); + } + } else { + if (me.inArea(sdk.areas.ChaosSanctuary) && target.classid === sdk.monsters.StormCaster1) { + // probably behind the wall - skip them + monsterList.shift(); + retry = 0; + } + if (retry++ > 3) { + monsterList.shift(); + retry = 0; + } + + Packet.flash(me.gid); + } + } else { + monsterList.shift(); + } + } + + if (attackCount > 0) { + ClassAttack.afterAttack(pickit); + this.openChests(range, orgx, orgy); + pickit && Pickit.pickItems(); + } else { + Precast.doPrecast(false); // we didn't attack anything but check if we need to precast. TODO: better method of keeping track of precast skills + } + + if (boss && !killedBoss) { + // check if boss corpse is around + if (boss.dead) { + console.log( + "ÿc7Cleared ÿc0:: " + (!!boss.name ? boss.name : bossId) + + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick) + ); + } else { + console.log("ÿc7Clear ÿc0:: ÿc1Failed to clear ÿc0:: " + (!!boss.name ? boss.name : bossId)); + } + } + + return true; + }, + + /** + * @description Clear monsters in a section based on range and spectype or clear monsters around a boss monster + * @param {number} [range=25] + * @param {number} [spectype=0] + * @param {number | Unit} [bossId] + * @param {(a: T, b: T) => number} [sortfunc] + * @param {boolean} [pickit] + * @returns {boolean} + * @todo change to passing an object + */ + clear: function (range, spectype, bossId, sortfunc, pickit = true) { + while (!me.gameReady) { + delay(40); + } + + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) return false; + + range === undefined && (range = 25); + spectype === undefined && (spectype = 0); + bossId === undefined && (bossId = false); + sortfunc === undefined && (sortfunc = false); + !sortfunc && (sortfunc = this.sortMonsters); + + if (typeof (range) !== "number") throw new Error("Attack.clear: range must be a number."); + + let i, boss, orgx, orgy, start, skillCheck; + let gidAttack = []; + let tick = getTickCount(); + let [killedBoss, logged] = [false, false]; + let [retry, attackCount] = [0, 0]; + + if (bossId) { + boss = Misc.poll(function () { + switch (true) { + case typeof bossId === "object": + return bossId; + case ((typeof bossId === "number" && bossId > 999)): + return Game.getMonster(-1, -1, bossId); + default: + return Game.getMonster(bossId); + } + }, 2000, 100); + + if (!boss) { + console.warn("Attack.clear: " + bossId + " not found"); + return Attack.clear(10); + } + + ({ orgx, orgy } = { orgx: boss.x, orgy: boss.y }); + if (Config.MFLeader + && !!bossId + // mfhelper is disabled for these scripts so announcing is pointless + && !Loader.scriptName(0).toLowerCase().includes("diablo") + && !Loader.scriptName(0).toLowerCase().includes("baal") + && Pather.makePortal()) { + say("clear " + (["number", "string"].includes(typeof bossId) ? bossId : bossId.name)); + } + } else { + ({ orgx, orgy } = { orgx: me.x, orgy: me.y }); + } + + let monsterList = []; + let target = Game.getMonster(); + + if (target) { + do { + if ((!spectype || (target.spectype & spectype)) && target.attackable && !this.skipCheck(target)) { + // Speed optimization - don't go through monster list until there's at least one within clear range + if (!start && getDistance(target, orgx, orgy) <= range + && (Pather.canTeleport() || !checkCollision(me, target, sdk.collision.WallOrRanged))) { + start = true; + } + + monsterList.push(copyUnit(target)); + } + } while (target.getNext()); + } + + while (start && monsterList.length > 0 && attackCount < Config.MaxAttackCount) { + if (me.dead) return false; + + boss && (({ orgx, orgy } = { orgx: boss.x, orgy: boss.y })); + monsterList.sort(sortfunc); + target = Game.getMonster(-1, -1, monsterList[0].gid); + + if (target && target.x !== undefined && (getDistance(target, orgx, orgy) <= range + || (this.getScarinessLevel(target) > 7 && target.distance <= range)) + && target.attackable) { + Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9); + tick = getTickCount(); + + if (!logged && boss && boss.gid === target.gid) { + logged = true; + console.log("ÿc7Clear ÿc0:: " + (!!target.name ? target.name : bossId)); + } + // me.overhead("attacking " + target.name + " spectype " + target.spectype + " id " + target.classid); + + // custom handling here, we want to find a valid monster to use our skill on + // if we wait until they are the current target, it may too late to be useful + if (Config.ChargeCast.skill > -1 + && Config.ChargeCast.spectype + && !(target.spectype & Config.ChargeCast.spectype)) { + let cRange = Skill.getRange(Config.ChargeCast.skill); + let cState = Skill.getState(Config.ChargeCast.skill); + let chargeTarget = monsterList.find(function (mon) { + return ( + (mon.spectype & Config.ChargeCast.spectype) + && (mon.distance <= cRange) + && (!cState || !mon.getState(cState)) + && !checkCollision(me, mon, sdk.collision.LineOfSight) + ); + }); + if (chargeTarget && chargeTarget.gid !== target.gid) { + Attack.doChargeCast(chargeTarget); + } + } + + const result = ClassAttack.doAttack(target, attackCount % 15 === 0); + + if (result) { + retry = 0; + + if (result === this.Result.CANTATTACK) { + monsterList.shift(); + + continue; + } else if (result === this.Result.NEEDMANA) { + continue; + } + + for (i = 0; i < gidAttack.length; i += 1) { + if (gidAttack[i].gid === target.gid) { + break; + } + } + + if (i === gidAttack.length) { + gidAttack.push({ gid: target.gid, attacks: 0, name: target.name }); + } + + gidAttack[i].attacks += 1; + attackCount += 1; + let isSpecial = target.isSpecial; + let secAttack = me.barbarian ? (isSpecial ? 2 : 4) : 5; + let checkSkill = Config.AttackSkill[isSpecial ? 1 : 3]; + let hammerCheck = me.paladin && checkSkill === sdk.skills.BlessedHammer; + + if (Config.AttackSkill[secAttack] > -1 && (!Attack.checkResist(target, checkSkill) + || (hammerCheck && !ClassAttack.getHammerPosition(target)))) { + skillCheck = Config.AttackSkill[secAttack]; + } else { + skillCheck = checkSkill; + } + + // Desync/bad position handler + switch (skillCheck) { + case sdk.skills.BlessedHammer: + // Tele in random direction with Blessed Hammer + if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 4 : 2) === 0) { + Pather.randMove(-1, 1, -1, 1, 5); + } + + break; + default: + // Flash with melee skills + if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 15 : 5) === 0 + && Skill.getRange(skillCheck) < 4) { + Packet.flash(me.gid); + } + + break; + } + + // Skip non-unique monsters after 15 attacks, except in Throne of Destruction + if (!me.inArea(sdk.areas.ThroneofDestruction) && !isSpecial && gidAttack[i].attacks > 15) { + console.log("ÿc1Skipping " + target.name + " " + target.gid + " " + gidAttack[i].attacks); + monsterList.shift(); + } + + /** + * @todo allow for more aggressive horking here + */ + if (target.dead || Config.FastPick || Config.FastFindItem) { + if ((target.isBoss || target.uniqueid > 0) && target.dead) { + // TODO: add uniqueids to sdk + target.isBoss && Attack._killed.add(target.classid); + target.uniqueid > -1 && Attack._killed.add(target.name); + } + if (boss && boss.gid === target.gid && target.dead) { + killedBoss = true; + console.log( + "ÿc7Cleared ÿc0:: " + (!!target.name ? target.name : bossId) + + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick) + ); + } + Config.FastFindItem && pickit && ClassAttack.findItem(); + Pickit.fastPick(); + } + } else { + if (me.inArea(sdk.areas.ChaosSanctuary) && target.classid === sdk.monsters.StormCaster1) { + // probably behind the wall - skip them + monsterList.shift(); + retry = 0; + } + if (retry++ > 3) { + monsterList.shift(); + retry = 0; + } + + Packet.flash(me.gid); + } + } else { + monsterList.shift(); + } + } + + if (attackCount > 0) { + ClassAttack.afterAttack(pickit); + this.openChests(range, orgx, orgy); + pickit && Pickit.pickItems(); + } else { + Precast.doPrecast(false); // we didn't attack anything but check if we need to precast. TODO: better method of keeping track of precast skills + } + + if (boss && !killedBoss) { + // check if boss corpse is around + if (boss.dead) { + console.log( + "ÿc7Cleared ÿc0:: " + (!!boss.name ? boss.name : bossId) + + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick) + ); + } else { + console.log("ÿc7Clear ÿc0:: ÿc1Failed to clear ÿc0:: " + (!!boss.name ? boss.name : bossId)); + } + } + + return true; + }, + + /** + * @description clear all monsters based on classid arguments + * @param {...number} ids + * @returns {boolean} + * @todo + * - Should there be a range parameter for this? + * - Should we keep track of where we started from? + */ + clearClassids: function (...ids) { + // lets keep track of where we started from and move back when done + const { x, y } = me; + let cleared = false; + + for (let i = 0; i < 3; i++) { + let monster = Game.getMonster(); + + if (monster) { + let list = []; + + do { + if (ids.includes(monster.classid) && monster.attackable) { + list.push(copyUnit(monster)); + } + } while (monster.getNext()); + + if (!list.length) { + break; + } + // if we cleared, return to our starting position + if (Attack.clearList(list)) { + Pather.moveTo(x, y); + cleared = true; + } + } else { + // if no monsters were found should that be a pass or fail? + return false; // fail for now + } + } + + return cleared; + }, + + /** + * @description Filter monsters based on classId, spectype and range + * @param {number} classid + * @param {number} spectype + * @param {number} range + * @param {Unit | {x: number, y: number}} center + * @returns {Monster[]} + */ + getMob: function (classid, spectype, range, center) { + let monsterList = []; + let monster = Game.getMonster(); + + range === undefined && (range = 25); + !center && (center = me); + + switch (typeof classid) { + case "number": + case "string": + monster = Game.getMonster(classid); + + if (monster) { + do { + if (getDistance(center.x, center.y, monster.x, monster.y) <= range + && (!spectype || (monster.spectype & spectype)) && monster.attackable) { + monsterList.push(copyUnit(monster)); + } + } while (monster.getNext()); + } + + break; + case "object": + monster = Game.getMonster(); + + if (monster) { + do { + if (classid.includes(monster.classid) && getDistance(center.x, center.y, monster.x, monster.y) <= range + && (!spectype || (monster.spectype & spectype)) && monster.attackable) { + monsterList.push(copyUnit(monster)); + } + } while (monster.getNext()); + } + + break; + } + + return monsterList; + }, + + /** + * @description Clear an already formed array of monstas + * @param {Function | Array} mainArg + * @param {Function} [sortFunc] + * @param {boolean} [refresh] + * @returns {boolean} + */ + clearList: function (mainArg, sortFunc, refresh) { + /** @type {Monster[]} */ + let monsterList; + /** @type {{ gid: number, attacks: number }[]} */ + let gidAttack = []; + let [retry, attackCount] = [0, 0]; + + switch (typeof mainArg) { + case "function": + monsterList = mainArg.call(this); + + break; + case "object": + monsterList = mainArg.slice(0); + + break; + case "boolean": // false from Attack.getMob() + return false; + default: + throw new Error("clearList: Invalid argument"); + } + + !sortFunc && (sortFunc = this.sortMonsters); + + while (monsterList.length > 0 && attackCount < Config.MaxAttackCount) { + if (me.dead) return false; + + if (refresh && attackCount > 0 && attackCount % refresh === 0) { + monsterList = mainArg.call(); + } + + monsterList.sort(sortFunc); + let target = copyUnit(monsterList[0]); + + if (target.x !== undefined && target.attackable) { + Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9); + // me.overhead("attacking " + target.name + " spectype " + target.spectype + " id " + target.classid); + let i; + let result = ClassAttack.doAttack(target, attackCount % 15 === 0); + + if (result) { + retry = 0; + + if (result === this.Result.CANTATTACK) { + monsterList.shift(); + + continue; + } else if (result === this.Result.NEEDMANA) { + continue; + } + + for (i = 0; i < gidAttack.length; i += 1) { + if (gidAttack[i].gid === target.gid) { + break; + } + } + + if (i === gidAttack.length) { + gidAttack.push({ gid: target.gid, attacks: 0 }); + } + + gidAttack[i].attacks += 1; + let isSpecial = target.isSpecial; + + // Desync/bad position handler + switch (Config.AttackSkill[isSpecial ? 1 : 3]) { + case sdk.skills.BlessedHammer: + // Tele in random direction with Blessed Hammer + if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 5 : 15) === 0) { + let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 4); + Pather.moveTo(coord.x, coord.y); + } + + break; + default: + // Flash with melee skills + if (gidAttack[i].attacks > 0 && gidAttack[i].attacks % (isSpecial ? 5 : 15) === 0 + && Skill.getRange(Config.AttackSkill[isSpecial ? 1 : 3]) < 4) { + Packet.flash(me.gid); + } + + break; + } + + // Skip non-unique monsters after 15 attacks, except in Throne of Destruction + if (!me.inArea(sdk.areas.ThroneofDestruction) && !isSpecial && gidAttack[i].attacks > 15) { + console.log("ÿc1Skipping " + target.name + " " + target.gid + " " + gidAttack[i].attacks); + monsterList.shift(); + } + + attackCount += 1; + + if (target.dead || Config.FastPick) { + Pickit.fastPick(); + } + } else { + if (retry++ > 3) { + monsterList.shift(); + retry = 0; + } + + Packet.flash(me.gid); + } + } else { + monsterList.shift(); + } + } + + if (attackCount > 0) { + ClassAttack.afterAttack(true); + this.openChests(Config.OpenChests.Range); + Pickit.pickItems(); + } else { + Precast.doPrecast(false); // we didn't attack anything but check if we need to precast. TODO: better method of keeping track of precast skills + } + + return true; + }, + + /** + * @param {number} x + * @param {number} y + * @param {number} [range=15] + * @param {number} [timer=3000] - time in ms + * @param {boolean} [skipBlocked=true] + * @param {boolean} [special=false] + * @returns {void} + */ + securePosition: function (x, y, range = 15, timer = 3000, skipBlocked = true, special = false) { + let tick; + + (typeof x !== "number" || typeof y !== "number") && ({ x, y } = me); + skipBlocked === true && (skipBlocked = sdk.collision.Ranged); + + while (true) { + [x, y].distance > 5 && Pather.moveTo(x, y); + + let monster = Game.getMonster(); + let monList = []; + + if (monster) { + do { + if (getDistance(monster, x, y) <= range && monster.attackable && this.canAttack(monster) + && (!skipBlocked || !checkCollision(me, monster, skipBlocked)) + && (Pather.canTeleport() || !checkCollision(me, monster, sdk.collision.BlockWall))) { + monList.push(copyUnit(monster)); + } + } while (monster.getNext()); + } + + if (!monList.length) { + !tick && (tick = getTickCount()); + + // only return if it's been safe long enough + if (getTickCount() - tick >= timer) { + return; + } + } else { + this.clearList(monList); + + // reset the timer when there's monsters in range + tick && (tick = false); + } + + if (special) { + if (me.paladin && Skill.canUse(sdk.skills.Redemption) + && Skill.setSkill(sdk.skills.Redemption, sdk.skills.hand.Right)) { + delay(1000); + } + } + + delay(100); + } + }, + + /** + * @description Draw lines around a room on minimap + * @param {Room} room + * @param {number} color + */ + markRoom: function (room, color) { + let arr = []; + const [rX, rY] = [room.x * 5, room.y * 5]; + + arr.push(new Line(rX, rY, rX, rY + room.ysize, color, true)); + arr.push(new Line(rX, rY, rX + room.xsize, rY, color, true)); + arr.push(new Line(rX + room.xsize, rY, rX + room.xsize, rY + room.ysize, color, true)); + arr.push(new Line(rX, rY + room.ysize, rX + room.xsize, rY + room.ysize, color, true)); + }, + + /** + * @description Count uniques in current area within getUnit range + */ + countUniques: function () { + !Attack.uniques && (Attack.uniques = 0); + !Attack.ignoredGids && (Attack.ignoredGids = []); + + let monster = Game.getMonster(); + + if (monster) { + do { + if ((monster.isSuperUnique) && Attack.ignoredGids.indexOf(monster.gid) === -1) { + Attack.uniques += 1; + Attack.ignoredGids.push(monster.gid); + } + } while (monster.getNext()); + } + }, + + /** + * @description Store average unique monsters counted in area during run + * @param {number} area + */ + storeStatistics: function (area) { + !FileTools.exists("statistics.json") && FileAction.write("statistics.json", "{}"); + + let obj = JSON.parse(FileAction.read("statistics.json")); + + if (obj) { + if (obj[area] === undefined) { + obj[area] = { + runs: 1, + averageUniques: (Attack.uniques).toFixed(4) + }; + } else { + let { averageUniques, runs } = obj[area]; + obj[area].averageUniques = ((averageUniques * runs + Attack.uniques) / (runs + 1)).toFixed(4); + obj[area].runs += 1; + } + + FileAction.write("statistics.json", JSON.stringify(obj)); + } + + Attack.uniques = 0; + Attack.ignoredGids = []; + }, + + /** + * @description Clear an entire area based on monster spectype using nearestNeighbourSearch + * @param {number} spectype + * @param {() => boolean} [cb] callback to end clearing early + * @returns {boolean} + */ + clearLevelWalk: function (spectype, cb = null) { + const Graph = require("../modules/Graph"); + + try { + console.info(true, getAreaName(me.area), "clearLevelWalk-nearestNeighbourSearch"); + let graph = new Graph(); + Graph.nearestNeighbourSearch(graph, function (room) { + if (typeof cb === "function" && cb()) { + throw new ScriptError("Clearing stopped by callback"); + } + const roomNode = new PathNode(room.walkableX, room.walkableY); + Pather.move(roomNode, { callback: cb, clearSettings: { clearPath: true } }); + Attack.clearEx(room.xsize, { + spectype: spectype || 0, + filter: function (unit) { + return unit && room.coordsInRoom(unit.x, unit.y); + } + }); + }, "walk"); + } catch (e) { + if (!(e instanceof ScriptError)) { + console.error(e); + } + } finally { + CollMap.removeHooks(); + console.info(false, getAreaName(me.area), "clearLevelWalk-nearestNeighbourSearch"); + } + }, + + /** + * @description Clear an entire area based on monster spectype + * @param {number} spectype + * @param {() => boolean} [cb] callback to end clearing early + * @returns {boolean} + */ + clearLevel: function (spectype = 0, cb = null) { + function RoomSort (a, b) { + return getDistance(myRoom[0], myRoom[1], a[0], a[1]) - getDistance(myRoom[0], myRoom[1], b[0], b[1]); + } + + let room = getRoom(); + if (!room) return false; + + console.time("clearLevel"); + console.info(true, getAreaName(me.area)); + + let myRoom, previousArea; + let rooms = []; + const currentArea = getArea().id; + + do { + rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]); + } while (room.getNext()); + + if (Config.MFLeader && rooms.length > 0) { + Pather.makePortal(); + console.log("clearlevel " + getAreaName(currentArea)); + say("clearlevel " + me.area); + } + + while (rooms.length > 0) { + // get the first room + initialize myRoom var + !myRoom && (room = getRoom(me.x, me.y)); + + if (typeof cb === "function" && cb()) { + break; + } + + if (room) { + // use previous room to calculate distance + if (room instanceof Array) { + myRoom = [room[0], room[1]]; + } else { + // create a new room to calculate distance (first room, done only once) + myRoom = [room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]; + } + } + + rooms.sort(RoomSort); + room = rooms.shift(); + + let result = Pather.getNearestWalkable(room[0], room[1], 18, 3); + + if (result) { + Pather.moveToEx( + result[0], result[1], + { retry: 3, clearSettings: { specType: spectype, clearPath: (!Pather.canTeleport()) } } + ); + previousArea = result; + + if (!this.clear(40, spectype)) { + break; + } + } else if (currentArea !== getArea().id) { + // Make sure bot does not get stuck in different area. + Pather.moveToEx( + previousArea[0], previousArea[1], + { retry: 3, clearSettings: { specType: spectype, clearPath: (!Pather.canTeleport()) } } + ); + } + } + + //this.storeStatistics(getAreaName(me.area)); + console.info(false, getAreaName(currentArea), "clearLevel"); + + return true; + }, + + /** + * @description Sort monsters based on distance, spectype and classId (summoners are attacked first) + * @param {Monster} unitA + * @param {Monster} unitB + * @returns {boolean} + * @todo Think this needs a collison check included for non tele chars, might prevent choosing + * closer mob that is actually behind a wall vs the one we pass trying to get behind the wall + */ + sortMonsters: function (unitA, unitB) { + // No special sorting for were-form + if (Config.Wereform) return getDistance(me, unitA) - getDistance(me, unitB); + + // sort main bosses first + // Andy + if (me.inArea(sdk.areas.CatacombsLvl4)) { + if (unitA.distance < 5 && unitA.classid === sdk.monsters.Andariel + && !checkCollision(me, unitA, sdk.collision.Ranged)) { + return -1; + } + } + + // Meph + if (me.inArea(sdk.areas.DuranceofHateLvl3)) { + if (unitA.distance < 5 && unitA.classid === sdk.monsters.Mephisto + && !checkCollision(me, unitA, sdk.collision.Ranged)) { + return -1; + } + } + + // Baal + if (me.inArea(sdk.areas.WorldstoneChamber)) { + if (unitA.classid === sdk.monsters.Baal) return -1; + } + + // Barb optimization + if (me.barbarian) { + if (!Attack.checkResist(unitA, Attack.getSkillElement(Config.AttackSkill[(unitA.isSpecial) ? 1 : 3]))) { + return 1; + } + + if (!Attack.checkResist(unitB, Attack.getSkillElement(Config.AttackSkill[(unitB.isSpecial) ? 1 : 3]))) { + return -1; + } + } + + // Put monsters under Attract curse at the end of the list - They are helping us + if (unitA.getState(sdk.states.Attract)) return 1; + if (unitB.getState(sdk.states.Attract)) return -1; + + const ids = [ + sdk.monsters.OblivionKnight1, sdk.monsters.OblivionKnight2, sdk.monsters.OblivionKnight3, + sdk.monsters.FallenShaman, sdk.monsters.CarverShaman, sdk.monsters.CarverShaman2, + sdk.monsters.DevilkinShaman, sdk.monsters.DevilkinShaman2, sdk.monsters.DarkShaman1, + sdk.monsters.DarkShaman2, sdk.monsters.WarpedShaman, sdk.monsters.HollowOne, sdk.monsters.Guardian1, + sdk.monsters.Guardian2, sdk.monsters.Unraveler1, sdk.monsters.Unraveler2, + sdk.monsters.Ancient1, sdk.monsters.BaalSubjectMummy, sdk.monsters.BloodRaven, sdk.monsters.RatManShaman, + sdk.monsters.FetishShaman, sdk.monsters.FlayerShaman1, sdk.monsters.FlayerShaman2, + sdk.monsters.SoulKillerShaman1, sdk.monsters.SoulKillerShaman2, sdk.monsters.StygianDollShaman1, + sdk.monsters.StygianDollShaman2, sdk.monsters.FleshSpawner1, sdk.monsters.FleshSpawner2, + sdk.monsters.StygianHag, sdk.monsters.Grotesque1, sdk.monsters.Ancient2, sdk.monsters.Ancient3, + sdk.monsters.Grotesque2 + ]; + + if (!me.inArea(sdk.areas.ClawViperTempleLvl2) && ids.includes(unitA.classid) && ids.includes(unitB.classid)) { + // Kill "scary" uniques first (like Bishibosh) + if ((unitA.isUnique) && (unitB.isUnique)) return getDistance(me, unitA) - getDistance(me, unitB); + if (unitA.isUnique) return -1; + if (unitB.isUnique) return 1; + + return getDistance(me, unitA) - getDistance(me, unitB); + } + + if (ids.includes(unitA.classid)) return -1; + if (ids.includes(unitB.classid)) return 1; + + if (Config.BossPriority) { + if ((unitA.isSuperUnique) && (unitB.isSuperUnique)) return getDistance(me, unitA) - getDistance(me, unitB); + + if (unitA.isSuperUnique) return -1; + if (unitB.isSuperUnique) return 1; + } + + return getDistance(me, unitA) - getDistance(me, unitB); + }, + + /** + * @description Check if a set of coords is valid/accessable + * @param {number} x + * @param {number} y + * @param {number} [skill=-1] + * @param {number} [unitid=0] + * @returns {boolean} If the spot is a valid location for walking/casting/attack + * @todo re-work this for more info: + * - casting skills can go over non-floors - excluding bliz/meteor - not sure if any others + * - physical skills can't, need to exclude monster objects though + * - splash skills can go through some objects, however some objects are cast blockers + */ + validSpot: function (x, y, skill = -1, unitid = 0) { + // Just in case + if (!me.area || !x || !y) return false; + // for now this just returns true and we leave getting into position to the actual class attack files + if (Skill.missileSkills.includes(skill) + || ([sdk.skills.Blizzard, sdk.skills.Meteor].includes(skill) + && unitid > 0 && !getBaseStat("monstats", unitid, "flying"))) { + return true; + } + + let result; + let mObject = Attack.monsterObjects.includes(unitid); + let nonFloorAreas = [ + sdk.areas.ArcaneSanctuary, sdk.areas.RiverofFlame, sdk.areas.ChaosSanctuary, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + + // Treat thrown errors as invalid spot + try { + result = getCollision(me.area, x, y); + } catch (e) { + return false; + } + + if (result === undefined) return false; + + switch (true) { + case Skill.needFloor.includes(skill) && nonFloorAreas.includes(me.area): + let isFloor = !!(result & (0 | sdk.collision.IsOnFloor)); + // this spot is not on the floor (lava (river/chaos, space (arcane), ect)) + if (!isFloor) { + return false; + } + + return !(result & sdk.collision.BlockWall); // outside lava area in abaddon returns coll 1 + case (mObject && (!!(result & sdk.collision.MonsterIsOnFloor) || !!(result & sdk.collision.MonsterObject))): + // kinda dumb - monster objects have a collision that causes them to not be attacked + // this should fix that + return true; + default: + // Avoid non-walkable spots, objects - this preserves the orignal function and also physical attack skills will get here + if ((result & sdk.collision.BlockWall) || (result & sdk.collision.Objects)) return false; + + break; + } + + return true; + }, + + /** + * @description Open chests when clearing + * @param {number} range + * @param {number} [x=me.x] + * @param {number} [y=me.y] + * @returns {boolean} + */ + openChests: function (range, x, y) { + if (!Config.OpenChests.Enabled) return false; + (typeof x !== "number" || typeof y !== "number") && ({ x, y } = me); + range === undefined && (range = 10); + + let list = []; + let ids = ["chest", "chest3", "weaponrack", "armorstand"]; + let unit = Game.getObject(); + + if (unit) { + do { + if (unit.name && getDistance(unit, x, y) <= range + && ids.includes(unit.name.toLowerCase())) { + list.push(copyUnit(unit)); + } + } while (unit.getNext()); + } + + while (list.length) { + list.sort(Sort.units); + + if (Misc.openChest(list.shift())) { + Pickit.pickItems(); + } + } + + return true; + }, + + /** + * @description build list of attackable monsters currently around us + * @param {function(): boolean} check - callback function to build list + * @returns {Array | []} + */ + buildMonsterList: function (check = () => true) { + let monList = []; + let monster = Game.getMonster(); + + if (monster) { + do { + if (monster.attackable && check(monster)) { + monList.push(copyUnit(monster)); + } + } while (monster.getNext()); + } + + return monList; + }, + + /** + * @param {Unit} unit + * @param {number} distance + * @param {number} spread + * @param {number} range + * @returns {{x: number, y: number}} x/y coords of safe spot + */ + findSafeSpot: function (unit, distance, spread, range) { + if (arguments.length < 4) throw new Error("deploy: Not enough arguments supplied"); + + let index; + let monList = []; + let count = 999; + + monList = this.buildMonsterList(); + monList.sort(Sort.units); + if (this.getMonsterCount(me.x, me.y, 15, monList) === 0) return true; + + CollMap.getNearbyRooms(unit.x, unit.y); + let grid = this.buildGrid(unit.x - distance, unit.x + distance, unit.y - distance, unit.y + distance, spread); + + if (!grid.length) return false; + grid.sort((a, b) => getDistance(b.x, b.y, unit.x, unit.y) - getDistance(a.x, a.y, unit.x, unit.y)); + + for (let i = 0; i < grid.length; i += 1) { + if (!(CollMap.getColl(grid[i].x, grid[i].y, true) & sdk.collision.BlockWall) + && !CollMap.checkColl(unit, { x: grid[i].x, y: grid[i].y }, sdk.collision.Ranged)) { + let currCount = this.getMonsterCount(grid[i].x, grid[i].y, range, monList); + + if (currCount < count) { + index = i; + count = currCount; + } + + if (currCount === 0) { + break; + } + } + } + + if (typeof index === "number") { + return { + x: grid[index].x, + y: grid[index].y, + }; + } + + return false; + }, + + deploy: function (unit, distance, spread, range) { + if (arguments.length < 4) throw new Error("deploy: Not enough arguments supplied"); + + let safeLoc = this.findSafeSpot(unit, distance, spread, range); + + return (typeof safeLoc === "object" ? Pather.moveToUnit(safeLoc, 0) : false); + }, + + getMonsterCount: function (x, y, range, list) { + let count = 0; + let ignored = [sdk.monsters.Diablo]; // why is diablo ignored? + + for (let i = 0; i < list.length; i += 1) { + if (ignored.indexOf(list[i].classid) === -1 && list[i].attackable + && getDistance(x, y, list[i].x, list[i].y) <= range) { + count += 1; + } + } + + // missile check? + let fire = Game.getObject("fire"); + + if (fire) { + do { + if (getDistance(x, y, fire.x, fire.y) <= 4) { + count += 100; + } + } while (fire.getNext()); + } + + return count; + }, + + buildGrid: function (xmin, xmax, ymin, ymax, spread) { + if (xmin >= xmax || ymin >= ymax || spread < 1) { + throw new Error("buildGrid: Bad parameters"); + } + + let grid = []; + + for (let i = xmin; i <= xmax; i += spread) { + for (let j = ymin; j <= ymax; j += spread) { + let coll = CollMap.getColl(i, j, true); + + if (typeof coll === "number") { + grid.push({ x: i, y: j, coll: coll }); + } + } + } + + return grid; + }, + + /** + * @description checks if we should skip a monster + * @param {Unit} unit + * @returns {Boolean} If we should skip this monster + */ + skipCheck: function (unit) { + if (me.inArea(sdk.areas.ThroneofDestruction)) return false; + if (unit.isSpecial && Config.SkipException && Config.SkipException.includes(unit.name)) { + console.log("ÿc1Skip Exception: " + unit.name); + return false; + } + + if (Config.SkipId.includes(unit.classid)) return true; + + let tempArray = []; + + // EnchantLoop: // Skip enchanted monsters + for (let i = 0; i < Config.SkipEnchant.length; i += 1) { + tempArray = Config.SkipEnchant[i].toLowerCase().split(" and "); + + for (let j = 0; j < tempArray.length; j += 1) { + switch (tempArray[j]) { + case "extra strong": + tempArray[j] = sdk.enchant.ExtraStrong; + + break; + case "extra fast": + tempArray[j] = sdk.enchant.ExtraFast; + + break; + case "cursed": + tempArray[j] = sdk.enchant.Cursed; + + break; + case "magic resistant": + tempArray[j] = sdk.enchant.MagicResistant; + + break; + case "fire enchanted": + tempArray[j] = sdk.enchant.FireEnchanted; + + break; + case "lightning enchanted": + tempArray[j] = sdk.enchant.LightningEnchanted; + + break; + case "cold enchanted": + tempArray[j] = sdk.enchant.ColdEnchanted; + + break; + case "mana burn": + tempArray[j] = sdk.enchant.ManaBurn; + + break; + case "teleportation": + tempArray[j] = sdk.enchant.Teleportation; + + break; + case "spectral hit": + tempArray[j] = sdk.enchant.SpectralHit; + + break; + case "stone skin": + tempArray[j] = sdk.enchant.StoneSkin; + + break; + case "multiple shots": + tempArray[j] = sdk.enchant.MultipleShots; + + break; + } + } + + if (tempArray.every(enchant => unit.getEnchant(enchant))) { + return true; + } + } + + // ImmuneLoop: // Skip immune monsters + for (let i = 0; i < Config.SkipImmune.length; i += 1) { + tempArray = Config.SkipImmune[i].toLowerCase().split(" and "); + + // Infinity calculations are built-in + if (tempArray.every(immnue => !Attack.checkResist(unit, immnue))) { + return true; + } + } + + // AuraLoop: // Skip monsters with auras + for (let i = 0; i < Config.SkipAura.length; i += 1) { + let aura = Config.SkipAura[i].toLowerCase(); + + switch (true) { + case aura === "might" && unit.getState(sdk.states.Might): + case aura === "blessed aim" && unit.getState(sdk.states.BlessedAim): + case aura === "fanaticism" && unit.getState(sdk.states.Fanaticism): + case aura === "conviction" && unit.getState(sdk.states.Conviction): + case aura === "holy fire" && unit.getState(sdk.states.HolyFire): + case aura === "holy freeze" && unit.getState(sdk.states.HolyFreeze): + case aura === "holy shock" && unit.getState(sdk.states.HolyShock): + return true; + default: + break; + } + } + + return false; + }, + + /** + * @description Get element by skill number + * @param {number} skillId + * @returns {"physical" | "fire" | "lightning" | "magic" | "cold" | "poison" | "holybolt" | "none" | false} + */ + getSkillElement: function (skillId) { + let elements = ["physical", "fire", "lightning", "magic", "cold", "poison", "none"]; + + switch (skillId) { + case sdk.skills.HolyFire: + return "fire"; + case sdk.skills.HolyFreeze: + return "cold"; + case sdk.skills.HolyShock: + return "lightning"; + case sdk.skills.CorpseExplosion: + case sdk.skills.Stun: + case sdk.skills.Concentrate: + case sdk.skills.Frenzy: + case sdk.skills.MindBlast: + case sdk.skills.Summoner: + return "physical"; + case sdk.skills.HolyBolt: + // no need to use this.elements array because it returns before going over the array + return "holybolt"; + } + + let eType = getBaseStat("skills", skillId, "etype"); + + return typeof (eType) === "number" ? elements[eType] : false; + }, + + /** + * @description Get a monster's resistance to specified element + * @param {Unit | Monster} unit + * @param {"physical" | "fire" | "lightning" | "magic" | "cold" | "poison" | "holybolt" | "none"} type + * @returns {number} + */ + getResist: function (unit, type) { + // some scripts pass empty units in throne room + if (!unit || !unit.getStat) return 100; + if (unit.isPlayer) return 0; + + switch (type) { + case "physical": + return unit.getStat(sdk.stats.DamageResist); + case "fire": + return unit.getStat(sdk.stats.FireResist); + case "lightning": + return unit.getStat(sdk.stats.LightningResist); + case "magic": + return unit.getStat(sdk.stats.MagicResist); + case "cold": + return unit.getStat(sdk.stats.ColdResist); + case "poison": + return unit.getStat(sdk.stats.PoisonResist); + case "none": + return 0; + case "holybolt": // check if a monster is undead + if (getBaseStat("monstats", unit.classid, "lUndead") || getBaseStat("monstats", unit.classid, "hUndead")) { + return 0; + } + // eslint-disable-next-line no-fallthrough + default: + return 100; + } + }, + + getLowerResistPercent: function () { + const calc = (level) => Math.floor(Math.min(25 + (45 * ((110 * level) / (level + 6)) / 100), 70)); + if (Skill.canUse(sdk.skills.LowerResist)) { + return calc(me.getSkill(sdk.skills.LowerResist, sdk.skills.subindex.SoftPoints)); + } + return 0; + }, + + getConvictionPercent: function () { + const calc = (level) => Math.floor(Math.min(25 + (5 * level), 150)); + if (me.expansion && this.checkInfinity()) { + return calc(12); + } + if (Skill.canUse(sdk.skills.Conviction)) { + return calc(me.getSkill(sdk.skills.Conviction, sdk.skills.subindex.SoftPoints)); + } + return 0; + }, + + // Check if a monster is immune to specified attack type + checkResist: function (unit, val, maxres = 100) { + if (!unit || !unit.type || unit.isPlayer) return true; + + const damageType = typeof val === "number" ? this.getSkillElement(val) : val; + const addLowerRes = !!(Skill.canUse(sdk.skills.LowerResist) && unit.curseable); + + // Static handler + if (val === sdk.skills.StaticField && this.getResist(unit, damageType) < 100) { + return unit.hpPercent > Config.CastStatic; + } + + // TODO: sometimes unit is out of range of conviction so need to check that + // baal in throne room doesn't have getState + if (this.infinity && ["fire", "lightning", "cold"].includes(damageType) && unit.getState) { + if (!unit.getState(sdk.states.Conviction)) { + if (addLowerRes && !unit.getState(sdk.states.LowerResist)) { + let lowerResPercent = this.getLowerResistPercent(); + return (this.getResist(unit, damageType) - (Math.floor((lowerResPercent + 85) / 5))) < 100; + } + return this.getResist(unit, damageType) < 117; + } + + return this.getResist(unit, damageType) < maxres; + } + + if (this.auradin && ["physical", "fire", "cold", "lightning"].includes(damageType) + && me.getState(sdk.states.Conviction) && unit.getState) { + let valid = false; + + // our main dps is not physical despite using zeal + if (damageType === "physical") return true; + + if (!unit.getState(sdk.states.Conviction)) { + return (this.getResist(unit, damageType) - (this.getConvictionPercent() / 5) < 100); + } + + // check unit's fire resistance + if (me.getState(sdk.states.HolyFire)) { + valid = this.getResist(unit, "fire") < maxres; + } + + // check unit's light resistance but only if the above check failed + if (me.getState(sdk.states.HolyShock) && !valid) { + valid = this.getResist(unit, "lightning") < maxres; + } + + // check unit's cold resistance but only if the above checks failed - we might be using an Ice Bow + if (me.getState(sdk.states.HolyFreeze) && !valid) { + valid = this.getResist(unit, "cold") < maxres; + } + + // TODO: maybe if still invalid at this point check physical resistance? Although if we are an auradin our physcial dps is low + + return valid; + } + + if (addLowerRes && ["fire", "lightning", "cold", "poison"].includes(damageType) && unit.getState) { + let lowerResPercent = this.getLowerResistPercent(); + if (!unit.getState(sdk.states.LowerResist)) { + return (this.getResist(unit, damageType) - (Math.floor(lowerResPercent / 5)) < 100); + } + } + + return this.getResist(unit, damageType) < maxres; + }, + + /** + * Check if we have valid skills to attack a monster + * @param {Monster} unit + * @returns {boolean} + */ + canAttack: function (unit) { + if (unit.isMonster) { + // Unique/Champion + if (unit.isSpecial) { + if (Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[1])) + || Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[2]))) { + return true; + } + } else { + if (Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[3])) + || Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[4]))) { + return true; + } + } + + if (Config.AttackSkill.length === 7) { + return Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[5])) + || Attack.checkResist(unit, this.getSkillElement(Config.AttackSkill[6])); + } + } + + return false; + }, + + /** + * Detect use of bows/crossbows + * @returns {string | false} + */ + usingBow: function () { + let item = me.getItem(-1, sdk.items.mode.Equipped); + + if (item) { + do { + if (item.isOnMain) { + switch (item.itemType) { + case sdk.items.type.Bow: + case sdk.items.type.AmazonBow: + return "bow"; + case sdk.items.type.Crossbow: + return "crossbow"; + } + } + } while (item.getNext()); + } + + return false; + }, + + /** + * Find an optimal attack position and move or walk to it + * @param {Unit} unit + * @param {number} distance + * @param {number} coll + * @param {boolean} walk + * @param {boolean} force + * @returns {boolean} sucessfully found and moved into position + */ + getIntoPosition: function (unit, distance, coll, walk, force) { + if (!unit || !unit.x || !unit.y) return false; + + walk === true && (walk = 1); + + force && console.debug("Forcing new position"); + + /** + * @todo If we've disabled tele for walking clear, allow use of tele specifically for repositioning + */ + if (distance < 4 && (!unit.hasOwnProperty("mode") || !unit.dead)) { + if (walk) { + if (unit.distance > 8 || checkCollision(me, unit, coll)) { + Pather.walkTo(unit.x, unit.y, 3); + } + } else { + Pather.moveTo(unit.x, unit.y, 0); + } + + return !CollMap.checkColl(me, unit, coll); + } + + let fullDistance = distance; + const name = unit.hasOwnProperty("name") ? unit.name : ""; + const angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); + const angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 135, -135, 180]; + const { x: orgX, y: orgY } = me; + + for (let n = 0; n < 3; n++) { + const coords = []; + n > 0 && (distance -= Math.floor(fullDistance / 3 - 1)); + + for (let currAngle of angles) { + const _angle = ((angle + currAngle) * Math.PI / 180); + let cx = Math.round((Math.cos(_angle)) * distance + unit.x); + let cy = Math.round((Math.sin(_angle)) * distance + unit.y); + + // ignore this spot as it's too close to our current position when we are forcing a new location + if (force && [cx, cy].distance < distance) continue; + if (Pather.checkSpot(cx, cy, sdk.collision.BlockWall, false)) { + coords.push({ x: cx, y: cy }); + } + } + if (!coords.length) continue; + + coords.sort(Sort.units); + + for (let i = 0; i < coords.length; i += 1) { + // Valid position found + if (!CollMap.checkColl({ x: coords[i].x, y: coords[i].y }, unit, coll, 1)) { + if (!Pather.canTeleport() && Pather.getWalkDistance(coords[i].x, coords[i].y) > unit.distance) { + if (Config.DebugMode.Path) { + console.debug( + "Skipping position due to walk distance being too far." + + "\n - DistanceToMonster: " + unit.distance + + "\n - DistanceToPosition: " + Pather.getWalkDistance(coords[i].x, coords[i].y) + ); + } + continue; + } + if ((() => { + switch (walk) { + case 1: + return Pather.walkTo(coords[i].x, coords[i].y, 2); + case 2: + if (coords[i].distance < 6 && !CollMap.checkColl(me, coords[i], sdk.collision.WallOrRanged)) { + return Pather.walkTo(coords[i].x, coords[i].y, 2); + } else { + return Pather.moveToEx(coords[i].x, coords[i].y, { retry: 1, allowPicking: !force }); + } + default: + return Pather.moveToEx(coords[i].x, coords[i].y, { retry: 1, allowPicking: !force }); + } + })()) { + if (Config.DebugMode.Path && force) { + console.debug( + "Sucessfully got into position. orginal Loc: " + orgX + "/" + orgY + + " new loc " + me.x + "/" + me.y + " distance: " + [orgX, orgY].distance + ); + } + return true; + } + } + } + } + + !!name && console.log("ÿc4Attackÿc0: No valid positions for: " + name); + + return false; + }, + + /** + * Find the nearest monster to us with optional exception parameters + * @param {{ skipBlocked?: boolean, skipImmune?: boolean, skipGid?: number}} givenSettings + * @returns {Monster | false} + */ + getNearestMonster: function (givenSettings = {}) { + const settings = Object.assign({}, { + skipBlocked: true, + skipImmune: true, + skipGid: -1, + }, givenSettings); + + let gid; + let monster = Game.getMonster(); + let range = 30; + + if (monster) { + do { + if (monster.attackable && !monster.getParent()) { + let distance = getDistance(me, monster); + + if (distance < range + && (settings.skipGid === -1 || monster.gid !== settings.skipGid) + && (!settings.skipBlocked || !checkCollision(me, monster, sdk.collision.WallOrRanged)) + && (!settings.skipImmune || Attack.canAttack(monster))) { + range = distance; + gid = monster.gid; + } + } + } while (monster.getNext()); + } + + return !!gid ? Game.getMonster(-1, -1, gid) : false; + }, + + /** + * Check valid corpse for Redemption/Horking/Summoning + * @param {Monster} unit + * @returns {boolean} valid corpse + */ + checkCorpse: function (unit) { + if (!unit || (unit.mode !== sdk.monsters.mode.Death && unit.mode !== sdk.monsters.mode.Dead)) return false; + if (unit.classid <= sdk.monsters.BurningDeadArcher2 && !getBaseStat("monstats2", unit.classid, "corpseSel")) { + return false; + } + return ([ + sdk.states.FrozenSolid, sdk.states.Revive, sdk.states.Redeemed, + sdk.states.CorpseNoDraw, sdk.states.Shatter, sdk.states.RestInPeace, sdk.states.CorpseNoSelect + ].every(state => !unit.getState(state))); + }, + + /** + * Get valid corpses for Redemption/Horking/Summoning + * @param {Monster} unit + * @param {number} range + * @returns {Monster[]} + */ + checkNearCorpses: function (unit, range = 15) { + let corpses = getUnits(sdk.unittype.Monster).filter(function (corpse) { + return getDistance(corpse, unit) <= range && Attack.checkCorpse(corpse); + }); + return corpses.length > 0 ? corpses : []; + }, + + /** + * @param {Monster} unit + * @returns {boolean} + */ + whirlwind: function (unit) { + if (!unit.attackable) return true; + + let angles = [180, 175, -175, 170, -170, 165, -165, 150, -150, 135, -135, 45, -45, 90, -90]; + + unit.isSpecial && angles.unshift(120); + + me.runwalk = me.gametype; + let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); + + // get a better spot + for (let i = 0; i < angles.length; i += 1) { + let coords = [ + Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * 4 + unit.x), + Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * 4 + unit.y) + ]; + + if (!CollMap.checkColl(me, { x: coords[0], y: coords[1] }, sdk.collision.BlockWall, 1)) { + return Skill.cast(sdk.skills.Whirlwind, sdk.skills.hand.Right, coords[0], coords[1]); + } + } + + return (Attack.validSpot(unit.x, unit.y) + && Skill.cast(sdk.skills.Whirlwind, Skill.getHand(sdk.skills.Whirlwind), me.x, me.y)); + }, + + /** + * @param {Monster} unit + * @returns {AttackResult} + */ + doPreAttack: function (unit) { + const preAttackInfo = Attack.getCustomPreAttack(unit) + ? Attack.getCustomPreAttack(unit) + : [Config.AttackSkill[0], Attack.getPrimarySlot()]; + preAttackInfo.length < 2 && preAttackInfo.push(Attack.getPrimarySlot()); + const [skill, slot] = preAttackInfo; + + if (skill > 0 + && Attack.checkResist(unit, skill) + && (!me.skillDelay || !Skill.isTimed(skill))) { + if (unit.distance > Skill.getRange(skill) || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(skill), sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + // Check if we need to charge cast - TODO: better check for charge vs not + if (Skill.charges.find(c => c.skill === skill)) { + Skill.castCharges(skill, unit); + } else { + Skill.cast(skill, Skill.getHand(skill), unit, null, null, slot); + } + + return Attack.Result.SUCCESS; + } + return Attack.Result.NOOP; + }, + + /** + * @param {Monster} unit + * @returns {boolean} + */ + doChargeCast: function (unit) { + const { skill, spectype, classids } = Config.ChargeCast; + const cRange = Skill.getRange(skill); + const cState = Skill.getState(skill); + + if (classids.length) { + /** + * @param {string | number} id + * @returns {boolean} + */ + const validId = function (id) { + return typeof id === "number" + ? unit.classid === id + : unit.name.toLowerCase().includes(id); + }; + if (!classids.some(validId)) { + return false; + } + } + + if ((!spectype || (unit.spectype & spectype)) + && (!cState || !unit.getState(cState)) + && (unit.distance < cRange || !checkCollision(me, unit, sdk.collision.LineOfSight))) { + return Skill.castCharges(skill, unit); + } + return false; + }, +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Amazon.js b/d2bs/kolbot/libs/core/Attacks/Amazon.js new file mode 100644 index 000000000..a2389b1ee --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Amazon.js @@ -0,0 +1,273 @@ +/** +* @filename Amazon.js +* @author kolton, theBGuy +* @desc Amazon attack sequence +* +*/ + +/** @implements {ClassAttack} */ +const ClassAttack = { + bowCheck: false, + lightFuryTick: 0, + + /** @param {Monster} unit */ + decideSkill: function (unit) { + let skills = { timed: -1, untimed: -1 }; + if (!unit) return skills; + + let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + let classid = unit.classid; + + // Get timed skill + let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + skills.timed = checkSkill; + } else if (Config.AttackSkill[5] > -1 + && Attack.checkResist(unit, Config.AttackSkill[5]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { + skills.timed = Config.AttackSkill[5]; + } + + // Get untimed skill + checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill)) { + skills.untimed = checkSkill; + } else if (Config.AttackSkill[6] > -1 + && Attack.checkResist(unit, Config.AttackSkill[6]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { + skills.untimed = Config.AttackSkill[6]; + } + + // Low mana timed skill + if (Config.LowManaSkill[0] > -1 + && Skill.getManaCost(skills.timed) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + skills.timed = Config.LowManaSkill[0]; + } + + // Low mana untimed skill + if (Config.LowManaSkill[1] > -1 + && Skill.getManaCost(skills.untimed) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[1])) { + skills.untimed = Config.LowManaSkill[1]; + } + + return skills; + }, + + /** + * @param {Monster | Player} unit + * @param {boolean} [preattack] + * @returns {AttackResult} + */ + doAttack: function (unit, preattack) { + if (!unit) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + let gid = unit.gid; + let needRepair = me.needRepair(); + + if ((Config.MercWatch && me.needMerc()) || needRepair.length > 0) { + console.log("towncheck"); + + if (Town.visitTown(!!needRepair.length)) { + // lost reference to the mob we were attacking + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + return Attack.Result.SUCCESS; + } + } + } + + if (Config.ChargeCast.skill > -1) { + Attack.doChargeCast(unit); + } + + if (preattack) { + let preAttackResult = Attack.doPreAttack(unit); + if (preAttackResult !== Attack.Result.NOOP) { + return preAttackResult; + } + } + + if (Skill.canUse(sdk.skills.InnerSight)) { + if (!unit.getState(sdk.states.InnerSight) + && unit.distance > 3 && unit.distance < 13 + && !checkCollision(me, unit, sdk.collision.Ranged)) { + Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit); + } + } + + if (Skill.canUse(sdk.skills.SlowMissiles)) { + if (!unit.getState(sdk.states.SlowMissiles)) { + if ((unit.distance > 3 || unit.getEnchant(sdk.enchant.LightningEnchanted)) + && unit.distance < 13 && !checkCollision(me, unit, sdk.collision.Ranged)) { + // Act Bosses and mini-bosses are immune to Slow Missles and pointless to use on lister or Cows, Use Inner-Sight instead + if ([sdk.monsters.HellBovine].includes(unit.classid) || unit.isBoss) { + // Check if already in this state + if (!unit.getState(sdk.states.InnerSight) && Config.UseInnerSight && Skill.canUse(sdk.skills.InnerSight)) { + Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit); + } + } else { + Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit); + } + } + } + } + + let mercRevive = 0; + let skills = this.decideSkill(unit); + let result = this.doCast(unit, skills.timed, skills.untimed); + + if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { + let merc = me.getMerc(); + + while (unit.attackable) { + if (!unit || !copyUnit(unit).x) { + unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); + } + if (!unit) return Attack.Result.SUCCESS; + + if (me.needMerc()) { + if (Config.MercWatch && mercRevive++ < 1) { + Town.visitTown(); + } else { + return Attack.Result.CANTATTACK; + } + + (merc === undefined || !merc) && (merc = me.getMerc()); + } + + if (!!merc && getDistance(merc, unit) > 5) { + Pather.moveToUnit(unit); + + let spot = Attack.findSafeSpot(unit, 10, 5, 9); + !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); + } + + let closeMob = Attack.getNearestMonster({ skipGid: gid }); + + if (!!closeMob) { + let findSkill = this.decideSkill(closeMob); + if (this.doCast(closeMob, findSkill.timed, findSkill.untimed) !== Attack.Result.SUCCESS) { + (Skill.canUse(sdk.skills.Decoy) && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, unit)); + } + } + } + + return Attack.Result.SUCCESS; + } + + return result; + }, + + afterAttack: function () { + Precast.doPrecast(false); + + let needRepair = (me.needRepair() || []); + + // Repair check, mainly to restock arrows + needRepair.length > 0 && Town.visitTown(true); + + this.lightFuryTick = 0; + }, + + /** + * @param {Monster | Player} unit + * @param {number} timedSkill + * @param {number} untimedSkill + * @returns {AttackResult} 0 - fail, 1 - success, 2 - no valid attack skills + */ + doCast: function (unit, timedSkill = -1, untimedSkill = -1) { + // No valid skills can be found + if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; + Config.TeleSwitch && me.switchToPrimary(); + + // Arrow/bolt check + if (this.bowCheck) { + switch (true) { + case this.bowCheck === "bow" && !me.getItem("aqv", sdk.items.mode.Equipped): + case this.bowCheck === "crossbow" && !me.getItem("cqv", sdk.items.mode.Equipped): + console.log("Bow check"); + Town.visitTown(); + + break; + } + } + + let walk; + + if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { + switch (timedSkill) { + case sdk.skills.LightningFury: + if (!this.lightFuryTick || getTickCount() - this.lightFuryTick > Config.LightningFuryDelay * 1000) { + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + if (!unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit)) { + ClassAttack.lightFuryTick = getTickCount(); + } + + return Attack.Result.SUCCESS; + } + + break; + default: + if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, unit.classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(timedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); + + return Attack.Result.SUCCESS; + } + } + + if (untimedSkill > -1) { + if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, unit.classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(untimedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); + + return Attack.Result.SUCCESS; + } + + Misc.poll(() => !me.skillDelay, 1000, 40); + + // Wait for Lightning Fury timeout + while (timedSkill === sdk.skills.LightningFury + && this.lightFuryTick && getTickCount() - this.lightFuryTick < Config.LightningFuryDelay * 1000) { + delay(40); + } + + return Attack.Result.SUCCESS; + } +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Assassin.js b/d2bs/kolbot/libs/core/Attacks/Assassin.js new file mode 100644 index 000000000..e1725cfa1 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Assassin.js @@ -0,0 +1,288 @@ +/** +* @filename Assassin.js +* @author kolton, theBGuy +* @desc Assassin attack sequence +* +*/ + +const ClassAttack = { + lastTrapPos: {}, + trapRange: 20, + + /** + * @param {Monster} unit + * @param {boolean} preattack + * @returns {AttackResult} + */ + doAttack: function (unit, preattack) { + if (!unit) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + let gid = unit.gid; + + if (Config.MercWatch && me.needMerc()) { + console.log("mercwatch"); + + if (Town.visitTown()) { + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + return Attack.Result.SUCCESS; // lost reference to the mob we were attacking + } + } + } + + if (Config.ChargeCast.skill > -1) { + Attack.doChargeCast(unit); + } + + if (preattack) { + let preAttackResult = Attack.doPreAttack(unit); + if (preAttackResult !== Attack.Result.NOOP) { + return preAttackResult; + } + } + + let mercRevive = 0; + let timedSkill = -1; + let untimedSkill = -1; + let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + let classid = unit.classid; + + // Cloak of Shadows (Aggressive) - can't be cast again until previous one runs out and next to useless if cast in precast sequence (won't blind anyone) + if (Config.AggressiveCloak && Skill.canUse(sdk.skills.CloakofShadows) + && !me.skillDelay && !me.getState(sdk.states.CloakofShadows)) { + if (unit.distance < 20) { + Skill.cast(sdk.skills.CloakofShadows, sdk.skills.hand.Right); + } else if (!Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + let checkTraps = this.checkTraps(unit); + + if (checkTraps) { + if (unit.distance > this.trapRange || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, this.trapRange, sdk.collision.Ranged) + || (checkCollision(me, unit, sdk.collision.BlockWall) + && (getCollision(me.area, unit.x, unit.y) & sdk.collision.BlockWall))) { + return Attack.Result.FAILED; + } + } + + this.placeTraps(unit, checkTraps); + } + + // Cloak of Shadows (Defensive; default) - can't be cast again until previous one runs out and next to useless if cast in precast sequence (won't blind anyone) + if (!Config.AggressiveCloak && Skill.canUse(sdk.skills.CloakofShadows) + && unit.distance < 20 && !me.skillDelay && !me.getState(sdk.states.CloakofShadows)) { + Skill.cast(sdk.skills.CloakofShadows, sdk.skills.hand.Right); + } + + // Get timed skill + let checkSkill = Attack.getCustomAttack(unit) + ? Attack.getCustomAttack(unit)[0] + : Config.AttackSkill[index]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + timedSkill = checkSkill; + } else if (Config.AttackSkill[5] > -1 + && Attack.checkResist(unit, Config.AttackSkill[5]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { + timedSkill = Config.AttackSkill[5]; + } + + // Get untimed skill + checkSkill = Attack.getCustomAttack(unit) + ? Attack.getCustomAttack(unit)[1] + : Config.AttackSkill[index + 1]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + untimedSkill = checkSkill; + } else if (Config.AttackSkill[6] > -1 + && Attack.checkResist(unit, Config.AttackSkill[6]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { + untimedSkill = Config.AttackSkill[6]; + } + + // Low mana timed skill + if (Config.LowManaSkill[0] > -1 + && Skill.getManaCost(timedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + timedSkill = Config.LowManaSkill[0]; + } + + // Low mana untimed skill + if (Config.LowManaSkill[1] > -1 + && Skill.getManaCost(untimedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[1])) { + untimedSkill = Config.LowManaSkill[1]; + } + + let result = this.doCast(unit, timedSkill, untimedSkill); + + if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { + let merc = me.getMerc(); + + while (unit.attackable) { + if (!unit || !copyUnit(unit).x) { + unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); + } + if (!unit) return Attack.Result.SUCCESS; + + if (me.needMerc()) { + if (Config.MercWatch && mercRevive++ < 1) { + Town.visitTown(); + } else { + return Attack.Result.CANTATTACK; + } + + (merc === undefined || !merc) && (merc = me.getMerc()); + } + + if (!!merc && getDistance(merc, unit) > 5) { + Pather.moveToUnit(unit); + + let spot = Attack.findSafeSpot(unit, 10, 5, 9); + !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); + } + + let closeMob = Attack.getNearestMonster({ skipGid: gid }); + !!closeMob && this.doCast(closeMob, timedSkill, untimedSkill); + } + + return Attack.Result.SUCCESS; + } + + return result; + }, + + afterAttack: function () { + Precast.doPrecast(false); + }, + + /** + * @param {Monster} unit + * @param {number} timedSkill + * @param {number} untimedSkill + * @returns {AttackResult} + */ + doCast: function (unit, timedSkill = -1, untimedSkill = -1) { + // No valid skills can be found + if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; + // unit became invalidated + if (!unit || !unit.attackable) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + + let walk; + let classid = unit.classid; + + if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { + switch (timedSkill) { + case sdk.skills.Whirlwind: + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.BlockWall)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.BlockWall)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Attack.whirlwind(unit); + + return Attack.Result.SUCCESS; + default: + if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(timedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); + + return Attack.Result.SUCCESS; + } + } + + if (untimedSkill > -1) { + if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(untimedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); + + return Attack.Result.SUCCESS; + } + + Misc.poll(() => !me.skillDelay, 1000, 40); + + return Attack.Result.SUCCESS; + }, + + checkTraps: function (unit) { + if (!Config.UseTraps || !unit) return false; + + // getDistance crashes when using an object with x, y props, that's why it's unit.x, unit.y and not unit + // is this still a thing ^^? todo: test it + if (me.getMinionCount(sdk.summons.type.AssassinTrap) === 0 || !this.lastTrapPos.hasOwnProperty("x") + || getDistance(unit.x, unit.y, this.lastTrapPos.x, this.lastTrapPos.y) > 15) { + return 5; + } + + return 5 - me.getMinionCount(sdk.summons.type.AssassinTrap); + }, + + // todo - either import soloplays immune to trap check or add config option for immune to traps + // since this is the base file probably better to leave the option available rather than hard code it + // check if unit is still attackable after each cast? + placeTraps: function (unit, amount = 5) { + let traps = 0; + this.lastTrapPos = { x: unit.x, y: unit.y }; + + for (let i = -1; i <= 1; i += 1) { + for (let j = -1; j <= 1; j += 1) { + // used for X formation + if (Math.abs(i) === Math.abs(j)) { + // unit can be an object with x, y props too, that's why having "mode" prop is checked + if (traps >= amount || (unit.hasOwnProperty("mode") && unit.dead)) return true; + + // Duriel, Mephisto, Diablo, Baal, other players - why not andy? + if ((unit.hasOwnProperty("classid") + && [ + sdk.monsters.Duriel, sdk.monsters.Mephisto, sdk.monsters.Diablo, sdk.monsters.Baal + ].includes(unit.classid)) + || (unit.hasOwnProperty("type") && unit.isPlayer)) { + if (traps >= Config.BossTraps.length) return true; + + Skill.cast(Config.BossTraps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j); + } else { + if (traps >= Config.Traps.length) return true; + + Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j); + } + + traps += 1; + } + } + } + + return true; + }, +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Barbarian.js b/d2bs/kolbot/libs/core/Attacks/Barbarian.js new file mode 100644 index 000000000..f6af603d6 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Barbarian.js @@ -0,0 +1,290 @@ +/** +* @filename Barbarian.js +* @author kolton, theBGuy +* @desc Barbarian attack sequence +* +*/ + +/** + * @todo + * - Add howl + */ + +const ClassAttack = { + /** + * @param {Monster} unit + * @param {boolean} preattack + * @returns {AttackResult} + */ + doAttack: function (unit, preattack = false) { + if (!unit) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + let gid = unit.gid; + let needRepair = me.needRepair(); + + if ((Config.MercWatch && me.needMerc()) || needRepair.length > 0) { + console.log("towncheck"); + + if (Town.visitTown(!!needRepair.length)) { + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + return Attack.Result.SUCCESS; // lost reference to the mob we were attacking + } + } + } + + if (Config.ChargeCast.skill > -1) { + Attack.doChargeCast(unit); + } + + if (preattack) { + let preAttackResult = Attack.doPreAttack(unit); + if (preAttackResult !== Attack.Result.NOOP) { + return preAttackResult; + } + } + + let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + let attackSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; + + if (!Attack.checkResist(unit, attackSkill)) { + attackSkill = -1; + + if (Config.AttackSkill[index + 1] > -1 && Attack.checkResist(unit, Config.AttackSkill[index + 1])) { + attackSkill = Config.AttackSkill[index + 1]; + } + } + + // Low mana skill + if (Skill.getManaCost(attackSkill) > me.mp + && Config.LowManaSkill[0] > -1 + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + attackSkill = Config.LowManaSkill[0]; + } + + // low weapon-quantity -> use secondary skill if we can + if (attackSkill === sdk.skills.DoubleThrow + && (me.getWeaponQuantity() <= 3 || me.getWeaponQuantity(sdk.body.LeftArm) <= 3) + && Skill.canUse(Config.AttackSkill[index + 1]) && Attack.checkResist(unit, Config.AttackSkill[index + 1])) { + attackSkill = Config.AttackSkill[index + 1]; + } + + // Telestomp with barb is pointless + return this.doCast(unit, attackSkill); + }, + + /** + * Check if we need to precast, repair items, or perform findItem + * @param {boolean} pickit - determines if we use findItem or not + */ + afterAttack: function (pickit = true) { + Precast.doPrecast(false); + + let needRepair = (me.needRepair() || []); + + // Repair check + needRepair.length > 0 && Town.visitTown(true); + pickit && this.findItem(me.inArea(sdk.areas.Travincal) ? 60 : 20); + }, + + /** + * @param {Monster} unit + * @param {number} attackSkill + * @returns {AttackResult} + */ + doCast: function (unit, attackSkill = -1) { + if (attackSkill < 0) return Attack.Result.CANTATTACK; + // check if unit became invalidated + if (!unit || !unit.attackable) return Attack.Result.SUCCESS; + (Config.TeleSwitch || Config.FindItemSwitch) && me.switchToPrimary(); + + switch (attackSkill) { + case sdk.skills.Whirlwind: + /** + * @todo we sometimes struggle getting into position because of monsters which is dumb since we can + * just whirl through them, so that needs to be fixed + */ + if (unit.distance > Skill.getRange(attackSkill) || checkCollision(me, unit, sdk.collision.BlockWall)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.BlockWall, 2)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Attack.whirlwind(unit); + + return Attack.Result.SUCCESS; + default: + if (Skill.getRange(attackSkill) < 4 && !Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(attackSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + let walk = (Skill.getRange(attackSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); + + return Attack.Result.SUCCESS; + } + }, + + /** + * Check whether there are any monsters in range that are attackable + * @param {number} range + * @returns {boolean} + */ + checkCloseMonsters: function (range = 10) { + const [mainAttElm, secAttElm] = [ + Attack.getSkillElement(Config.AttackSkill[1]), + Attack.getSkillElement(Config.AttackSkill[3]), + ]; + let monster = Game.getMonster(); + + if (monster) { + do { + if (monster.distance <= range + && monster.attackable + && !checkCollision(me, monster, sdk.collision.Ranged) + && ( + Attack.checkResist(monster, monster.isSpecial ? mainAttElm : secAttElm) + || (Config.AttackSkill[3] > -1 && Attack.checkResist(monster, secAttElm)) + ) + ) { + return true; + } + } while (monster.getNext()); + } + + return false; + }, + + /** + * Use findItem skill to hork bodies + * @param {number} range + * @returns {boolean} + */ + findItem: function (range = 10) { + if (!Skill.canUse(sdk.skills.FindItem)) return false; + + let corpseList = []; + const { x: orgX, y: orgY } = me; + + MainLoop: + for (let i = 0; i < 3; i += 1) { + let corpse = Game.getMonster(); + + if (corpse) { + do { + if (corpse.dead && getDistance(corpse, orgX, orgY) <= range && this.checkCorpse(corpse)) { + corpseList.push(copyUnit(corpse)); + } + } while (corpse.getNext()); + } + + while (corpseList.length > 0) { + if (this.checkCloseMonsters(5)) { + console.debug("Monsters nearby, clearing"); + Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot()); + Attack.clear(10, false, false, false, false); + Pather.moveToEx(orgX, orgY, { allowPicking: false }); + + continue MainLoop; + } + + corpseList.sort(Sort.units); + const check = corpseList.shift(); + let attempted = false; + let invalidated = false; + // get the actual corpse rather than the copied unit + corpse = Game.getMonster(check.classid, sdk.monsters.mode.Dead, check.gid); + + if (this.checkCorpse(corpse)) { + if (corpse.distance > 30 || checkCollision(me, corpse, sdk.collision.BlockWall)) { + Pather.moveNearUnit(corpse, 5); + } + Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot() ^ 1); + + CorpseLoop: + for (let j = 0; j < 3; j += 1) { + // sometimes corpse can become invalidated - necro summoned from it or baal wave clearing, ect + // this still doesn't seem to capture baal wave clearing + if (j > 0) { + corpse = Game.getMonster(check.classid, sdk.monsters.mode.Dead, check.gid); + if (!this.checkCorpse(corpse)) { + invalidated = true; + break; + } + } + // see if we can find a new position if we failed the first time - sometimes findItem is bugged + j > 0 && Attack.getIntoPosition(corpse, 5, sdk.collision.BlockWall, Pather.useTeleport(), true); + // only delay if we actually casted the skill + if (Skill.cast(sdk.skills.FindItem, sdk.skills.hand.Right, corpse)) { + let tick = getTickCount(); + attempted = true; + + while (getTickCount() - tick < 1000) { + if (corpse.getState(sdk.states.CorpseNoSelect)) { + Config.FastPick ? Pickit.fastPick() : Pickit.pickItems(range); + + break CorpseLoop; + } + + delay(10); + } + } + } + + if (attempted && !invalidated && corpse && !corpse.getState(sdk.states.CorpseNoSelect)) { + // if (!me.inArea(sdk.areas.ThroneofDestruction)) { + // D2Bot.printToConsole("Failed to hork " + JSON.stringify(corpse) + " at " + getAreaName(me.area)); + // } + console.debug("Failed to hork " + JSON.stringify(corpse) + " at " + getAreaName(me.area)); + } + } + } + } + + Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot()); + + return true; + }, + + /** + * Check if corpse is horkable + * @param {Monster} unit + * @returns {boolean} + */ + checkCorpse: function (unit) { + if (!unit || !copyUnit(unit).x || !unit.dead) { + return false; + } + if ([sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3].indexOf(unit.classid) === -1 + && unit.spectype === sdk.monsters.spectype.All) { + // why ignore all normal monsters? + return false; + } + + // monstats2 doesn't contain guest monsters info. sigh.. + if (unit.classid <= sdk.monsters.BurningDeadArcher2 + && !getBaseStat("monstats2", unit.classid, "corpseSel")) { + return false; + } + + let states = [ + sdk.states.FrozenSolid, sdk.states.Revive, sdk.states.Redeemed, + sdk.states.CorpseNoDraw, sdk.states.Shatter, sdk.states.RestInPeace, sdk.states.CorpseNoSelect + ]; + + return (unit.distance <= 25 + && !checkCollision(me, unit, sdk.collision.Ranged) + && states.every(function (state) { + return !unit.getState(state); + })); + } +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Druid.js b/d2bs/kolbot/libs/core/Attacks/Druid.js new file mode 100644 index 000000000..f735f74dd --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Druid.js @@ -0,0 +1,201 @@ +/** +* @filename Druid.js +* @author kolton, theBGuy +* @desc Druid attack sequence +* +*/ + +const ClassAttack = { + doAttack: function (unit, preattack = false) { + if (!unit) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + let gid = unit.gid; + + if (Config.MercWatch && me.needMerc()) { + console.log("mercwatch"); + + if (Town.visitTown()) { + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + return Attack.Result.SUCCESS; // lost reference to the mob we were attacking + } + } + } + + // Rebuff Hurricane + if (Skill.canUse(sdk.skills.Hurricane) && !me.getState(sdk.states.Hurricane)) { + Skill.cast(sdk.skills.Hurricane, sdk.skills.hand.Right); + } + // Rebuff Cyclone Armor + if (Skill.canUse(sdk.skills.CycloneArmor) && !me.getState(sdk.states.CycloneArmor)) { + Skill.cast(sdk.skills.CycloneArmor, sdk.skills.hand.Right); + } + + if (Config.ChargeCast.skill > -1) { + Attack.doChargeCast(unit); + } + + if (preattack) { + let preAttackResult = Attack.doPreAttack(unit); + if (preAttackResult !== Attack.Result.NOOP) { + return preAttackResult; + } + } + + let mercRevive = 0; + let timedSkill = -1; + let untimedSkill = -1; + let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + let classid = unit.classid; + + // Get timed skill + let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + timedSkill = checkSkill; + } else if (Config.AttackSkill[5] > -1 + && Attack.checkResist(unit, Config.AttackSkill[5]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { + timedSkill = Config.AttackSkill[5]; + } + + // Get untimed skill + checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + untimedSkill = checkSkill; + } else if (Config.AttackSkill[6] > -1 + && Attack.checkResist(unit, Config.AttackSkill[6]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { + untimedSkill = Config.AttackSkill[6]; + } + + // Low mana timed skill + if (Config.LowManaSkill[0] > -1 + && Skill.getManaCost(timedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + timedSkill = Config.LowManaSkill[0]; + } + + // Low mana untimed skill + if (Config.LowManaSkill[1] > -1 + && Skill.getManaCost(untimedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[1])) { + untimedSkill = Config.LowManaSkill[1]; + } + + let result = this.doCast(unit, timedSkill, untimedSkill); + + if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { + let merc = me.getMerc(); + + while (unit.attackable) { + if (!unit || !copyUnit(unit).x) { + unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); + } + if (!unit) return Attack.Result.SUCCESS; + + if (me.needMerc()) { + if (Config.MercWatch && mercRevive++ < 1) { + Town.visitTown(); + } else { + return Attack.Result.CANTATTACK; + } + + (merc === undefined || !merc) && (merc = me.getMerc()); + } + + if (!!merc && getDistance(merc, unit) > 5) { + Pather.moveToUnit(unit); + + let spot = Attack.findSafeSpot(unit, 10, 5, 9); + !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); + } + + let closeMob = Attack.getNearestMonster({ skipGid: gid }); + !!closeMob && this.doCast(closeMob, timedSkill, untimedSkill); + } + + return Attack.Result.SUCCESS; + } + + return result; + }, + + afterAttack: function () { + Precast.doPrecast(false); + }, + + // Returns: 0 - fail, 1 - success, 2 - no valid attack skills + doCast: function (unit, timedSkill = -1, untimedSkill = -1) { + // No valid skills can be found + if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; + // unit became invalidated + if (!unit || !unit.attackable) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + + let walk; + let classid = unit.classid; + + if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { + switch (timedSkill) { + case sdk.skills.Tornado: + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + // Randomized x coord changes tornado path and prevents constant missing + !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit.x + rand(-2, 2), unit.y); + + return Attack.Result.SUCCESS; + default: + if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(timedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); + + return Attack.Result.SUCCESS; + } + } + + if (untimedSkill > -1) { + if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(untimedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); + + return Attack.Result.SUCCESS; + } + + Misc.poll(() => !me.skillDelay, 1000, 40); + + return Attack.Result.SUCCESS; + } +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Necromancer.js b/d2bs/kolbot/libs/core/Attacks/Necromancer.js new file mode 100644 index 000000000..6503bd420 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Necromancer.js @@ -0,0 +1,574 @@ +/** +* @filename Necromancer.js +* @author kolton, theBGuy +* @desc Necromancer attack sequence +* +*/ + +const ClassAttack = { + novaTick: 0, + maxSkeletons: 0, + maxMages: 0, + maxRevives: 0, + + setArmySize: function () { + ClassAttack.maxSkeletons = Config.Skeletons === "max" + ? Skill.getMaxSummonCount(sdk.skills.RaiseSkeleton) + : Config.Skeletons; + ClassAttack.maxMages = Config.SkeletonMages === "max" + ? Skill.getMaxSummonCount(sdk.skills.RaiseSkeletalMage) + : Config.SkeletonMages; + ClassAttack.maxRevives = Config.Revives === "max" + ? Skill.getMaxSummonCount(sdk.skills.Revive) + : Config.Revives; + }, + + /** + * @returns {boolean} true - doesn't use summons or has all he can summon, false - not full of summons yet + */ + isArmyFull: function () { + // This necro doesn't summon anything so assume he's full + if (Config.Skeletons + Config.SkeletonMages + Config.Revives === 0) { + return true; + } + + // Make sure we have a current count of summons needed + this.setArmySize(); + + // See if we're at full army count + if ((me.getMinionCount(sdk.summons.type.Skeleton) < this.maxSkeletons) + && (me.getMinionCount(sdk.summons.type.SkeletonMage) < this.maxMages) + && (me.getMinionCount(sdk.summons.type.Revive) < this.maxRevives)) { + return false; + } + + // If we got this far this necro has all the summons he needs + return true; + }, + + /** + * Check if we can use specific curse on monster + * @param {Monster} unit + * @param {number} curseID + * @returns {boolean} + */ + canCurse: function (unit, curseID) { + if (unit === undefined || unit.dead || !Skill.canUse(curseID)) return false; + + let state = (() => { + switch (curseID) { + case sdk.skills.AmplifyDamage: + return sdk.states.AmplifyDamage; + case sdk.skills.DimVision: + // dim doesn't work on oblivion knights + if ([ + sdk.monsters.OblivionKnight1, sdk.monsters.OblivionKnight2, sdk.monsters.OblivionKnight3 + ].includes(unit.classid)) { + return false; + } + return sdk.states.DimVision; + case sdk.skills.Weaken: + return sdk.states.Weaken; + case sdk.skills.IronMaiden: + return sdk.states.IronMaiden; + case sdk.skills.Terror: + return unit.scareable ? sdk.states.Terror : false; + case sdk.skills.Confuse: + // doens't work on specials + return unit.scareable ? sdk.states.Confuse : false; + case sdk.skills.LifeTap: + return sdk.states.LifeTap; + case sdk.skills.Attract: + // doens't work on specials + return unit.scareable ? sdk.states.Attract : false; + case sdk.skills.Decrepify: + return sdk.states.Decrepify; + case sdk.skills.LowerResist: + return sdk.states.LowerResist; + default: + console.warn("(ÿc9canCurse) :: ÿc1Invalid Curse ID: " + curseID); + + return false; + } + })(); + + return state ? !unit.getState(state) : false; + }, + + /** + * @param {Monster} unit + * @returns {number | boolean} + */ + getCustomCurse: function (unit) { + if (Config.CustomCurse.length <= 0) return false; + + let curse = Config.CustomCurse + .findIndex(function (unitID) { + if ((typeof unitID[0] === "number" && unit.classid && unit.classid === unitID[0]) + || (typeof unitID[0] === "string" && unit.name && unit.name.toLowerCase() === unitID[0].toLowerCase())) { + return true; + } + return false; + }); + if (curse > -1) { + // format [id, curse, spectype] + if (Config.CustomCurse[curse].length === 3) { + return ((unit.spectype & Config.CustomCurse[curse][2]) ? Config.CustomCurse[curse][1] : false); + } else { + return Config.CustomCurse[curse][1]; + } + } + + return false; + }, + + /** + * @param {Monster} unit + * @param {boolean} preattack + * @returns {number} 0 - fail, 1 - success, 2 - no valid attack skills + */ + doAttack: function (unit, preattack = false) { + if (!unit || unit.dead) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + + let mercRevive = 0; + let gid = unit.gid; + let classid = unit.classid; + let [timedSkill, untimedSkill, customCurse] = [-1, -1, -1]; + const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + + if (Config.MercWatch && me.needMerc()) { + console.log("mercwatch"); + + if (Town.visitTown()) { + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + return Attack.Result.SUCCESS; // lost reference to the mob we were attacking + } + } + } + + if (preattack) { + let preAttackResult = Attack.doPreAttack(unit); + if (preAttackResult !== Attack.Result.NOOP) { + return preAttackResult; + } + } + + // only continue if we can actually curse the unit otherwise its a waste of time + if (unit.curseable) { + customCurse = this.getCustomCurse(unit); + + if (customCurse && this.canCurse(unit, customCurse)) { + if (unit.distance > 25 || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, 25, sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + Skill.cast(customCurse, sdk.skills.hand.Right, unit); + + return Attack.Result.SUCCESS; + } else if (!customCurse) { + if (Config.Curse[0] > 0 && unit.isSpecial && this.canCurse(unit, Config.Curse[0])) { + if (unit.distance > 25 || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, 25, sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + Skill.cast(Config.Curse[0], sdk.skills.hand.Right, unit); + + return Attack.Result.SUCCESS; + } + + if (Config.Curse[1] > 0 && !unit.isSpecial && this.canCurse(unit, Config.Curse[1])) { + if (unit.distance > 25 || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, 25, sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + Skill.cast(Config.Curse[1], sdk.skills.hand.Right, unit); + + return Attack.Result.SUCCESS; + } + } + } + + // Get timed skill + let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + timedSkill = checkSkill; + } else if (Config.AttackSkill[5] > -1 + && Attack.checkResist(unit, Config.AttackSkill[5]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { + timedSkill = Config.AttackSkill[5]; + } + + // Get untimed skill + checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + untimedSkill = checkSkill; + } else if (Config.AttackSkill[6] > -1 + && Attack.checkResist(unit, Config.AttackSkill[6]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { + untimedSkill = Config.AttackSkill[6]; + } + + // Low mana timed skill + if (Config.LowManaSkill[0] > -1 + && Skill.getManaCost(timedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + timedSkill = Config.LowManaSkill[0]; + } + + // Low mana untimed skill + if (Config.LowManaSkill[1] > -1 + && Skill.getManaCost(untimedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[1])) { + untimedSkill = Config.LowManaSkill[1]; + } + + let result = this.doCast(unit, timedSkill, untimedSkill); + + if (result === 1) { + Config.ActiveSummon && this.raiseArmy(); + this.explodeCorpses(unit); + } else if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { + let merc = me.getMerc(); + + while (unit.attackable) { + if (!unit || !copyUnit(unit).x) { + unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); + } + if (!unit) return Attack.Result.SUCCESS; + + if (me.needMerc()) { + if (Config.MercWatch && mercRevive++ < 1) { + Town.visitTown(); + } else { + return Attack.Result.CANTATTACK; + } + + (merc === undefined || !merc) && (merc = me.getMerc()); + } + + if (!!merc && getDistance(merc, unit) > 5) { + Pather.moveToUnit(unit); + + let spot = Attack.findSafeSpot(unit, 10, 5, 9); + !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); + } + + Config.ActiveSummon && this.raiseArmy(); + this.explodeCorpses(unit); + let closeMob = Attack.getNearestMonster({ skipGid: gid }); + !!closeMob && this.doCast(closeMob, timedSkill, untimedSkill); + } + + return Attack.Result.SUCCESS; + } + + return result; + }, + + afterAttack: function () { + Precast.doPrecast(false); + this.raiseArmy(); + this.novaTick = 0; + }, + + /** + * @param {Monster} unit + * @param {number} timedSkill + * @param {number} untimedSkill + * @returns {number} 0 - fail, 1 - success, 2 - no valid attack skills + */ + doCast: function (unit, timedSkill = -1, untimedSkill = -1) { + // No valid skills can be found + if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; + // unit became invalidated + if (!unit || !unit.attackable) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + + let walk; + let classid = unit.classid; + + // Check for bodies to exploit for CorpseExplosion before committing to an attack for non-summoner type necros + this.isArmyFull() && this.checkCorpseNearMonster(unit) && this.explodeCorpses(unit); + + if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) { + switch (timedSkill) { + case sdk.skills.PoisonNova: + if (!this.novaTick || getTickCount() - this.novaTick > Config.PoisonNovaDelay * 1000) { + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + if (!unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit)) { + this.novaTick = getTickCount(); + } + } + + break; + case sdk.skills.Summoner: // Pure Summoner + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + delay(300); + + break; + default: + if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + let walk = (Skill.getRange(timedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); + + break; + } + } + + if (untimedSkill > -1) { + if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(untimedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); + + return Attack.Result.SUCCESS; + } + + Misc.poll(() => !me.skillDelay, 1000, 40); + + // Delay for Poison Nova + while (timedSkill === sdk.skills.PoisonNova + && this.novaTick && getTickCount() - this.novaTick < Config.PoisonNovaDelay * 1000) { + delay(40); + } + + return Attack.Result.SUCCESS; + }, + + /** + * @param {number} range + */ + raiseArmy: function (range = 25) { + let tick, count; + + this.setArmySize(); + + for (let i = 0; i < 3; i += 1) { + let corpse = Game.getMonster(-1, sdk.monsters.mode.Dead); + let corpseList = []; + + if (corpse) { + do { + // within casting distance + if (corpse.distance <= range && this.checkCorpse(corpse)) { + corpseList.push(copyUnit(corpse)); + } + } while (corpse.getNext()); + } + + while (corpseList.length > 0) { + corpse = corpseList.shift(); + + // should probably have a way to priortize which ones we summon first + if (me.getMinionCount(sdk.summons.type.Skeleton) < this.maxSkeletons) { + if (!Skill.cast(sdk.skills.RaiseSkeleton, sdk.skills.hand.Right, corpse)) { + return false; + } + + count = me.getMinionCount(sdk.summons.type.Skeleton); + tick = getTickCount(); + + while (getTickCount() - tick < 200) { + if (me.getMinionCount(sdk.summons.type.Skeleton) > count) { + break; + } + + delay(10); + } + } else if (me.getMinionCount(sdk.summons.type.SkeletonMage) < this.maxMages) { + if (!Skill.cast(sdk.skills.RaiseSkeletalMage, sdk.skills.hand.Right, corpse)) { + return false; + } + + count = me.getMinionCount(sdk.summons.type.SkeletonMage); + tick = getTickCount(); + + while (getTickCount() - tick < 200) { + if (me.getMinionCount(sdk.summons.type.SkeletonMage) > count) { + break; + } + + delay(10); + } + } else if (me.getMinionCount(sdk.summons.type.Revive) < this.maxRevives) { + if (this.checkCorpse(corpse, true)) { + console.log("Reviving " + corpse.name); + + if (!Skill.cast(sdk.skills.Revive, sdk.skills.hand.Right, corpse)) { + return false; + } + + count = me.getMinionCount(sdk.summons.type.Revive); + tick = getTickCount(); + + while (getTickCount() - tick < 200) { + if (me.getMinionCount(sdk.summons.type.Revive) > count) { + break; + } + + delay(10); + } + } + } else { + return true; + } + } + } + + return true; + }, + + /** + * @param {Monster} unit + */ + explodeCorpses: function (unit) { + if (Config.ExplodeCorpses === 0 || unit.dead) return false; + + let corpseList = []; + let range = Math.floor((me.getSkill(Config.ExplodeCorpses, sdk.skills.subindex.SoftPoints) + 7) / 3); + let corpse = Game.getMonster(-1, sdk.monsters.mode.Dead); + + if (corpse) { + do { + if (getDistance(unit, corpse) <= range && this.checkCorpse(corpse)) { + corpseList.push(copyUnit(corpse)); + } + } while (corpse.getNext()); + + // Shuffle the corpseList so if running multiple necrobots they explode separate corpses not the same ones + corpseList.length > 1 && (corpseList = corpseList.shuffle()); + + if (this.isArmyFull()) { + // We don't need corpses as we are not a Summoner Necro, Spam CE till monster dies or we run out of bodies. + do { + corpse = corpseList.shift(); + + if (corpse) { + if (!unit.dead && this.checkCorpse(corpse) && getDistance(corpse, unit) <= range) { + // Added corpse ID so I can see when it blows another monster with the same ClassID and Name + me.overhead("Exploding: " + corpse.classid + " " + corpse.name + " id:" + corpse.gid); + + if (Skill.cast(Config.ExplodeCorpses, sdk.skills.hand.Right, corpse)) { + delay(me.ping + 1); + } + } + } + } while (corpseList.length > 0); + } else { + // We are a Summoner Necro, we should conserve corpses, only blow 2 at a time so we can check for needed re-summons. + for (let i = 0; i <= 1; i += 1) { + if (corpseList.length > 0) { + corpse = corpseList.shift(); + + if (corpse) { + me.overhead("Exploding: " + corpse.classid + " " + corpse.name); + + if (Skill.cast(Config.ExplodeCorpses, sdk.skills.hand.Right, corpse)) { + delay(200); + } + } + } else { + break; + } + } + } + } + + return true; + }, + + /** + * @param {Monster} monster + * @param {number} range + */ + checkCorpseNearMonster: function (monster, range) { + let corpse = Game.getMonster(-1, sdk.monsters.mode.Dead); + + // Assume CorpseExplosion if no range specified + if (range === undefined) { + range = Math.floor((me.getSkill(Config.ExplodeCorpses, sdk.skills.subindex.SoftPoints) + 7) / 3); + } + + if (corpse) { + do { + if (getDistance(corpse, monster) <= range) { + return true; + } + } while (corpse.getNext()); + } + + return false; + }, + + /** + * @param {Unit} unit + * @param {boolean} revive + */ + checkCorpse: function (unit, revive = false) { + if (!unit || unit.mode !== sdk.monsters.mode.Dead) return false; + + let baseId = getBaseStat("monstats", unit.classid, "baseid"), badList = [312, 571]; + let states = [ + sdk.states.FrozenSolid, sdk.states.Revive, sdk.states.Redeemed, + sdk.states.CorpseNoDraw, sdk.states.Shatter, sdk.states.RestInPeace, sdk.states.CorpseNoSelect + ]; + + if (revive + && (unit.isSpecial || badList.includes(baseId) + || (Config.ReviveUnstackable && getBaseStat("monstats2", baseId, "sizex") === 3))) { + return false; + } + + if (!getBaseStat("monstats2", baseId, revive ? "revive" : "corpseSel")) return false; + + return !!(unit.distance <= 25 + && !checkCollision(me, unit, sdk.collision.Ranged) + && states.every(state => !unit.getState(state))); + } +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Paladin.js b/d2bs/kolbot/libs/core/Attacks/Paladin.js new file mode 100644 index 000000000..ae0e096e4 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Paladin.js @@ -0,0 +1,398 @@ +/** +* @filename Paladin.js +* @author kolton, theBGuy +* @desc Paladin attack sequence +* +*/ + +const ClassAttack = { + attackAuras: [sdk.skills.HolyFire, sdk.skills.HolyFreeze, sdk.skills.HolyShock], + + /** + * @param {Unit} unit + * @param {boolean} [preattack] + * @returns {AttackResult} + */ + doAttack: function (unit, preattack) { + if (!unit) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + let gid = unit.gid; + + if (Config.MercWatch && me.needMerc()) { + console.log("mercwatch"); + + if (Town.visitTown()) { + // lost reference to the mob we were attacking + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + return Attack.Result.SUCCESS; + } + } + } + + if (Config.ChargeCast.skill > -1) { + Attack.doChargeCast(unit); + } + + if (preattack) { + let preAttackResult = Attack.doPreAttack(unit); + if (preAttackResult !== Attack.Result.NOOP) { + return preAttackResult; + } + } + + let mercRevive = 0; + let [attackSkill, aura] = [-1, -1]; + const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + + if (Attack.getCustomAttack(unit)) { + [attackSkill, aura] = Attack.getCustomAttack(unit); + } else { + attackSkill = Config.AttackSkill[index]; + aura = Config.AttackSkill[index + 1]; + } + + // Classic auradin check + if (this.attackAuras.includes(aura)) { + // Monster immune to primary aura + if (!Attack.checkResist(unit, aura)) { + // Reset skills + [attackSkill, aura] = [-1, -1]; + + // Set to secondary if not immune, check if using secondary attack aura if not check main skill for immunity + if (Config.AttackSkill[5] > -1) { + let _check = (this.attackAuras.includes(Config.AttackSkill[6]) + ? Config.AttackSkill[6] + : Config.AttackSkill[5]); + if (Attack.checkResist(unit, _check)) { + attackSkill = Config.AttackSkill[5]; + aura = Config.AttackSkill[6]; + } + } + } + } else { + // Monster immune to primary skill + if (!Attack.checkResist(unit, attackSkill)) { + // Reset skills + [attackSkill, aura] = [-1, -1]; + + // Set to secondary if not immune + if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5])) { + attackSkill = Config.AttackSkill[5]; + aura = Config.AttackSkill[6]; + } else if ( + Config.AttackSkill.length === 9 + && Config.AttackSkill[7] > -1 + && Attack.checkResist(unit, Config.AttackSkill[7]) + ) { + attackSkill = Config.AttackSkill[7]; + aura = Config.AttackSkill[8]; + } + } + } + + // Low mana skill + if (Config.LowManaSkill[0] > -1 + && Skill.getManaCost(attackSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + [attackSkill, aura] = Config.LowManaSkill; + } + + let result = this.doCast(unit, attackSkill, aura); + + if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { + let merc = me.getMerc(); + + while (unit.attackable) { + if (!unit || !copyUnit(unit).x) { + unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); + } + if (!unit) return Attack.Result.SUCCESS; + + if (me.needMerc()) { + if (Config.MercWatch && mercRevive++ < 1) { + Town.visitTown(); + } else { + return Attack.Result.CANTATTACK; + } + + (merc === undefined || !merc) && (merc = me.getMerc()); + } + + if (!!merc && getDistance(merc, unit) > 5) { + Pather.moveToUnit(unit); + + let spot = Attack.findSafeSpot(unit, 10, 5, 9); + !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); + } + + let closeMob = Attack.getNearestMonster({ skipGid: gid }); + !!closeMob && this.doCast(closeMob, attackSkill, aura); + } + + return Attack.Result.SUCCESS; + } + + return result; + }, + + afterAttack: function () { + Precast.doPrecast(false); + + // only proceed with other checks if we can use redemption and the config values aren't 0 + if (Skill.canUse(sdk.skills.Redemption) && Config.Redemption.some(v => v > 0)) { + if ((me.hpPercent < Config.Redemption[0] || me.mpPercent < Config.Redemption[1]) + && Attack.checkNearCorpses(me) > 2 && Skill.setSkill(sdk.skills.Redemption, sdk.skills.hand.Right)) { + delay(1500); + } + } + + /** + * @todo add config options for these and possibly add to Pather.walkTo + */ + // if (Skill.canUse(sdk.skills.Cleansing) + // && ([sdk.states.AmplifyDamage, sdk.states.Decrepify].some(s => me.getState(s)) || me.hpPercent < 70 && me.getState(sdk.states.Poison)) + // && !me.checkForMobs({range: 12, coll: sdk.collision.BlockWall}) && Skill.setSkill(sdk.skills.Cleansing, sdk.skills.hand.Right)) { + // me.overhead("Delaying for a second to get rid of Poison"); + // Misc.poll(() => (![sdk.states.AmplifyDamage, sdk.states.Decrepify, sdk.states.Poison].some(s => me.getState(s)) || me.mode === sdk.player.mode.GettingHit), 1500, 50); + // } + + // if (Skill.canUse(sdk.skills.Meditation) && me.mpPercent < 50 && !me.getState(sdk.states.Meditation) + // && Skill.setSkill(sdk.skills.Meditation, sdk.skills.hand.Right)) { + // Misc.poll(() => (me.mpPercent >= 50 || me.mode === sdk.player.mode.GettingHit), 1500, 50); + // } + }, + + doCast: function (unit, attackSkill = -1, aura = -1) { + if (attackSkill < 0) return Attack.Result.CANTATTACK; + // unit became invalidated + if (!unit || !unit.attackable) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + + switch (attackSkill) { + case sdk.skills.BlessedHammer: + // todo: add doll avoid to other classes + if (Config.AvoidDolls && unit.isDoll) { + this.dollAvoid(unit); + aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); + Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); + + return Attack.Result.SUCCESS; + } + + // todo: maybe if we are currently surrounded and no tele to just attack from where we are + // hammers cut a pretty wide arc so likely this would be enough to clear our path + if (!this.getHammerPosition(unit)) { + // Fallback to secondary skill if it exists + if (Config.AttackSkill[5] > -1 + && Config.AttackSkill[5] !== sdk.skills.BlessedHammer + && Attack.checkResist(unit, Config.AttackSkill[5])) { + return this.doCast(unit, Config.AttackSkill[5], Config.AttackSkill[6]); + } + + return Attack.Result.FAILED; + } + + if (unit.distance > 9 || !unit.attackable) return Attack.Result.SUCCESS; + + aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); + + for (let i = 0; i < 3; i += 1) { + Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); + + if (!unit.attackable || unit.distance > 9 || unit.isPlayer) { + break; + } + } + + return Attack.Result.SUCCESS; + case sdk.skills.HolyBolt: + if (unit.distance > Skill.getRange(attackSkill) + 3 || CollMap.checkColl(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + CollMap.reset(); + + if (unit.distance > Skill.getRange(attackSkill) || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.FriendlyRanged, true)) { + return Attack.Result.FAILED; + } + } + + if (!unit.dead) { + aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); + Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); + } + + return Attack.Result.SUCCESS; + case sdk.skills.FistoftheHeavens: + if (!me.skillDelay) { + if (unit.distance > Skill.getRange(attackSkill) + || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.FriendlyRanged, true)) { + return Attack.Result.FAILED; + } + } + + if (!unit.dead) { + aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); + Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); + + return Attack.Result.SUCCESS; + } + } + + break; + case sdk.skills.Attack: + case sdk.skills.Sacrifice: + case sdk.skills.Zeal: + case sdk.skills.Vengeance: + if (!Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) { + return Attack.Result.FAILED; + } + + // 3591 - wall/line of sight/ranged/items/objects/closeddoor + if (unit.distance > 3 || checkCollision(me, unit, sdk.collision.WallOrRanged)) { + if (!Attack.getIntoPosition(unit, 3, sdk.collision.WallOrRanged, true)) { + return Attack.Result.FAILED; + } + } + + if (unit.attackable) { + aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); + return (Skill.cast(attackSkill, sdk.skills.hand.LeftNoShift, unit) + ? Attack.Result.SUCCESS + : Attack.Result.FAILED); + } + + break; + default: + if (Skill.getRange(attackSkill) < 4 && !Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(attackSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + let walk = (attackSkill !== sdk.skills.Smite + && Skill.getRange(attackSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + // walk short distances instead of tele for melee attacks. teleport if failed to walk + if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + if (!unit.dead) { + aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right); + Skill.cast(attackSkill, Skill.getHand(attackSkill), unit); + } + + return Attack.Result.SUCCESS; + } + + Misc.poll(() => !me.skillDelay, 1000, 40); + + return Attack.Result.SUCCESS; + }, + + dollAvoid: function (unit) { + let distance = 14; + + for (let i = 0; i < 2 * Math.PI; i += Math.PI / 6) { + let cx = Math.round(Math.cos(i) * distance); + let cy = Math.round(Math.sin(i) * distance); + + if (Attack.validSpot(unit.x + cx, unit.y + cy)) { + // don't clear while trying to reposition + return Pather.moveToEx(unit.x + cx, unit.y + cy, { clearSettings: { allowClearing: false } }); + } + } + + return false; + }, + + /** + * @param {Monster | Player} unit + * @returns {boolean} + */ + getHammerPosition: function (unit) { + let x, y, positions; + const canTele = Pather.canTeleport(); + const baseId = getBaseStat("monstats", unit.classid, "baseid"); + let size = getBaseStat("monstats2", baseId, "sizex"); + + // in case base stat returns something outrageous + (typeof size !== "number" || size < 1 || size > 3) && (size = 3); + + switch (unit.type) { + case sdk.unittype.Player: + x = unit.x; + y = unit.y; + positions = [[x + 2, y], [x + 2, y + 1]]; + + break; + case sdk.unittype.Monster: + let commonCheck = (unit.isMoving && unit.distance < 10); + x = commonCheck && getDistance(me, unit.targetx, unit.targety) > 5 ? unit.targetx : unit.x; + y = commonCheck && getDistance(me, unit.targetx, unit.targety) > 5 ? unit.targety : unit.y; + positions = [[x + 2, y + 1], [x, y + 3], [x + 2, y - 1], [x - 2, y + 2], [x - 5, y]]; + size === 3 && positions.unshift([x + 2, y + 2]); + + break; + } + + // If one of the valid positions is a position im at already + for (let i = 0; i < positions.length; i += 1) { + let check = { x: positions[i][0], y: positions[i][1] }; + + if (canTele && [check.x, check.y].distance < 1) { + return true; + } else if (!canTele && ([check.x, check.y].distance < 1 + && !CollMap.checkColl(unit, check, sdk.collision.BlockWalk, 0)) + || ([check.x, check.y].distance <= 4 && me.getMobCount(6) > 2)) { + return true; + } + } + + for (let i = 0; i < positions.length; i += 1) { + let check = { x: positions[i][0], y: positions[i][1] }; + + if (Attack.validSpot(check.x, check.y) + && !CollMap.checkColl(unit, check, sdk.collision.BlockWalk, 0)) { + if (this.reposition(check.x, check.y)) return true; + } + } + + // console.debug("Failed to find a hammer position for " + unit.name + " distance from me: " + unit.distance); + + return false; + }, + + reposition: function (x, y) { + if (typeof x !== "number" || typeof y !== "number") return false; + const node = { x: x, y: y }; + if (node.distance > 0) { + if (Pather.useTeleport()) { + node.distance > 30 + ? Pather.moveTo(x, y) + : Pather.teleportTo(x, y, 3); + } else { + if (node.distance <= 4) { + Misc.click(0, 0, x, y); + } else if (!CollMap.checkColl(me, node, sdk.collision.BlockWalk, 3)) { + Pather.walkTo(x, y); + } else { + // don't clear while trying to reposition + Pather.move(node, { clearSettings: { allowClearing: false } }); + } + + delay(200); + } + } + + return true; + } +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Sorceress.js b/d2bs/kolbot/libs/core/Attacks/Sorceress.js new file mode 100644 index 000000000..d8b07c403 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Sorceress.js @@ -0,0 +1,271 @@ +/** +* @filename Sorceress.js +* @author kolton, theBGuy +* @desc Sorceress attack sequence +* +*/ + +/** @implements {ClassAttack} */ +const ClassAttack = { + /** @param {Monster} unit */ + decideSkill: function (unit) { + let skills = { timed: -1, untimed: -1 }; + if (!unit || !unit.attackable) return skills; + + let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + let classid = unit.classid; + + // Get timed skill + let checkSkill = Attack.getCustomAttack(unit) + ? Attack.getCustomAttack(unit)[0] + : Config.AttackSkill[index]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + skills.timed = checkSkill; + } else if (Config.AttackSkill[5] > -1 + && Attack.checkResist(unit, Config.AttackSkill[5]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5], classid)) { + skills.timed = Config.AttackSkill[5]; + } + + // Get untimed skill + checkSkill = Attack.getCustomAttack(unit) + ? Attack.getCustomAttack(unit)[1] + : Config.AttackSkill[index + 1]; + + if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill, classid)) { + skills.untimed = checkSkill; + } else if (Config.AttackSkill[6] > -1 + && Attack.checkResist(unit, Config.AttackSkill[6]) + && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6], classid)) { + skills.untimed = Config.AttackSkill[6]; + } + + // Low mana timed skill + if (Config.LowManaSkill[0] > -1 + && Skill.getManaCost(skills.timed) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + skills.timed = Config.LowManaSkill[0]; + } + + // Low mana untimed skill + if (Config.LowManaSkill[1] > -1 + && Skill.getManaCost(skills.untimed) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[1])) { + skills.untimed = Config.LowManaSkill[1]; + } + + return skills; + }, + + /** + * @param {Monster} unit + * @param {boolean} preattack + * @returns {AttackResult} + */ + doAttack: function (unit, preattack = false) { + if (!unit) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + let gid = unit.gid; + + if (Config.MercWatch && me.needMerc()) { + if (Town.visitTown()) { + console.log("mercwatch"); + + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + console.debug("Lost reference to unit"); + return Attack.Result.SUCCESS; + } + } + } + + // Keep Energy Shield active + if (Skill.canUse(sdk.skills.EnergyShield) && !me.getState(sdk.states.EnergyShield)) { + Skill.cast(sdk.skills.EnergyShield, sdk.skills.hand.Right); + } + + // Keep Thunder-Storm active + if (Skill.canUse(sdk.skills.ThunderStorm) && !me.getState(sdk.states.ThunderStorm)) { + Skill.cast(sdk.skills.ThunderStorm, sdk.skills.hand.Right); + } + + if (Config.ChargeCast.skill > -1) { + Attack.doChargeCast(unit); + } + + if (preattack) { + let preAttackResult = Attack.doPreAttack(unit); + if (preAttackResult !== Attack.Result.NOOP) { + return preAttackResult; + } + } + + let useStatic = (Config.StaticList.length > 0 + && Config.CastStatic < 100 + && Skill.canUse(sdk.skills.StaticField) + && Attack.checkResist(unit, "lightning")); + let idCheck = function (id) { + if (unit) { + switch (true) { + case typeof id === "number" && unit.classid && unit.classid === id: + case typeof id === "string" && unit.name && unit.name.toLowerCase() === id.toLowerCase(): + case typeof id === "function" && id(unit): + return true; + default: + return false; + } + } + + return false; + }; + + // Static - needs to be re-done + if (useStatic && Config.StaticList.some(id => idCheck(id)) && unit.hpPercent > Config.CastStatic) { + let staticRange = Skill.getRange(sdk.skills.StaticField); + let casts = 0; + + while (!me.dead && unit.hpPercent > Config.CastStatic && unit.attackable) { + if (unit.distance > staticRange || checkCollision(me, unit, sdk.collision.Ranged)) { + if (!Attack.getIntoPosition(unit, staticRange, sdk.collision.Ranged)) { + return Attack.Result.FAILED; + } + } + + // if we fail to cast or we've casted 3 or more times - do something else + if (!Skill.cast(sdk.skills.StaticField, sdk.skills.hand.Right) || casts >= 3) { + break; + } else { + casts++; + } + } + + // re-check mob after static + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + console.debug("Lost reference to unit"); + return Attack.Result.SUCCESS; + } + } + + let skills = this.decideSkill(unit); + let result = this.doCast(unit, skills.timed, skills.untimed); + + if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) { + let merc = me.getMerc(); + let mercRevive = 0; + + while (unit.attackable) { + if (!unit || !copyUnit(unit).x) { + unit = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80); + } + if (!unit) return Attack.Result.SUCCESS; + + if (me.needMerc()) { + if (Config.MercWatch && mercRevive++ < 1) { + Town.visitTown(); + } else { + return Attack.Result.CANTATTACK; + } + + (merc === undefined || !merc) && (merc = me.getMerc()); + } + + if (!!merc && getDistance(merc, unit) > 7) { + Pather.moveToUnit(unit); + + let spot = Attack.findSafeSpot(unit, 10, 5, 9); + !!spot && !!spot.x && Pather.walkTo(spot.x, spot.y); + } + + let closeMob = Attack.getNearestMonster({ skipGid: gid }); + + if (!!closeMob) { + let findSkill = this.decideSkill(closeMob); + if (this.doCast(closeMob, findSkill.timed, findSkill.untimed) !== Attack.Result.SUCCESS) { + (Skill.haveTK && Packet.telekinesis(unit)); + } + } + } + + return Attack.Result.SUCCESS; + } + + return result; + }, + + afterAttack: function () { + Precast.doPrecast(false); + }, + + /** + * @param {Monster | Player} unit + * @param {number} timedSkill + * @param {number} untimedSkill + * @returns {AttackResult} 0 - fail, 1 - success, 2 - no valid attack skills + */ + doCast: function (unit, timedSkill = -1, untimedSkill = -1) { + // No valid skills can be found + if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK; + // unit became invalidated + if (!unit || !unit.attackable) return Attack.Result.SUCCESS; + Config.TeleSwitch && me.switchToPrimary(); + + let walk, noMana = false; + let classid = unit.classid; + + if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill)) && Skill.getManaCost(timedSkill) < me.mp) { + if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, timedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(timedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + if (!unit.dead && !checkCollision(me, unit, sdk.collision.Ranged)) { + Skill.cast(timedSkill, Skill.getHand(timedSkill), unit); + } + return Attack.Result.SUCCESS; + } else { + noMana = !me.skillDelay; + } + + if (untimedSkill > -1 && Skill.getManaCost(untimedSkill) < me.mp) { + if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y, untimedSkill, classid)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(untimedSkill) || checkCollision(me, unit, sdk.collision.Ranged)) { + // Allow short-distance walking for melee skills + walk = (Skill.getRange(untimedSkill) < 4 + && unit.distance < 10 + && !checkCollision(me, unit, sdk.collision.BlockWall) + ); + + if (!Attack.getIntoPosition(unit, Skill.getRange(untimedSkill), sdk.collision.Ranged, walk)) { + return Attack.Result.FAILED; + } + } + + !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit); + + return Attack.Result.SUCCESS; + } else { + noMana = true; + } + + // don't count as failed + if (noMana) return Attack.Result.NEEDMANA; + + Misc.poll(() => !me.skillDelay, 1000, 40); + + return Attack.Result.SUCCESS; + } +}; diff --git a/d2bs/kolbot/libs/core/Attacks/Wereform.js b/d2bs/kolbot/libs/core/Attacks/Wereform.js new file mode 100644 index 000000000..3f013d74f --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Wereform.js @@ -0,0 +1,181 @@ +/** +* @filename Wereform.js +* @author kolton, theBGuy +* @desc Wereform attack sequence +* +*/ + +// todo - handle a Bear necro summonmancer + +const ClassAttack = (function () { + const baseLL = me.getStat(sdk.stats.LifeLeech); + const baseED = me.getStat(sdk.stats.DamagePercent); + const feralBoost = () => ( + ((Math.floor(me.getSkill(sdk.skills.FeralRage, sdk.skills.subindex.SoftPoints) / 2) + 3) * 4) + baseLL + ); + const maulBoost = () => ( + ((Math.floor(me.getSkill(sdk.skills.Maul, sdk.skills.subindex.SoftPoints) / 2) + 3) * 20) + baseED + ); + + const wereform = { + rage: 0, + maul: 0, + duration: 0, // todo - handle duration, if we are about to lose our form, recast it before attacking + }; + + return { + feralBoost: 0, + maulBoost: 0, + + doAttack: function (unit, preattack) { + if (!unit) return Attack.Result.SUCCESS; + let gid = unit.gid; + + if (Config.MercWatch && me.needMerc()) { + console.debug("mercwatch"); + + if (Town.visitTown()) { + if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) { + return Attack.Result.SUCCESS; // lost reference to the mob we were attacking + } + } + } + + if (!wereform.rage && Config.AttackSkill.includes(sdk.skills.FeralRage)) { + // amount of life leech with max rage + wereform.rage = feralBoost(); + } + + if (!wereform.maul && Config.AttackSkill.includes(sdk.skills.Maul)) { + // amount of enhanced damage with max maul + wereform.maul = maulBoost(); + } + + Skill.shapeShift(Config.Wereform); + + if (((Config.AttackSkill[0] === sdk.skills.FeralRage + && (!me.getState(sdk.states.FeralRage) || me.getStat(sdk.stats.LifeLeech) < wereform.rage)) + || (Config.AttackSkill[0] === sdk.skills.Maul + && (!me.getState(sdk.states.Maul) || me.getStat(sdk.stats.DamagePercent) < wereform.maul)) + || (Config.AttackSkill[0] === sdk.skills.ShockWave && !unit.isSpecial && !unit.getState(sdk.states.Stunned)) + || (preattack && Config.AttackSkill[0] > 0)) + && Attack.checkResist(unit, Config.AttackSkill[0]) + && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0])) + && (Skill.wereFormCheck(Config.AttackSkill[0]) || !me.shapeshifted)) { + if (unit.distance > Skill.getRange(Config.AttackSkill[0]) + || checkCollision(me, unit, sdk.collision.WallOrRanged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(Config.AttackSkill[0]), sdk.collision.WallOrRanged, true)) { + return Attack.Result.FAILED; + } + } + + Skill.cast(Config.AttackSkill[0], Skill.getHand(Config.AttackSkill[0]), unit); + + return Attack.Result.SUCCESS; + } + + // Rebuff Armageddon + if (Skill.canUse(sdk.skills.Armageddon) && !me.getState(sdk.states.Armageddon)) { + Skill.cast(sdk.skills.Armageddon, sdk.skills.hand.Right); + } + + let timedSkill = -1; + let untimedSkill = -1; + let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3; + + // Get timed skill + let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index]; + + if (Attack.checkResist(unit, checkSkill) && Skill.wereFormCheck(checkSkill) && Attack.validSpot(unit.x, unit.y)) { + timedSkill = checkSkill; + } else if (Config.AttackSkill[5] > -1 + && Attack.checkResist(unit, Config.AttackSkill[5]) + && Attack.validSpot(unit.x, unit.y)) { + timedSkill = Config.AttackSkill[5]; + } + + // Get untimed skill + checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1]; + + if (Attack.checkResist(unit, checkSkill) && Skill.wereFormCheck(checkSkill) && Attack.validSpot(unit.x, unit.y)) { + untimedSkill = checkSkill; + } else if (Config.AttackSkill[6] > -1 + && Attack.checkResist(unit, Config.AttackSkill[6]) + && Attack.validSpot(unit.x, unit.y)) { + untimedSkill = Config.AttackSkill[6]; + } + + // eval skills + switch (true) { + case timedSkill === sdk.skills.Fury && untimedSkill === sdk.skills.FeralRage: + if (!me.getState(sdk.states.FeralRage) || me.getStat(sdk.stats.LifeLeech) < wereform.rage) { + timedSkill = sdk.skills.FeralRage; + } + + break; + case timedSkill === sdk.skills.Fury && untimedSkill === sdk.skills.Rabies: + case timedSkill === sdk.skills.FireClaws && untimedSkill === sdk.skills.Rabies: + if (!unit.getState(sdk.states.Rabies)) { + timedSkill = sdk.skills.Rabies; + } + + break; + case timedSkill === sdk.skills.ShockWave && untimedSkill === sdk.skills.Maul: + case timedSkill === sdk.skills.Maul && untimedSkill === sdk.skills.ShockWave: + case timedSkill === sdk.skills.Maul && untimedSkill === sdk.skills.FireClaws: + if (!me.getState(sdk.states.Maul)) { + timedSkill = sdk.skills.Maul; + } + + break; + } + + // Low mana timed skill + if (Config.LowManaSkill[0] > -1 + && Skill.getManaCost(timedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[0])) { + timedSkill = Config.LowManaSkill[0]; + } + + // Low mana untimed skill + if (Config.LowManaSkill[1] > -1 + && Skill.getManaCost(untimedSkill) > me.mp + && Attack.checkResist(unit, Config.LowManaSkill[1])) { + untimedSkill = Config.LowManaSkill[1]; + } + + // use our secondary skill if we can't use our primary + let choosenSkill = (Skill.isTimed(timedSkill) && me.skillDelay && untimedSkill > -1 ? untimedSkill : timedSkill); + + return this.doCast(unit, choosenSkill); + }, + + afterAttack: function () { + Precast.doPrecast(false); + }, + + // Returns: 0 - fail, 1 - success, 2 - no valid attack skills + doCast: function (unit, skill) { + // unit reference no longer valid or it died + if (!unit || unit.dead) return Attack.Result.SUCCESS; + // No valid skills can be found + if (skill < 0) return Attack.Result.CANTATTACK; + + if (Skill.getRange(skill) < 4 && !Attack.validSpot(unit.x, unit.y)) { + return Attack.Result.FAILED; + } + + if (unit.distance > Skill.getRange(skill) || checkCollision(me, unit, sdk.collision.WallOrRanged)) { + if (!Attack.getIntoPosition(unit, Skill.getRange(skill), sdk.collision.WallOrRanged, true)) { + return Attack.Result.FAILED; + } + } + + unit.attackable && Skill.cast(skill, Skill.getHand(skill), unit); + + Misc.poll(() => !me.skillDelay, 1000, 40); + + return Attack.Result.SUCCESS; + } + }; +})(); diff --git a/d2bs/kolbot/libs/core/Auto/AutoBuild.js b/d2bs/kolbot/libs/core/Auto/AutoBuild.js new file mode 100644 index 000000000..381fd3f36 --- /dev/null +++ b/d2bs/kolbot/libs/core/Auto/AutoBuild.js @@ -0,0 +1,106 @@ +/** +* @filename AutoBuild.js +* @author alogwe +* @desc This script is included when any script includes libs/core/Config.js and calls Config.init(). +* If enabled, loads a threaded helper script that will monitor changes in character level and +* upon level up detection, it will spend skill and stat points based on a configurable +* character build template file located in libs/config/Builds/*. +* +* Any skill and stat points obtained as quest rewards are currently +* invisible to this script and must be spent manually. +* +*/ +js_strict(true); + +!isIncluded("core/Prototypes.js") && include("core/Prototypes.js"); +!isIncluded("core/Cubing.js") && include("core/Cubing.js"); +!isIncluded("core/Runewords.js") && include("core/Runewords.js"); + +const AutoBuild = new function AutoBuild () { + Config.AutoBuild.DebugMode && (Config.AutoBuild.Verbose = true); + + let debug = !!Config.AutoBuild.DebugMode; + let verbose = !!Config.AutoBuild.Verbose; + let configUpdateLevel = 0; + + // Apply all Update functions from the build template in order from level 1 to me.charlvl. + // By reapplying all of the changes to the Config object, we preserve + // the state of the Config file without altering the saved char config. + function applyConfigUpdates () { + debug && this.print("Updating Config from level " + configUpdateLevel + " to " + me.charlvl); + while (configUpdateLevel < me.charlvl) { + configUpdateLevel += 1; + Skill.init(); + AutoBuildTemplate[configUpdateLevel].Update.apply(Config); + } + } + + function getBuildType () { + let build = Config.AutoBuild.Template; + if (!build) { + this.print("Config.AutoBuild.Template is either 'false', or invalid (" + build + ")"); + throw new Error("Invalid build template, read libs/config/Builds/README.txt for information"); + } + return build; + } + + function getCurrentScript () { + return getScript(true).name.toLowerCase(); + } + + function getLogFilename () { + let d = new Date(); + let dateString = d.getMonth() + "_" + d.getDate() + "_" + d.getFullYear(); + return ("logs/AutoBuild." + me.realm + "." + me.charname + "." + dateString + ".log"); + } + + function getTemplateFilename () { + let build = getBuildType(); + let template = "config/Builds/" + sdk.player.class.nameOf(me.classid) + "." + build + ".js"; + return template.toLowerCase(); + } + + function initialize () { + let currentScript = getCurrentScript(); + let template = getTemplateFilename(); + this.print("Including build template " + template + " into " + currentScript); + if (!include(template)) throw new Error("Failed to include template: " + template); + + // Only load() helper thread from default.dbj if it isn't loaded + if (currentScript === "default.dbj" && !getScript("tools\\autobuildthread.js")) { + load("threads/autobuildthread.js"); + } + + // All threads except autobuildthread.js use this event listener + // to update their thread-local Config object + if (currentScript !== "tools\\autobuildthread.js") { + addEventListener("scriptmsg", levelUpHandler); + } + + // Resynchronize our Config object with all past changes + // made to it by AutoBuild system + applyConfigUpdates(); + } + + function levelUpHandler (obj) { + if (typeof obj === "object" && obj.hasOwnProperty("event") && obj.event === "level up") { + applyConfigUpdates(); + } + } + + function log (message) { FileTools.appendText(getLogFilename(), message + "\n"); } + + // Only print to console from autobuildthread.js, + // but log from all scripts + function myPrint () { + let args = Array.prototype.slice.call(arguments); + args.unshift("AutoBuild:"); + let result = args.join(" "); + verbose && print.call(this, result); + debug && log.call(this, result); + } + + this.print = myPrint; + this.initialize = initialize; + this.applyConfigUpdates = applyConfigUpdates; +}; diff --git a/d2bs/kolbot/libs/core/Auto/AutoSkill.js b/d2bs/kolbot/libs/core/Auto/AutoSkill.js new file mode 100644 index 000000000..005bf6df9 --- /dev/null +++ b/d2bs/kolbot/libs/core/Auto/AutoSkill.js @@ -0,0 +1,161 @@ +/** +* @filename AutoSkill.js +* @author Original work by Nad42, edited by IMBA +* @desc Automatically allocate skill points and its pre-requisites if necessary +* +*/ + +const AutoSkill = new function () { + this.skillBuildOrder = []; + this.save = 0; + + /* skillBuildOrder - array of skill points to spend in order + save - number of skill points that will not be spent and saved + + skillBuildOrder Settings + Set skillBuildOrder in the array form: [[skill, count, satisfy], [skill, count, satisfy], ... [skill, count, satisfy]] + skill - skill id number (see /sdk/txt/skills.txt) + count - maximum number of skill points to allocate for that skill + satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + + skillBuildOrder = [ + [37, 1, true], [42, 1, true], [54, 1, true], //warmth, static, teleport + [59, 1, false], [55, 7, true], [45, 13, true], //blizzard, glacial spike, ice blast + [59, 7, false], [65, 1, true], //blizzard, cold mastery + [59, 20, false], [65, 20, true], //max blizzard, max cold mastery + [55, 20, true], [45, 20, true], //max glacial spike, max ice blast + ]; + */ + + //a function to return false if have all prereqs or a skill if not + this.needPreReq = function (skillid) { + //a loop to go through each reqskill + for (let t = sdk.stats.PreviousSkillLeft; t >= sdk.stats.PreviousSkillRight; t--) { + // Check ReqSkills + let preReq = (getBaseStat("skills", skillid, t)); + + if (preReq > sdk.skills.Attack && preReq < 356 && !me.getSkill(preReq, sdk.skills.subindex.HardPoints)) { + return preReq; + } + } + + return false; + }; + + this.skillCheck = function (skillid, count) { + let _hardPoints = me.getSkill(skillid, sdk.skills.subindex.HardPoints); + if (_hardPoints <= me.charlvl - getBaseStat("skills", skillid, sdk.stats.MinimumRequiredLevel) + && _hardPoints < count) { + return true; + } + + return false; + }; + + this.skillToAdd = function (inputArray) { + for (let i = 0; i < inputArray.length; i += 1) { + // limit maximum allocation count to 20 + if (inputArray[i][1] > 20) { + console.log( + "AutoSkill: Skill build index " + i + " has allocation count of " + + inputArray[i][1] + " and it will be limited to 20" + ); + inputArray[i][1] = 20; + } + + // set satify condition as default if not specified + if (inputArray[i][2] === undefined) { + inputArray[i][2] = true; + } + + // check to see if skill count in previous array is satisfied + if (i > 0 && inputArray[i - 1][2]) { + const _prevHardPoints = (me.getSkill(inputArray[i - 1][0], sdk.skills.subindex.HardPoints) || 0); + if (_prevHardPoints < inputArray[i - 1][1]) return false; + } + + if (me.getSkill(inputArray[i][0], sdk.skills.subindex.HardPoints) + && this.skillCheck(inputArray[i][0], inputArray[i][1])) { + return inputArray[i][0]; + } + + let reqIn; + let reqOut = this.needPreReq(inputArray[i][0]); + + if (!reqOut && this.skillCheck(inputArray[i][0], inputArray[i][1])) { + return inputArray[i][0]; + } + + while (reqOut) { + reqIn = reqOut; + reqOut = this.needPreReq(reqIn); + } + + if (this.skillCheck(reqIn, 1)) { + return reqIn; + } + } + + return false; + }; + + this.allocate = function () { + let tick = getTickCount(); + + this.remaining = me.getStat(sdk.stats.NewSkills); + + if (!getUIFlag(sdk.uiflags.TradePrompt)) { + let addTo = this.skillToAdd(this.skillBuildOrder); + + if (addTo) { + console.log("AutoSkill: Using skill point in Skill: " + getSkillById(addTo) + " ID: " + addTo); + delay(100); + useSkillPoint(addTo, 1); + } + } + + while (getTickCount() - tick < 1500 + 2 * me.ping) { + if (this.remaining > me.getStat(sdk.stats.NewSkills)) { + return true; + } + + delay(100); + } + + return false; + }; + + this.remaining = 0; + this.count = 0; + + this.init = function (skillBuildOrder, save = 0) { + this.skillBuildOrder = skillBuildOrder; + this.save = save; + + if (!this.skillBuildOrder || !this.skillBuildOrder.length) { + console.log("AutoSkill: No build array specified"); + + return false; + } + + while (me.getStat(sdk.stats.NewSkills) > this.save) { + this.allocate(); + delay(200 + me.ping); // may need longer delay under high ping + + // break out of loop if we have skill points available but cannot allocate further due to unsatisfied skill + if (me.getStat(sdk.stats.NewSkills) === this.remaining) { + this.count += 1; + } + + if (this.count > 2) { + break; + } + } + + console.log("AutoSkill: Finished allocating skill points"); + + return true; + }; + + return true; +}; diff --git a/d2bs/kolbot/libs/core/Auto/AutoStat.js b/d2bs/kolbot/libs/core/Auto/AutoStat.js new file mode 100644 index 000000000..ae4ea2e80 --- /dev/null +++ b/d2bs/kolbot/libs/core/Auto/AutoStat.js @@ -0,0 +1,751 @@ +/* eslint-disable no-labels */ +/** +* @filename AutoStat.js +* @author IMBA +* @desc Automatically allocate stat points +* +*/ + +const AutoStat = new function () { + this.statBuildOrder = []; + this.save = 0; + this.block = 0; + this.bulkStat = true; + + /* statBuildOrder - array of stat points to spend in order + save - remaining stat points that will not be spent and saved. + block - an integer value set to desired block chance. This is ignored in classic. + bulkStat - set true to spend multiple stat points at once (up to 100), or false to spend 1 point at a time. + + statBuildOrder Settings + The script will stat in the order of precedence. You may want to stat strength or dexterity first. + + Set stats to desired integer value, and it will stat *hard points up to the desired value. + You can also set to string value "all", and it will spend all the remaining points. + Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + + statBuildOrder = [ + ["strength", 25], ["energy", 75], ["vitality", 75], + ["strength", 55], ["vitality", "all"] + ]; + */ + + this.getBlock = function () { + if (!me.usingShield()) return this.block; + + // cast holy shield if available + if (Skill.canUse(sdk.skills.HolyShield) && !me.getState(sdk.states.HolyShield)) { + if (Precast.cast(sdk.skills.HolyShield)) { + delay(1000); + } else { + return this.block; + } + } + + if (me.classic) { + return Math.floor(me.getStat(sdk.stats.ToBlock) + getBaseStat(15, me.classid, 23)); + } + + return Math.min( + 75, + Math.floor( + (me.getStat(sdk.stats.ToBlock) + getBaseStat(15, me.classid, 23)) + * (me.getStat(sdk.stats.Dexterity) - 15) / (me.charlvl * 2) + ) + ); + }; + + // this check may not be necessary with this.validItem(), but consider it double check + // verify that the set bonuses are there + this.verifySetStats = function (unit, type, stats) { + let string = type === sdk.stats.Strength ? sdk.locale.text.ToStrength : sdk.locale.text.ToDexterity; + + if (unit) { + let temp = unit.description.split("\n"); + + for (let i = 0; i < temp.length; i += 1) { + if (temp[i].match(getLocaleString(string), "i")) { + if (parseInt(temp[i].replace(/(y|ÿ)c[0-9!"+<;.*]/, ""), 10) === stats) { + return true; + } + } + } + } + + return false; + }; + + this.validItem = function (item) { + // ignore item bonuses from secondary weapon slot + if (me.expansion && item.isOnSwap) return false; + // check if character meets str, dex, and level requirement since stat bonuses only apply when they are active + return me.getStat(sdk.stats.Strength) >= item.strreq + && me.getStat(sdk.stats.Dexterity) >= item.dexreq + && me.charlvl >= item.lvlreq; + }; + + // get stats from set bonuses + this.setBonus = function (type) { + // set bonuses do not have energy or vitality (we can ignore this) + if (type === sdk.stats.Energy || type === sdk.stats.Vitality) return 0; + + // these are the only sets with possible stat bonuses + let sets = { + "angelic": [], "artic": [], "civerb": [], "iratha": [], + "isenhart": [], "vidala": [], "cowking": [], "disciple": [], + "griswold": [], "mavina": [], "naj": [], "orphan": [] + }; + + let i, j, setStat = 0; + let items = me.getItems(); + + if (items) { + for (i = 0; i < items.length; i += 1) { + if (items[i].isEquipped && items[i].set && this.validItem(items[i])) { + idSwitch: + switch (items[i].classid) { + case sdk.items.Crown: + if (items[i].getStat(sdk.stats.LightResist) === 30) { + sets.iratha.push(items[i]); + } + + break; + case sdk.items.LightGauntlets: + if (items[i].getStat(sdk.stats.MaxHp) === 20) { + sets.artic.push(items[i]); + } else if (items[i].getStat(sdk.stats.ColdResist) === 30) { + sets.iratha.push(items[i]); + } + + break; + case sdk.items.HeavyBoots: + if (items[i].getStat(sdk.stats.Dexterity) === 20) { + sets.cowking.push(items[i]); + } + + break; + case sdk.items.HeavyBelt: + if (items[i].getStat(sdk.stats.MinDamage) === 5) { + sets.iratha.push(items[i]); + } + + break; + case sdk.items.Amulet: + if (items[i].getStat(sdk.stats.DamagetoMana) === 20) { + sets.angelic.push(items[i]); + } else if (items[i].getStat(sdk.stats.HpRegen) === 4) { + sets.civerb.push(items[i]); + } else if (items[i].getStat(sdk.stats.PoisonLengthResist) === 75) { + sets.iratha.push(items[i]); + } else if (items[i].getStat(sdk.stats.ColdResist) === 20) { + sets.vidala.push(items[i]); + } else if (items[i].getStat(sdk.stats.ColdResist) === 18) { + sets.disciple.push(items[i]); + } + + break; + case sdk.items.Ring: + if (items[i].getStat(sdk.stats.HpRegen) === 6) { + // do not count ring twice + for (j = 0; j < sets.angelic.length; j += 1) { + if (sets.angelic[j].classid === items[i].classid) { + break idSwitch; + } + } + + sets.angelic.push(items[i]); + } + + break; + case sdk.items.Sabre: + // do not count twice in case of dual wield + for (j = 0; j < sets.angelic.length; j += 1) { + if (sets.angelic[j].classid === items[i].classid) { + break idSwitch; + } + } + + sets.angelic.push(items[i]); + + break; + case sdk.items.RingMail: + sets.angelic.push(items[i]); + + break; + case sdk.items.ShortWarBow: + case sdk.items.QuiltedArmor: + case sdk.items.LightBelt: + sets.artic.push(items[i]); + + break; + case sdk.items.GrandScepter: + // do not count twice in case of dual wield + for (j = 0; j < sets.civerb.length; j += 1) { + if (sets.civerb[j].classid === items[i].classid) { + break idSwitch; + } + } + + sets.civerb.push(items[i]); + + break; + case sdk.items.LargeShield: + sets.civerb.push(items[i]); + + break; + case sdk.items.BroadSword: + // do not count twice in case of dual wield + for (j = 0; j < sets.isenhart.length; j += 1) { + if (sets.isenhart[j].classid === items[i].classid) { + break idSwitch; + } + } + + sets.isenhart.push(items[i]); + + break; + case sdk.items.FullHelm: + case sdk.items.BreastPlate: + case sdk.items.GothicShield: + sets.isenhart.push(items[i]); + + break; + case sdk.items.LongBattleBow: + case sdk.items.LeatherArmor: + case sdk.items.LightPlatedBoots: + sets.vidala.push(items[i]); + + break; + case sdk.items.StuddedLeather: + case sdk.items.WarHat: + sets.cowking.push(items[i]); + + break; + case sdk.items.DemonhideBoots: + case sdk.items.DuskShroud: + case sdk.items.BrambleMitts: + case sdk.items.MithrilCoil: + sets.disciple.push(items[i]); + + break; + case sdk.items.Caduceus: + // do not count twice in case of dual wield + for (j = 0; j < sets.griswold.length; j += 1) { + if (sets.griswold[j].classid === items[i].classid) { + break idSwitch; + } + } + + sets.griswold.push(items[i]); + + break; + case sdk.items.OrnatePlate: + case sdk.items.Corona: + case sdk.items.VortexShield: + sets.griswold.push(items[i]); + + break; + case sdk.items.GrandMatronBow: + case sdk.items.BattleGauntlets: + case sdk.items.SharkskinBelt: + case sdk.items.Diadem: + case sdk.items.KrakenShell: + sets.mavina.push(items[i]); + + break; + case sdk.items.ElderStaff: + case sdk.items.Circlet: + case sdk.items.HellforgePlate: + sets.naj.push(items[i]); + + break; + case sdk.items.WingedHelm: + case sdk.items.RoundShield: + case sdk.items.SharkskinGloves: + case sdk.items.BattleBelt: + sets.orphan.push(items[i]); + + break; + } + } + } + } + + for (i in sets) { + if (sets.hasOwnProperty(i)) { + MainSwitch: + switch (i) { + case "angelic": + if (sets[i].length >= 2 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 10)) { + break MainSwitch; + } + } + + setStat += 10; + } + + break; + case "artic": + if (sets[i].length >= 2 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 5)) { + break MainSwitch; + } + } + + setStat += 5; + } + + break; + case "civerb": + if (sets[i].length === 3 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 15)) { + break MainSwitch; + } + } + + setStat += 15; + } + + break; + case "iratha": + if (sets[i].length === 4 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 15)) { + break MainSwitch; + } + } + + setStat += 15; + } + + break; + case "isenhart": + if (sets[i].length >= 2 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 10)) { + break MainSwitch; + } + } + + setStat += 10; + } + + if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 10)) { + break MainSwitch; + } + } + + setStat += 10; + } + + break; + case "vidala": + if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 15)) { + break MainSwitch; + } + } + + setStat += 15; + } + + if (sets[i].length === 4 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 10)) { + break MainSwitch; + } + } + + setStat += 10; + } + + break; + case "cowking": + if (sets[i].length === 3 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 20)) { + break MainSwitch; + } + } + + setStat += 20; + } + + break; + case "disciple": + if (sets[i].length >= 4 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 10)) { + break MainSwitch; + } + } + + setStat += 10; + } + + break; + case "griswold": + if (sets[i].length >= 2 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 20)) { + break MainSwitch; + } + } + + setStat += 20; + } + + if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 30)) { + break MainSwitch; + } + } + + setStat += 30; + } + + break; + case "mavina": + if (sets[i].length >= 2 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 20)) { + break MainSwitch; + } + } + + setStat += 20; + } + + if (sets[i].length >= 3 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 30)) { + break MainSwitch; + } + } + + setStat += 30; + } + + break; + case "naj": + if (sets[i].length === 3 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 15)) { + break MainSwitch; + } + } + + setStat += 15; + } + + if (sets[i].length === 3 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 20)) { + break MainSwitch; + } + } + + setStat += 20; + } + + break; + case "orphan": + if (sets[i].length === 4 && type === sdk.stats.Dexterity) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 10)) { + break MainSwitch; + } + } + + setStat += 10; + } + + if (sets[i].length === 4 && type === sdk.stats.Strength) { + for (j = 0; j < sets[i].length; j += 1) { + if (!this.verifySetStats(sets[i][j], type, 20)) { + break MainSwitch; + } + } + + setStat += 20; + } + + break; + } + } + } + + return setStat; + }; + + // return stat values excluding stat bonuses from sets and/or items + this.getHardStats = function (type) { + let i, statID; + let addedStat = 0; + let items = me.getItems(); + + switch (type) { + case sdk.stats.Strength: + type = sdk.stats.Strength; + statID = sdk.stats.PerLevelStrength; + + break; + case sdk.stats.Energy: + type = sdk.stats.Energy; + statID = sdk.stats.PerLevelEnergy; + + break; + case sdk.stats.Dexterity: + type = sdk.stats.Dexterity; + statID = sdk.stats.PerLevelDexterity; + + break; + case sdk.stats.Vitality: + type = sdk.stats.Vitality; + statID = sdk.stats.PerLevelVitality; + + break; + } + + if (items) { + for (i = 0; i < items.length; i += 1) { + // items equipped or charms in inventory + if ((items[i].isEquipped || items[i].isEquippedCharm) && this.validItem(items[i])) { + // stats + items[i].getStat(type) && (addedStat += items[i].getStat(type)); + + // stats per level + if (items[i].getStat(statID)) { + addedStat += Math.floor(items[i].getStat(statID) / 8 * me.charlvl); + } + } + } + } + + return (me.getStat(type) - addedStat - this.setBonus(type)); + }; + + this.requiredDex = function () { + let set = false; + let inactiveDex = 0; + let items = me.getItems(); + + if (items) { + for (let i = 0; i < items.length; i += 1) { + // items equipped but inactive (these are possible dex sources unseen by me.getStat(sdk.stats.Dexterity)) + if (items[i].isEquipped && !items[i].isOnSwap && !this.validItem(items[i])) { + if (items[i].quality === sdk.items.quality.Set) { + set = true; + + break; + } + + // stats + items[i].getStat(sdk.stats.Dexterity) && (inactiveDex += items[i].getStat(sdk.stats.Dexterity)); + + // stats per level + if (items[i].getStat(sdk.stats.PerLevelDexterity)) { + inactiveDex += Math.floor(items[i].getStat(sdk.stats.PerLevelDexterity) / 8 * me.charlvl); + } + } + } + } + + // just stat 1 at a time if there's set item (there could be dex bonus for currently inactive set) + if (set) { + return 1; + } + + // returns amount of dexterity required to get the desired block chance + return Math.ceil( + (2 * me.charlvl * this.block) / (me.getStat(sdk.stats.ToBlock) + getBaseStat(15, me.classid, 23)) + 15 + ) - me.getStat(sdk.stats.Dexterity) - inactiveDex; + }; + + this.useStats = function (type, goal = false) { + let currStat = me.getStat(sdk.stats.StatPts); + let tick = getTickCount(); + let statIDToString = [ + getLocaleString(sdk.locale.text.Strength), getLocaleString(sdk.locale.text.Energy), + getLocaleString(sdk.locale.text.Dexterity), getLocaleString(sdk.locale.text.Vitality) + ]; + + // use 0x3a packet to spend multiple stat points at once (up to 100) + if (this.bulkStat) { + if (goal) { + new PacketBuilder() + .byte(sdk.packets.send.AddStat) + .byte(type) + .byte(Math.min(me.getStat(sdk.stats.StatPts) - this.save - 1, goal - 1, 99)) + .send(); + } else { + new PacketBuilder() + .byte(sdk.packets.send.AddStat) + .byte(type) + .byte(Math.min(me.getStat(sdk.stats.StatPts) - this.save - 1, 99)) + .send(); + } + } else { + useStatPoint(type); + } + + while (getTickCount() - tick < 3000) { + if (currStat > me.getStat(sdk.stats.StatPts)) { + console.log( + "AutoStat: Using " + (currStat - me.getStat(sdk.stats.StatPts)) + + " stat points in " + statIDToString[type] + ); + return true; + } + + delay(100); + } + + return false; + }; + + this.addStatPoint = function () { + this.remaining = me.getStat(sdk.stats.StatPts); + + let hardStats; + + for (let i = 0; i < this.statBuildOrder.length; i += 1) { + switch (this.statBuildOrder[i][0]) { + case sdk.stats.Strength: + case "s": + case "str": + case "strength": + if (typeof this.statBuildOrder[i][1] === "string") { + switch (this.statBuildOrder[i][1]) { + case "all": + return this.useStats(sdk.stats.Strength); + default: + break; + } + } else { + hardStats = this.getHardStats(sdk.stats.Strength); + + if (hardStats < this.statBuildOrder[i][1]) { + return this.useStats(sdk.stats.Strength, this.statBuildOrder[i][1] - hardStats); + } + } + + break; + case sdk.stats.Energy: + case "e": + case "enr": + case "energy": + if (typeof this.statBuildOrder[i][1] === "string") { + switch (this.statBuildOrder[i][1]) { + case "all": + return this.useStats(sdk.stats.Energy); + default: + break; + } + } else { + hardStats = this.getHardStats(sdk.stats.Energy); + + if (hardStats < this.statBuildOrder[i][1]) { + return this.useStats(sdk.stats.Energy, this.statBuildOrder[i][1] - hardStats); + } + } + + break; + case sdk.stats.Dexterity: + case "d": + case "dex": + case "dexterity": + if (typeof this.statBuildOrder[i][1] === "string") { + switch (this.statBuildOrder[i][1]) { + case "block": + if (me.expansion) { + if (this.getBlock() < this.block) { + return this.useStats(sdk.stats.Dexterity, this.requiredDex()); + } + } + + break; + case "all": + return this.useStats(sdk.stats.Dexterity); + default: + break; + } + } else { + hardStats = this.getHardStats(sdk.stats.Dexterity); + + if (hardStats < this.statBuildOrder[i][1]) { + return this.useStats(sdk.stats.Dexterity, this.statBuildOrder[i][1] - hardStats); + } + } + + break; + case sdk.stats.Vitality: + case "v": + case "vit": + case "vitality": + if (typeof this.statBuildOrder[i][1] === "string") { + switch (this.statBuildOrder[i][1]) { + case "all": + return this.useStats(sdk.stats.Vitality); + default: + break; + } + } else { + hardStats = this.getHardStats(sdk.stats.Vitality); + + if (hardStats < this.statBuildOrder[i][1]) { + return this.useStats(sdk.stats.Vitality, this.statBuildOrder[i][1] - hardStats); + } + } + + break; + } + } + + return false; + }; + + this.remaining = 0; + this.count = 0; + + this.init = function (statBuildOrder, save = 0, block = 0, bulkStat = true) { + this.statBuildOrder = statBuildOrder; + this.save = save; + this.block = block; + this.bulkStat = bulkStat; + + if (!this.statBuildOrder || !this.statBuildOrder.length) { + console.log("AutoStat: No build array specified"); + + return false; + } + + while (me.getStat(sdk.stats.StatPts) > this.save) { + this.addStatPoint(); + delay(150 + me.ping); // spending multiple single stat at a time with short delay may cause r/d + + // break out of loop if we have stat points available but finished allocating as configured + if (me.getStat(sdk.stats.StatPts) === this.remaining) { + this.count += 1; + } + + if (this.count > 2) { + break; + } + } + + console.log("AutoStat: Finished allocating stat points"); + + return true; + }; + + return true; +}; diff --git a/d2bs/kolbot/libs/core/CollMap.js b/d2bs/kolbot/libs/core/CollMap.js new file mode 100644 index 000000000..88f1af755 --- /dev/null +++ b/d2bs/kolbot/libs/core/CollMap.js @@ -0,0 +1,286 @@ +/** +* @filename CollMap.js +* @author kolton +* @desc manipulate map collision data +* +*/ + +const CollMap = new function () { + this.rooms = []; + this.maps = []; + /** @type {Line[]} */ + this.hooks = []; + this.colors = { + green: 0x84, + red: 0x0a, + black: 0x00, + white: 0xff, + purple: 0x9b, + blue: 0x97, + }; + + /** + * @param {Room} room + * @param {('green' | 'red' | 'black' | 'white' | 'purple' | 'blue' | number)} [color='green'] + * @param {boolean} [update=false] + * @returns {void} + */ + this.drawRoom = function (room, color = "green", update = false) { + let idx = this.hooks.findIndex(h => h.room.x === room.x && h.room.y === room.y); + if (idx >= 0) { + if (!update) return; + this.hooks[idx].lines.forEach(l => l.remove()); + this.hooks.splice(idx, 1); + } + const lineColor = typeof color === "string" + ? (color in this.colors) ? this.colors[color] : this.colors.green + : color; + let lines = [ + new Line(room.x * 5, room.y * 5, room.x * 5 + room.xsize, room.y * 5, lineColor, true), + new Line(room.x * 5 + room.xsize, room.y * 5, room.x * 5 + room.xsize, room.y * 5 + room.ysize, lineColor, true), + new Line(room.x * 5 + room.xsize, room.y * 5 + room.ysize, room.x * 5, room.y * 5 + room.ysize, lineColor, true), + new Line(room.x * 5, room.y * 5 + room.ysize, room.x * 5, room.y * 5, lineColor, true), + ]; + this.hooks.push({ room: room, lines: lines }); + }; + + /** @param {Room} room */ + this.removeHookForRoom = function (room) { + let index = this.hooks.findIndex(h => h.room.x === room.x && h.room.y === room.y); + if (index !== -1) { + this.hooks[index].lines.forEach(l => l.remove()); + this.hooks.splice(index, 1); + } + }; + + this.removeHooks = function () { + this.hooks.forEach(hook => hook.lines.forEach(l => l.remove())); + this.hooks = []; + }; + + /** + * @param {number} x + * @param {number} y + * @returns {boolean} + */ + this.getNearbyRooms = function (x, y) { + let room = getRoom(x, y); + if (!room) return false; + + let rooms = room.getNearby(); + if (!rooms) return false; + + for (let i = 0; i < rooms.length; i += 1) { + let [rX, rY] = [rooms[i].x * 5 + rooms[i].xsize / 2, rooms[i].y * 5 + rooms[i].ysize / 2]; + if (this.getRoomIndex(rX, rY, true) === undefined) { + this.addRoom(rooms[i]); + } + } + + return true; + }; + + /** + * @param {number | Room} x + * @param {number} [y] + * @returns {boolean} + */ + this.addRoom = function (x, y) { + let room = x instanceof Room ? x : getRoom(x, y); + + // Coords are not in the returned room. + if (arguments.length === 2 && !this.coordsInRoom(x, y, room)) { + return false; + } + + let coll = !!room ? room.getCollision() : null; + + if (coll) { + this.rooms.push({ x: room.x, y: room.y, xsize: room.xsize, ysize: room.ysize }); + this.maps.push(coll); + + return true; + } + + return false; + }; + + /** + * @param {number} x + * @param {number} y + * @param {boolean} [cacheOnly] + * @returns {boolean} + */ + this.getColl = function (x, y, cacheOnly) { + let index = this.getRoomIndex(x, y, cacheOnly); + + if (index === undefined) { + return 5; + } + + let j = x - this.rooms[index].x * 5; + let i = y - this.rooms[index].y * 5; + + if (this.maps[index] !== undefined && this.maps[index][i] !== undefined && this.maps[index][i][j] !== undefined) { + return this.maps[index][i][j]; + } + + return 5; + }; + + /** + * @param {number} x + * @param {number} y + * @param {boolean} [cacheOnly] + * @returns {number | undefined} + */ + this.getRoomIndex = function (x, y, cacheOnly) { + this.rooms.length > 25 && this.reset(); + + let i; + + for (i = 0; i < this.rooms.length; i += 1) { + if (this.coordsInRoom(x, y, this.rooms[i])) { + return i; + } + } + + if (!cacheOnly && this.addRoom(x, y)) { + return i; + } + + return undefined; + }; + + /** + * @param {number} x + * @param {number} y + * @param {Room} room + * @returns {boolean} + */ + this.coordsInRoom = function (x, y, room) { + if (room && x >= room.x * 5 && x < room.x * 5 + room.xsize && y >= room.y * 5 && y < room.y * 5 + room.ysize) { + return true; + } + + return false; + }; + + this.reset = function () { + this.rooms = []; + this.maps = []; + }; + + /** + * Check collision between unitA and unitB. true = collision present, false = collision not present + * If checking for blocking collisions (0x1, 0x4), true means blocked, false means not blocked + * @param {Unit | PathNode} unitA + * @param {Unit | PathNode} unitB + * @param {number} coll + * @param {number} thickness + * @returns {boolean} + */ + this.checkColl = function (unitA, unitB, coll, thickness) { + thickness === undefined && (thickness = 1); + + let i, k, l, cx, cy; + let angle = Math.atan2(unitA.y - unitB.y, unitA.x - unitB.x); + let distance = Math.round(getDistance(unitA, unitB)); + + for (i = 1; i < distance; i += 1) { + cx = Math.round((Math.cos(angle)) * i + unitB.x); + cy = Math.round((Math.sin(angle)) * i + unitB.y); + + // check thicker line + for (k = cx - thickness; k <= cx + thickness; k += 1) { + for (l = cy - thickness; l <= cy + thickness; l += 1) { + if (this.getColl(k, l, false) & coll) { + return true; + } + } + } + } + + return false; + }; + + /** + * @param {Room} room + * @returns {PathNode} + */ + this.getTelePoint = function (room) { + // returns {x, y, distance} of a valid point with lowest distance from room center + // distance is from room center, handy for keeping bot from trying to teleport on walls + + if (!room) throw new Error("Invalid room passed to getTelePoint"); + + let roomx = room.x * 5; + let roomy = room.y * 5; + + if (getCollision(room.area, roomx, roomy) & 1) { + let collision = room.getCollision(), validTiles = []; + let aMid = Math.round(collision.length / 2), bMid = Math.round(collision[0].length / 2); + + for (let a = 0; a < collision.length; a++) { + for (let b = 0; b < collision[a].length; b++) { + if (!(collision[a][b] & 1)) { + validTiles.push({ + x: roomx + b - bMid, + y: roomy + a - aMid, + distance: getDistance(0, 0, a - aMid, b - bMid) + }); + } + } + } + + if (validTiles.length) { + validTiles.sort((a, b) => a.distance - b.distance); + + return validTiles[0]; + } + + return null; + } + + return { x: roomx, y: roomy, distance: 0 }; + }; + + /** + * @param {number} cX + * @param {number} xmin + * @param {number} xmax + * @param {number} cY + * @param {number} ymin + * @param {number} ymax + * @param {number} factor + * @returns {PathNode} + */ + this.getRandCoordinate = function (cX, xmin, xmax, cY, ymin, ymax, factor = 1) { + // returns randomized {x, y} object with valid coordinates + let coordX, coordY; + let retry = 0; + + do { + if (retry > 30) { + console.log("failed to get valid coordinate"); + coordX = cX; + coordY = cY; + + break; + } + + coordX = cX + factor * rand(xmin, xmax); + coordY = cY + factor * rand(ymin, ymax); + + if (cX === coordX && cY === coordY) { // recalculate if same coordiante + coordX = 0; + continue; + } + + retry++; + } while (getCollision(me.area, coordX, coordY) & 1); + + // console.log("Move " + retry + " from (" + cX + ", " + cY + ") to (" + coordX + ", " + coordY + ")"); + return new PathNode(coordX, coordY); + }; +}; diff --git a/d2bs/kolbot/libs/core/Common.js b/d2bs/kolbot/libs/core/Common.js new file mode 100644 index 000000000..92c61ccec --- /dev/null +++ b/d2bs/kolbot/libs/core/Common.js @@ -0,0 +1,11 @@ +/** +* @filename Common.js +* @author theBGuy +* @desc collection of functions shared between muliple scripts +* +*/ + +const Common = { + // each common functionality is loaded into this object when it's needed + // for the actual function files @see core/Common/ +}; diff --git a/d2bs/kolbot/libs/core/Common/Ancients.js b/d2bs/kolbot/libs/core/Common/Ancients.js new file mode 100644 index 000000000..15ddd8b0e --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Ancients.js @@ -0,0 +1,155 @@ +/** +* @filename Ancients.js +* @author theBGuy +* @desc Handle Ancients quest +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + Object.defineProperty(Common, "Ancients", { + value: new function () { + this.altarSpot = { x: 10047, y: 12622 }; + this.archway = { x: 10050, y: 12637 }; + this.talicStatue = { x: 10037, y: 12617 }; + this.madawcStatue = { x: 10048, y: 12607 }; + this.korlicStatue = { x: 10058, y: 12617 }; + this.lastPrep = 0; + + this.canAttack = function () { + let ancient = Game.getMonster(); + + if (ancient) { + do { + if (!ancient.getParent() && !Attack.canAttack(ancient)) { + console.log("Can't attack ancients"); + return false; + } + } while (ancient.getNext()); + } + + return true; + }; + + this.touchAltar = function () { + let altar = Misc.poll(() => Game.getObject(sdk.objects.AncientsAltar), 5000, 100); + + if (altar) { + while (altar.mode !== sdk.objects.mode.Active) { + if (Skill.haveTK) { + (this.archway.distance > 1 || altar.distance > 20) && Pather.moveToUnit(this.archway); + Packet.telekinesis(altar); + } else { + Pather.moveToUnit(altar); + altar.interact(); + } + delay(200 + me.ping); + me.cancel(); + } + + // wait for ancients to spawn + while (!Game.getMonster(sdk.monsters.TalictheDefender)) { + delay(250 + me.ping); + } + + return true; + } else { + Pather.moveNearUnit(this.altarSpot, (Skill.haveTK ? 19 : 5)); + } + + return false; + }; + + this.checkStatues = function () { + let statues = getUnits(sdk.unittype.Object) + .filter(u => [ + sdk.objects.KorlictheProtectorStatue, + sdk.objects.TalictheDefenderStatue, + sdk.objects.MadawctheGuardianStatue].includes(u.classid) + && u.mode === sdk.objects.mode.Active); + return statues.length === 3; + }; + + this.checkCorners = function () { + let pos = [ + { x: 10036, y: 12592 }, { x: 10066, y: 12589 }, + { x: 10065, y: 12623 }, { x: 10058, y: 12648 }, + { x: 10040, y: 12660 }, { x: 10036, y: 12630 }, + { x: 10038, y: 12611 } + ]; + Pather.moveToUnit(this.altarSpot); + if (!this.checkStatues()) { + return pos.forEach((node) => { + // no mobs at that next, skip it + if ([node.x, node.y].distance < 35 && [node.x, node.y].mobCount({ range: 30 }) === 0) { + return; + } + Pather.moveTo(node.x, node.y); + Attack.clear(30); + }); + } + + return true; + }; + + this.killAncients = function (checkQuest = false) { + let retry = 0; + let attackRange = Skill.getRange(Config.AttackSkill[1]); + Pather.moveNearUnit(this.altarSpot, attackRange); + + while (!this.checkStatues()) { + if (retry > 5) { + console.log("Failed to kill anicents."); + + break; + } + /** + * @todo - far cast pwning the ancients + */ + Attack.clearClassids( + sdk.monsters.KorlictheProtector, sdk.monsters.TalictheDefender, sdk.monsters.MadawctheGuardian + ); + delay(1000); + + if (checkQuest) { + if (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed)) { + break; + } + console.log("Failed to kill anicents. Attempt: " + retry); + } + + this.checkCorners(); + retry++; + } + }; + + this.ancientsPrep = function () { + Town.goToTown(); + Town.fillTome(sdk.items.TomeofTownPortal); + [ + sdk.items.StaminaPotion, sdk.items.AntidotePotion, sdk.items.ThawingPotion + ].forEach(p => Town.buyPots(10, p, true)); + Town.buyPotions(); + Pather.usePortal(sdk.areas.ArreatSummit, me.name); + Common.Ancients.lastPrep = getTickCount(); + }; + + this.startAncients = function (preTasks = false, checkQuest = false) { + let retry = 0; + this.touchAltar(); + + while (!this.canAttack()) { + if (retry > 10) throw new Error("I think I'm unable to complete ancients, I've rolled them 10 times"); + preTasks && getTickCount() - this.lastPrep > Time.minutes(1) + ? this.ancientsPrep() + : Pather.makePortal(); + this.touchAltar(); + retry++; + } + + this.killAncients(checkQuest); + }; + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Common/Baal.js b/d2bs/kolbot/libs/core/Common/Baal.js new file mode 100644 index 000000000..9d051b392 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Baal.js @@ -0,0 +1,331 @@ +/** +* @filename Baal.js +* @author theBGuy +* @desc Handle Baal functions +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + Object.defineProperty(Common, "Baal", { + value: new function Baal () { + this.throneCoords = { + bottomLeft: { x: 15072, y: 5073 }, + bottomRight: { x: 15118, y: 5073 }, + bottomCenter: { x: 15093, y: 5073 }, + entraceArchway: { x: 15097, y: 5099 }, + topLeft: { x: 15072, y: 5002 }, + topRight: { x: 15118, y: 5002 }, + baal: { x: 15090, y: 5014 }, + }; + + this.checkHydra = function () { + let hydra = Game.getMonster(getLocaleString(sdk.locale.monsters.Hydra)); + if (hydra) { + do { + if (hydra.mode !== sdk.monsters.mode.Dead + && hydra.getStat(sdk.stats.Alignment) !== 2) { + let _pos = [ + this.throneCoords.bottomLeft, this.throneCoords.bottomRight, + this.throneCoords.topRight, this.throneCoords.topLeft, + ].sort(function (a, b) { + return getDistance(me, a) - getDistance(me, b); + }).first(); + Pather.moveTo(_pos.x, _pos.y); + while (hydra.mode !== sdk.monsters.mode.Dead) { + delay(500); + if (!copyUnit(hydra).x) { + break; + } + } + + break; + } + } while (hydra.getNext()); + } + + return true; + }; + + this.checkThrone = function (clear = true) { + let monster = Game.getMonster(); + + if (monster) { + do { + if (monster.attackable + && monster.y < 5080 + && (monster.x > 15072 && monster.x < 15118)) { + switch (monster.classid) { + case sdk.monsters.WarpedFallen: + case sdk.monsters.WarpedShaman: + return 1; + case sdk.monsters.BaalSubjectMummy: + case sdk.monsters.BaalColdMage: + return 2; + case sdk.monsters.Council4: + return 3; + case sdk.monsters.VenomLord2: + return 4; + case sdk.monsters.ListerTheTormenter: + return 5; + default: + if (clear) { + Attack.getIntoPosition(monster, 10, sdk.collision.Ranged); + Attack.clear(15); + } + + return false; + } + } + } while (monster.getNext()); + } + + return false; + }; + + this.clearThrone = function () { + if (!Game.getMonster(sdk.monsters.ThroneBaal)) return true; + + let monList = []; + + if (Config.AvoidDolls) { + let mon = Game.getMonster(sdk.monsters.SoulKiller); + + if (mon) { + do { + // exclude dolls from the list + if (!mon.isDoll && mon.x >= 15072 && mon.x <= 15118 + && mon.y >= 5002 && mon.y <= 5079 + && mon.attackable && !Attack.skipCheck(mon)) { + monList.push(copyUnit(mon)); + } + } while (mon.getNext()); + } + + if (monList.length > 0) { + return Attack.clearList(monList); + } + } + + let pos = [ + { x: 15097, y: 5054 }, { x: 15079, y: 5014 }, + { x: 15085, y: 5053 }, { x: 15085, y: 5040 }, + { x: 15098, y: 5040 }, { x: 15099, y: 5022 }, + { x: 15086, y: 5024 }, { x: 15079, y: 5014 } + ]; + return pos.forEach(function (node) { + // no mobs at that next, skip it + if ([node.x, node.y].distance < 35 + && [node.x, node.y].mobCount({ range: 30 }) === 0) { + return; + } + Pather.moveTo(node.x, node.y); + Attack.clear(30); + }); + }; + + this.preattack = function () { + switch (me.classid) { + case sdk.player.class.Sorceress: + if ([ + sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb, sdk.skills.FireWall + ].includes(Config.AttackSkill[1])) { + if (me.getState(sdk.states.SkillDelay)) { + delay(50); + } else { + Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 15094 + rand(-2, 2), 5024 + rand(-2, 2)); + } + } + + break; + case sdk.player.class.Paladin: + if (Config.AttackSkill[3] === sdk.skills.BlessedHammer) { + Config.AttackSkill[4] > 0 && Skill.setSkill(Config.AttackSkill[4], sdk.skills.hand.Right); + + return Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Left); + } + + break; + case sdk.player.class.Druid: + if ([sdk.skills.Tornado, sdk.skills.Fissure, sdk.skills.Volcano].includes(Config.AttackSkill[3])) { + Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Right, 15094 + rand(-1, 1), 5029); + + return true; + } + + break; + case sdk.player.class.Assassin: + if (Config.UseTraps) { + let check = ClassAttack.checkTraps({ x: 15094, y: 5028 }); + + if (check) { + return ClassAttack.placeTraps({ x: 15094, y: 5028 }, 5); + } + } + + if (Config.AttackSkill[3] === sdk.skills.ShockWeb) { + return Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Right, 15094, 5028); + } + + break; + } + + return false; + }; + + this.clearWaves = function () { + Pather.moveTo(15094, me.paladin ? 5029 : 5038); + + let tick = getTickCount(); + let totalTick = getTickCount(); + let lastWave = 0; + + MainLoop: + while (true) { + if (!Game.getMonster(sdk.monsters.ThroneBaal)) return true; + const wave = this.checkThrone() || 0; + + switch (wave) { + case 1: + Attack.clearClassids(sdk.monsters.WarpedFallen, sdk.monsters.WarpedShaman) && (tick = getTickCount()); + + break; + case 2: + Attack.clearClassids(sdk.monsters.BaalSubjectMummy, sdk.monsters.BaalColdMage) && (tick = getTickCount()); + + break; + case 3: + Attack.clearClassids(sdk.monsters.Council4) && (tick = getTickCount()); + this.checkHydra() && (tick = getTickCount()); + + break; + case 4: + Attack.clearClassids(sdk.monsters.VenomLord2) && (tick = getTickCount()); + + break; + case 5: + if (Attack.clearClassids(sdk.monsters.ListerTheTormenter, sdk.monsters.Minion1, sdk.monsters.Minion2)) { + tick = getTickCount(); + } + + break MainLoop; + default: + if (getTickCount() - tick < Time.seconds(7)) { + if (Skill.canUse(sdk.skills.Cleansing) && me.getState(sdk.states.Poison)) { + Skill.setSkill(sdk.skills.Cleansing, sdk.skills.hand.Right); + Misc.poll(function () { + if (Config.AttackSkill[3] === sdk.skills.BlessedHammer) { + Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Left); + } + return !me.getState(sdk.states.Poison) || me.mode === sdk.player.mode.GettingHit; + }, Time.seconds(3), 100); + } + } + + if (getTickCount() - tick > Time.seconds(20)) { + this.clearThrone(); + tick = getTickCount(); + } + + if (!this.preattack()) { + delay(100); + } + + break; + } + + if (wave > 0 && wave > lastWave) { + lastWave = wave; + } + + switch (me.classid) { + case sdk.player.class.Amazon: + case sdk.player.class.Sorceress: + case sdk.player.class.Necromancer: + case sdk.player.class.Assassin: + if (Config.AttackSkill[3] === sdk.skills.FrozenOrb && (lastWave < 4)) { + [15106, 5040].distance > 3 && Pather.moveTo(15106, 5040); + } else { + [15116, 5026].distance > 3 && Pather.moveTo(15116, 5026); + } + + break; + case sdk.player.class.Paladin: + if (Config.AttackSkill[3] === sdk.skills.BlessedHammer) { + [15094, 5029].distance > 3 && Pather.moveTo(15094, 5029); + + break; + } + // eslint-disable-next-line no-fallthrough + case sdk.player.class.Druid: + if ([sdk.skills.Fissure, sdk.skills.Volcano].includes(Config.AttackSkill[3])) { + [15116, 5026].distance > 3 && Pather.moveTo(15116, 5026); + + break; + } + + if (Config.AttackSkill[3] === sdk.skills.Tornado) { + [15094, 5029].distance > 3 && Pather.moveTo(15106, 5041); + + break; + } + // eslint-disable-next-line no-fallthrough + case sdk.player.class.Barbarian: + [15101, 5045].distance > 3 && Pather.moveTo(15101, 5045); + + break; + } + + // If we've been in the throne for 30 minutes that's way too long + if (getTickCount() - totalTick > Time.minutes(30)) { + return false; + } + + delay(10); + } + + this.clearThrone(); + + return true; + }; + + this.killBaal = function () { + if (me.inArea(sdk.areas.ThroneofDestruction)) { + Config.PublicMode && Loader.scriptName() === "Baal" && say(Config.Baal.BaalMessage); + me.checkForMobs({ range: 30 }) && this.clearWaves(); // ensure waves are actually done + Pather.moveTo(15090, 5008); + Misc.poll(function () { + return !Game.getMonster(sdk.monsters.ThroneBaal); + }, Time.seconds(5), 100); + Precast.doPrecast(true); + Misc.poll(function () { + if (me.mode === sdk.player.mode.GettingHit || me.checkForMobs({ range: 15 })) { + Common.Baal.clearThrone(); + Pather.moveTo(15090, 5008); + } + return !Game.getMonster(sdk.monsters.ThroneBaal); + }, Time.minutes(3), 1000); + + let portal = Game.getObject(sdk.objects.WorldstonePortal); + + if (portal) { + Pather.usePortal(null, null, portal); + } else { + throw new Error("Couldn't find portal."); + } + } + + if (me.inArea(sdk.areas.WorldstoneChamber)) { + Pather.moveTo(15134, 5923); + Attack.kill(sdk.monsters.Baal); + Pickit.pickItems(); + + return true; + } + + return false; + }; + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Common/Cain.js b/d2bs/kolbot/libs/core/Common/Cain.js new file mode 100644 index 000000000..a6fe32ebf --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Cain.js @@ -0,0 +1,139 @@ +/** +* @filename Cain.js +* @author theBGuy +* @desc Complete cain quest +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + Object.defineProperty(Common, "Cain", { + value: { + activateStone: function (stone) { + for (let i = 0; i < 3; i++) { + // don't use tk if we are right next to it + let useTK = (stone.distance > 5 && Skill.useTK(stone) && i === 0); + if (useTK) { + stone.distance > 13 && Attack.getIntoPosition(stone, 13, sdk.collision.Ranged); + if (!Packet.telekinesis(stone)) { + console.debug("Failed to tk: attempt: " + i); + continue; + } + } else { + [(stone.x + 1), (stone.y + 2)].distance > 5 && Pather.moveTo(stone.x + 1, stone.y + 2, 3); + Misc.click(0, 0, stone); + } + + if (Misc.poll(() => stone.mode, 1000, 50)) { + return true; + } + Packet.flash(me.gid); + } + + // Click to stop walking in case we got stuck + !me.idle && Misc.click(0, 0, me.x, me.y); + + return false; + }, + + run: function () { + MainLoop: + while (true) { + switch (true) { + case !Game.getItem(sdk.quest.item.ScrollofInifuss) + && !Game.getItem(sdk.quest.item.KeytotheCairnStones) + && !Misc.checkQuest(sdk.quest.id.TheSearchForCain, 4): + Pather.useWaypoint(sdk.areas.DarkWood, true); + Precast.doPrecast(true); + + if (!Pather.moveToPreset(sdk.areas.DarkWood, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) { + throw new Error("Failed to move to Tree of Inifuss"); + } + + let tree = Game.getObject(sdk.quest.chest.InifussTree); + !!tree && tree.distance > 5 && Pather.moveToUnit(tree); + Misc.openChest(tree); + let scroll = Misc.poll(() => Game.getItem(sdk.quest.item.ScrollofInifuss), 1000, 100); + + Pickit.pickItem(scroll); + Town.goToTown(); + Town.npcInteract("Akara"); + + break; + case Game.getItem(sdk.quest.item.ScrollofInifuss): + Town.goToTown(1); + Town.npcInteract("Akara"); + + break; + case Game.getItem(sdk.quest.item.KeytotheCairnStones) && !me.inArea(sdk.areas.StonyField): + Pather.journeyTo(sdk.areas.StonyField); + Precast.doPrecast(true); + + break; + case Game.getItem(sdk.quest.item.KeytotheCairnStones) && me.inArea(sdk.areas.StonyField): + Pather.moveToPresetMonster( + sdk.areas.StonyField, + sdk.monsters.preset.Rakanishu, + { offX: 10, offY: 10, pop: true } + ); + Attack.securePosition(me.x, me.y, 40, 3000, true); + Pather.moveToPresetObject( + sdk.areas.StonyField, + sdk.quest.chest.StoneAlpha, + { clearSettings: { clearPath: true } } + ); + let stones = [ + Game.getObject(sdk.quest.chest.StoneAlpha), + Game.getObject(sdk.quest.chest.StoneBeta), + Game.getObject(sdk.quest.chest.StoneGamma), + Game.getObject(sdk.quest.chest.StoneDelta), + Game.getObject(sdk.quest.chest.StoneLambda) + ]; + + while (stones.some((stone) => !stone.mode)) { + for (let i = 0; i < stones.length; i++) { + let stone = stones[i]; + + if (this.activateStone(stone)) { + stones.splice(i, 1); + i--; + } + delay(10); + } + } + + let tick = getTickCount(); + // wait up to two minutes + while (getTickCount() - tick < Time.minutes(2)) { + if (Pather.getPortal(sdk.areas.Tristram)) { + Pather.usePortal(sdk.areas.Tristram); + + break; + } + } + + break; + case me.inArea(sdk.areas.Tristram) + && !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed): + let gibbet = Game.getObject(sdk.quest.chest.CainsJail); + + if (gibbet && !gibbet.mode) { + Pather.moveTo(gibbet.x, gibbet.y); + if (Misc.poll(() => Misc.openChest(gibbet), 2000, 100)) { + Town.goToTown(1); + Town.npcInteract("Akara") && console.log("Akara done"); + } + } + + break; + default: + break MainLoop; + } + } + + return true; + } + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Common/Cows.js b/d2bs/kolbot/libs/core/Common/Cows.js new file mode 100644 index 000000000..7a73cadcb --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Cows.js @@ -0,0 +1,79 @@ +/** +* @filename Cows.js +* @author theBGuy +* @desc clear Moo Moo Farm +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + Object.defineProperty(Common, "Cows", { + value: { + buildCowRooms: function () { + let finalRooms = []; + let indexes = []; + + let kingPreset = Game.getPresetMonster(sdk.areas.MooMooFarm, sdk.monsters.preset.TheCowKing); + let badRooms = getRoom(kingPreset.roomx * 5 + kingPreset.x, kingPreset.roomy * 5 + kingPreset.y).getNearby(); + + for (let i = 0; i < badRooms.length; i += 1) { + let badRooms2 = badRooms[i].getNearby(); + + for (let j = 0; j < badRooms2.length; j += 1) { + if (indexes.indexOf(badRooms2[j].x + "" + badRooms2[j].y) === -1) { + indexes.push(badRooms2[j].x + "" + badRooms2[j].y); + } + } + } + + let room = getRoom(); + + do { + if (indexes.indexOf(room.x + "" + room.y) === -1) { + finalRooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]); + } + } while (room.getNext()); + + return finalRooms; + }, + + clearCowLevel: function () { + function roomSort(a, b) { + return getDistance(myRoom[0], myRoom[1], a[0], a[1]) - getDistance(myRoom[0], myRoom[1], b[0], b[1]); + } + + Config.MFLeader && Pather.makePortal() && say("cows"); + + let myRoom; + let rooms = this.buildCowRooms(); + + while (rooms.length > 0) { + // get the first room + initialize myRoom var + !myRoom && (room = getRoom(me.x, me.y)); + + if (room) { + // use previous room to calculate distance + if (room instanceof Array) { + myRoom = [room[0], room[1]]; + } else { + // create a new room to calculate distance (first room, done only once) + myRoom = [room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]; + } + } + + rooms.sort(roomSort); + let room = rooms.shift(); + let result = Pather.getNearestWalkable(room[0], room[1], 10, 2); + + if (result) { + Pather.moveTo(result[0], result[1], 3); + if (!Attack.clear(30)) return false; + } + } + + return true; + }, + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Common/Diablo.js b/d2bs/kolbot/libs/core/Common/Diablo.js new file mode 100644 index 000000000..569aedf07 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Diablo.js @@ -0,0 +1,670 @@ +/** +* @filename Diablo.js +* @author theBGuy +* @desc Handle Diablo related functions +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + /** + * @todo + * - keep track of seals opened and bosses killed to + * - improve targetting when using getBoss, sometimes we run to the location we want to attack from while running past the boss + * I see this mostly with viz, also have seen seis fail due to not being close enough, even though he spawned + */ + Object.defineProperty(Common, "Diablo", { + /** + * @namespace Common + * + * @typedef Diablo + * @property {boolean} diabloSpawned + * + */ + value: { + diabloSpawned: false, + diaWaitTime: Time.seconds(30), + clearRadius: 30, + done: false, + waitForGlow: false, + sealOrder: [], + openedSeals: [], + vizLayout: -1, + seisLayout: -1, + infLayout: -1, + entranceCoords: { x: 7790, y: 5544 }, + starCoords: { x: 7791, y: 5293 }, + // path coordinates + entranceToStar: [ + [7794, 5517], [7791, 5491], [7768, 5459], + [7775, 5424], [7817, 5458], [7777, 5408], + [7769, 5379], [7777, 5357], [7809, 5359], + [7805, 5330], [7780, 5317], [7791, 5293]], + starToVizA: [ + [7759, 5295], [7734, 5295], [7716, 5295], [7718, 5276], + [7697, 5292], [7678, 5293], [7665, 5276], [7662, 5314] + ], + starToVizB: [ + [7759, 5295], [7734, 5295], [7716, 5295], + [7701, 5315], [7666, 5313], [7653, 5284] + ], + starToSeisA: [ + [7781, 5259], [7805, 5258], [7802, 5237], [7776, 5228], + [7775, 5205], [7804, 5193], [7814, 5169], [7788, 5153] + ], + starToSeisB: [ + [7781, 5259], [7805, 5258], [7802, 5237], [7776, 5228], + [7811, 5218], [7807, 5194], [7779, 5193], [7774, 5160], [7803, 5154] + ], + starToInfA: [ + [7809, 5268], [7834, 5306], [7852, 5280], + [7852, 5310], [7869, 5294], [7895, 5295], [7919, 5290] + ], + starToInfB: [ + [7809, 5268], [7834, 5306], [7852, 5280], [7852, 5310], + [7869, 5294], [7895, 5274], [7927, 5275], [7932, 5297], [7923, 5313] + ], + // check for strays array + cleared: [], + + diabloLightsEvent: function (bytes = []) { + if (me.inArea(sdk.areas.ChaosSanctuary) + && bytes && bytes.length === 2 && bytes[0] === 0x89 && bytes[1] === 0x0C) { + Common.Diablo.diabloSpawned = true; + } + }, + + sort: function (a, b) { + if (Config.BossPriority) { + if ((a.isSuperUnique) && (b.isSuperUnique)) return getDistance(me, a) - getDistance(me, b); + if (a.isSuperUnique) return -1; + if (b.isSuperUnique) return 1; + } + + // Entrance to Star / De Seis + if (me.y > 5325 || me.y < 5260) return (a.y > b.y ? -1 : 1); + // Vizier + if (me.x < 7765) return (a.x > b.x ? -1 : 1); + // Infector + if (me.x > 7825) return (!checkCollision(me, a, sdk.collision.BlockWall) && a.x < b.x ? -1 : 1); + + return getDistance(me, a) - getDistance(me, b); + }, + + getLayout: function (seal, value) { + let sealPreset = Game.getPresetObject(sdk.areas.ChaosSanctuary, seal); + if (!seal) throw new Error("Seal preset not found. Can't continue."); + + if (sealPreset.roomy * 5 + sealPreset.y === value + || sealPreset.roomx * 5 + sealPreset.x === value) { + return 1; + } + + return 2; + }, + + /** + * - VizLayout - 1 = "Y", 2 = "L" + * - SeisLayout - 1 = "2", 2 = "5" + * - InfLayout - 1 = "I", 2 = "J" + */ + initLayout: function () { + // 1 = "Y", 2 = "L" + Common.Diablo.vizLayout = this.getLayout(sdk.objects.DiabloSealVizier, 5275); + // 1 = "2", 2 = "5" + Common.Diablo.seisLayout = this.getLayout(sdk.objects.DiabloSealSeis, 7773); + // 1 = "I", 2 = "J" + Common.Diablo.infLayout = this.getLayout(sdk.objects.DiabloSealInfector, 7893); + }, + + /** + * Follow static path + * @param {number[][]} path + * @returns {void} + */ + followPath: function (path) { + if (Config.Diablo.Fast) { + let last = path.last(); + let lastNode = { x: last[0], y: last[1] }; + Pather.moveToUnit(lastNode); + return; + } + + for (let i = 0; i < path.length; i++) { + this.cleared.length > 0 && this.clearStrays(); + + // no monsters at the next node, skip it + let next = i + 1 !== path.length ? path[i + 1] : null; + if (next && next.distance < 40 && next.mobCount({ range: 35 }) === 0) { + continue; + } + + Pather.moveTo(path[i][0], path[i][1], 3, getDistance(me, path[i][0], path[i][1]) > 50); + Attack.clear(this.clearRadius, 0, false, Common.Diablo.sort); + + // Push cleared positions so they can be checked for strays + this.cleared.push(path[i]); + + // After 5 nodes go back 2 nodes to check for monsters + if (i === 5 && path.length > 8) { + path = path.slice(3); + i = 0; + } + } + }, + + clearStrays: function () { + let oldPos = { x: me.x, y: me.y }; + let monster = Game.getMonster(); + + if (monster) { + do { + if (monster.attackable) { + for (let i = 0; i < this.cleared.length; i += 1) { + if (getDistance(monster, this.cleared[i][0], this.cleared[i][1]) < 30 + && Attack.validSpot(monster.x, monster.y)) { + Pather.moveToUnit(monster); + Attack.clear(15, 0, false, Common.Diablo.sort); + + break; + } + } + } + } while (monster.getNext()); + } + + getDistance(me, oldPos.x, oldPos.y) > 5 && Pather.moveTo(oldPos.x, oldPos.y); + + return true; + }, + + /** + * @param {number[] | string[]} sealOrder + * @param {boolean} openSeals + */ + runSeals: function (sealOrder, openSeals = true, recheck = false) { + console.log("seal order: " + sealOrder); + Common.Diablo.sealOrder = sealOrder; + let seals = { + 1: () => this.vizierSeal(openSeals), + 2: () => this.seisSeal(openSeals), + 3: () => this.infectorSeal(openSeals), + "vizier": () => this.vizierSeal(openSeals), + "seis": () => this.seisSeal(openSeals), + "infector": () => this.infectorSeal(openSeals), + }; + try { + recheck && addEventListener("gamepacket", Common.Diablo.diabloLightsEvent); + sealOrder.forEach(seal => { + if (recheck && Common.Diablo.diabloSpawned) throw new ScriptError("Diablo spawned"); + seals[seal](); + }); + } catch (e) { + if (!(e instanceof ScriptError)) { + throw e; // it wasn't the custom error so throw it to the next handler + } + } finally { + recheck && removeEventListener("gamepacket", Common.Diablo.diabloLightsEvent); + } + }, + + /** + * Attempt casting telekinesis on seal to activate it + * @param {Unit} seal + * @returns {boolean} + */ + tkSeal: function (seal) { + if (!Skill.useTK(seal)) return false; + + for (let i = 0; i < 5; i++) { + seal.distance > 20 && Attack.getIntoPosition(seal, 18, sdk.collision.WallOrRanged); + + if (Packet.telekinesis(seal) + && Misc.poll(() => seal.mode, 1000, 100)) { + break; + } + } + + return !!seal.mode; + }, + + /** + * Open one of diablos seals + * @param {number} classid + * @returns {boolean} + */ + openSeal: function (classid) { + let seal; + const mainSeal = [ + sdk.objects.DiabloSealVizier, sdk.objects.DiabloSealSeis, sdk.objects.DiabloSealInfector + ].includes(classid); + const warn = Config.PublicMode && mainSeal && Loader.scriptName() === "Diablo"; + const seisSeal = classid === sdk.objects.DiabloSealSeis; + const infSeal = [sdk.objects.DiabloSealInfector, sdk.objects.DiabloSealInfector2].includes(classid); + let usetk = (Skill.haveTK && (!seisSeal || this.seisLayout !== 1)); + + for (let i = 0; i < 5; i++) { + if (!seal) { + usetk + ? Pather.moveNearPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, classid, 15) + : Pather.moveToPresetObject( + sdk.areas.ChaosSanctuary, + classid, + { offX: seisSeal ? 5 : 2, offY: seisSeal ? 5 : 0 } + ); + seal = Misc.poll(() => Game.getObject(classid), 1000, 100); + } + + if (!seal) { + console.debug("Couldn't find seal: " + classid); + return false; + } + + if (seal.mode) { + warn && say(Config.Diablo.SealWarning); + return true; + } + + // Clear around Infector seal, Any leftover abyss knights casting decrep is bad news with Infector + if ((infSeal || i > 1) && me.getMobCount() > 1) { + Attack.clear(15); + // Move back to seal + usetk ? Pather.moveNearUnit(seal, 15) : Pather.moveToUnit(seal, seisSeal ? 5 : 2, seisSeal ? 5 : 0); + } + + if (usetk && this.tkSeal(seal)) { + return seal.mode; + } else { + usetk && (usetk = false); + + if (classid === sdk.objects.DiabloSealInfector && me.assassin && this.infLayout === 1) { + if (Config.UseTraps) { + let check = ClassAttack.checkTraps({ x: 7899, y: 5293 }); + check && ClassAttack.placeTraps({ x: 7899, y: 5293 }, check); + } + } + + seisSeal ? Misc.poll(function () { + // stupid diablo shit, walk around the de-seis seal clicking it until we find "the spot"...sigh + if (!seal.mode) { + Pather.walkTo(seal.x + (rand(-1, 1)), seal.y + (rand(-1, 1))); + clickUnitAndWait(0, 0, seal) || seal.interact(); + } + return !!seal.mode; + }, 3000, 60) : seal.interact(); + + // de seis optimization + if (seisSeal && Attack.validSpot(seal.x + 15, seal.y)) { + Pather.walkTo(seal.x + 15, seal.y); + } else { + Pather.walkTo(seal.x - 5, seal.y - 5); + } + } + + delay(seisSeal ? 1000 + me.ping : 500 + me.ping); + + if (seal.mode) { + break; + } + } + + return (!!seal && seal.mode); + }, + + /** + * @param {boolean} openSeal + * @returns {boolean} + */ + vizierSeal: function (openSeal = true) { + console.log("Viz layout " + Common.Diablo.vizLayout); + let path = (Common.Diablo.vizLayout === 1 ? this.starToVizA : this.starToVizB); + let distCheck = path.last(); + + if (Config.Diablo.SealLeader || Config.Diablo.Fast) { + Common.Diablo.vizLayout === 1 ? Pather.moveTo(7708, 5269) : Pather.moveTo(7647, 5267); + Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, 35, 3000, true); + Config.Diablo.SealLeader && Pather.makePortal() && say("in"); + } + + distCheck.distance > 30 && this.followPath(Common.Diablo.vizLayout === 1 ? this.starToVizA : this.starToVizB); + + if (openSeal + && ![sdk.objects.DiabloSealVizier2, sdk.objects.DiabloSealVizier].every(s => Common.Diablo.openSeal(s))) { + throw new Error("Failed to open Vizier seals."); + } + + delay(1 + me.ping); + let cb = () => { + let viz = Game.getMonster(getLocaleString(sdk.locale.monsters.GrandVizierofChaos)); + return viz && (viz.distance < Skill.getRange(Config.AttackSkill[1]) || viz.dead); + }; + /** + * @todo better coords or maybe a delay, viz appears in different locations and sometimes its right where we are moving to + * which is okay for hammerdins or melee chars but not for soft chars like sorcs + */ + Common.Diablo.vizLayout === 1 + ? Pather.moveToEx(7691, 5292, { callback: cb }) + : Pather.moveToEx(7695, 5316, { callback: cb }); + + if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.GrandVizierofChaos))) { + throw new Error("Failed to kill Vizier"); + } + + Config.Diablo.SealLeader && say("out"); + + return true; + }, + + /** + * @param {boolean} openSeal + * @returns {boolean} + */ + seisSeal: function (openSeal = true) { + console.log("Seis layout " + Common.Diablo.seisLayout); + let path = (Common.Diablo.seisLayout === 1 ? this.starToSeisA : this.starToSeisB); + let distCheck = path.last(); + + if (Config.Diablo.SealLeader || Config.Diablo.Fast) { + Common.Diablo.seisLayout === 1 ? Pather.moveTo(7767, 5147) : Pather.moveTo(7820, 5147); + Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, 35, 3000, true); + Config.Diablo.SealLeader && Pather.makePortal() && say("in"); + } + + if (distCheck.distance > 30) { + this.followPath(Common.Diablo.seisLayout === 1 ? this.starToSeisA : this.starToSeisB); + } + + if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealSeis)) { + throw new Error("Failed to open de Seis seal."); + } + let cb = () => { + let seis = Game.getMonster(getLocaleString(sdk.locale.monsters.LordDeSeis)); + return seis && (seis.distance < Skill.getRange(Config.AttackSkill[1]) || seis.dead); + }; + Common.Diablo.seisLayout === 1 + ? Pather.moveToEx(7798, 5194, { callback: cb }) + : Pather.moveToEx(7796, 5155, { callback: cb }); + try { + if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.LordDeSeis))) { + throw new Error("Failed to kill de Seis"); + } + } catch (e) { + // sometimes we fail just because we aren't in range, + Pather.moveToEx(this.starCoords.x, this.starCoords.y, { minDist: 15, callback: () => { + let seis = Game.getMonster(getLocaleString(sdk.locale.monsters.LordDeSeis)); + return seis && (seis.distance < 30 || seis.dead); + } }); + } + + Config.Diablo.SealLeader && say("out"); + + return true; + }, + + /** + * @param {boolean} openSeal + * @returns {boolean} + */ + infectorSeal: function (openSeal = true) { + Precast.doPrecast(true); + console.log("Inf layout " + Common.Diablo.infLayout); + let path = (Common.Diablo.infLayout === 1 ? this.starToInfA : this.starToInfB); + let distCheck = path.last(); + + if (Config.Diablo.SealLeader || Config.Diablo.Fast) { + Common.Diablo.infLayout === 1 ? Pather.moveTo(7860, 5314) : Pather.moveTo(7909, 5317); + Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, 35, 3000, true); + Config.Diablo.SealLeader && Pather.makePortal() && say("in"); + } + + distCheck.distance > 70 && this.followPath(Common.Diablo.infLayout === 1 ? this.starToInfA : this.starToInfB); + + let cb = () => { + let inf = Game.getMonster(getLocaleString(sdk.locale.monsters.InfectorofSouls)); + return inf && (inf.distance < Skill.getRange(Config.AttackSkill[1]) || inf.dead); + }; + + let moveToLoc = () => { + if (Common.Diablo.infLayout === 1) { + (me.sorceress || me.assassin) && Pather.moveToEx(7876, 5296, { callback: cb }); + delay(1 + me.ping); + } else { + delay(1 + me.ping); + Pather.moveToEx(7928, 5295, { callback: cb }); + } + }; + + if (Config.Diablo.Fast) { + if (openSeal + && ![ + sdk.objects.DiabloSealInfector2, sdk.objects.DiabloSealInfector + ].every(s => Common.Diablo.openSeal(s))) { + throw new Error("Failed to open Infector seals."); + } + moveToLoc(); + + if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.InfectorofSouls))) { + throw new Error("Failed to kill Infector"); + } + } else { + if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealInfector)) { + throw new Error("Failed to open Infector seals."); + } + moveToLoc(); + + if (!Common.Diablo.getBoss(getLocaleString(sdk.locale.monsters.InfectorofSouls))) { + throw new Error("Failed to kill Infector"); + } + if (openSeal && !Common.Diablo.openSeal(sdk.objects.DiabloSealInfector2)) { + throw new Error("Failed to open Infector seals."); + } + // wait until seal has been popped to avoid missing diablo due to wait time ending before he spawns, happens if leader does town chores after seal boss + !openSeal && [3, "infector"].includes(Common.Diablo.sealOrder.last()) && Misc.poll(() => { + if (Common.Diablo.diabloSpawned) return true; + + let lastSeal = Game.getObject(sdk.objects.DiabloSealInfector2); + if (lastSeal && lastSeal.mode) { + return true; + } + return false; + }, Time.minutes(3), 1000); + } + + Config.Diablo.SealLeader && say("out"); + + return true; + }, + + hammerdinPreAttack: function (name, amount = 5) { + if (me.paladin && Config.AttackSkill[1] === sdk.skills.BlessedHammer) { + let target = Game.getMonster(name); + + if (!target || !target.attackable) return true; + + let positions = [[6, 11], [0, 8], [8, -1], [-9, 2], [0, -11], [8, -8]]; + + for (let i = 0; i < positions.length; i += 1) { + // check if we can move there + if (Attack.validSpot(target.x + positions[i][0], target.y + positions[i][1])) { + Pather.moveTo(target.x + positions[i][0], target.y + positions[i][1]); + Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); + + for (let n = 0; n < amount; n += 1) { + Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Left); + } + + return true; + } + } + } + + return false; + }, + + preattack: function (id) { + let coords = (() => { + switch (id) { + case getLocaleString(sdk.locale.monsters.GrandVizierofChaos): + return Common.Diablo.vizLayout === 1 ? [7676, 5295] : [7684, 5318]; + case getLocaleString(sdk.locale.monsters.LordDeSeis): + return Common.Diablo.seisLayout === 1 ? [7778, 5216] : [7775, 5208]; + case getLocaleString(sdk.locale.monsters.InfectorofSouls): + return Common.Diablo.infLayout === 1 ? [7913, 5292] : [7915, 5280]; + default: + return []; + } + })(); + if (!coords.length) return false; + + switch (me.classid) { + case sdk.player.class.Sorceress: + if ([ + sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb, sdk.skills.FireWall + ].includes(Config.AttackSkill[1])) { + me.skillDelay && delay(500); + Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, coords[0], coords[1]); + + return true; + } + + break; + case sdk.player.class.Paladin: + return this.hammerdinPreAttack(id, 8); + case sdk.player.class.Assassin: + if (Config.UseTraps) { + let trapCheck = ClassAttack.checkTraps({ x: coords[0], y: coords[1] }); + + if (trapCheck) { + ClassAttack.placeTraps({ x: coords[0], y: coords[1] }, 5); + + return true; + } + } + + break; + } + + return false; + }, + + getBoss: function (name) { + let glow = Game.getObject(sdk.objects.SealGlow); + + if (this.waitForGlow) { + while (true) { + if (!this.preattack(name)) { + delay(500); + } + + glow = Game.getObject(sdk.objects.SealGlow); + + if (glow) { + break; + } + } + } + + for (let i = 0; i < 16; i += 1) { + let boss = Game.getMonster(name); + + if (boss) { + Common.Diablo.hammerdinPreAttack(name, 8); + return (Config.Diablo.Fast ? Attack.kill(boss) : Attack.clear(40, 0, boss, this.sort)); + } + + delay(250); + } + + return !!glow; + }, + + moveToStar: function () { + switch (me.classid) { + case sdk.player.class.Amazon: + case sdk.player.class.Sorceress: + case sdk.player.class.Necromancer: + case sdk.player.class.Assassin: + return Pather.moveNear(7791, 5293, (me.sorceress ? 35 : 25), { returnSpotOnError: true }); + case sdk.player.class.Paladin: + case sdk.player.class.Druid: + case sdk.player.class.Barbarian: + return Pather.moveTo(7788, 5292); + } + + return false; + }, + + diabloPrep: function () { + if (Config.Diablo.SealLeader) { + Pather.moveTo(7763, 5267); + Pather.makePortal() && say("in"); + Pather.moveTo(7788, 5292); + } + + this.moveToStar(); + + let tick = getTickCount(); + + while (getTickCount() - tick < this.diaWaitTime) { + if (getTickCount() - tick >= Time.seconds(8)) { + switch (me.classid) { + case sdk.player.class.Sorceress: + if ([ + sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb, sdk.skills.FireWall + ].includes(Config.AttackSkill[1])) { + Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 7793 + rand(-1, 1), 5293); + } + + delay(500); + + break; + case sdk.player.class.Paladin: + Skill.setSkill(Config.AttackSkill[2]); + if (Config.AttackSkill[1] === sdk.skills.BlessedHammer) { + Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Left); + } + + break; + case sdk.player.class.Druid: + if ([sdk.skills.Tornado, sdk.skills.Fissure, sdk.skills.Volcano].includes(Config.AttackSkill[3])) { + Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 7793 + rand(-1, 1), 5293); + + break; + } + + delay(500); + + break; + case sdk.player.class.Assassin: + if (Config.UseTraps) { + let trapCheck = ClassAttack.checkTraps({ x: 7793, y: 5293 }); + trapCheck && ClassAttack.placeTraps({ x: 7793, y: 5293, classid: sdk.monsters.Diablo }, trapCheck); + } + + if (Config.AttackSkill[1] === sdk.skills.ShockWeb) { + Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 7793, 5293); + } + + delay(500); + + break; + default: + delay(500); + + break; + } + } else { + delay(500); + } + + if (Game.getMonster(sdk.monsters.Diablo)) { + return true; + } + } + + throw new Error("Diablo not found"); + }, + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Common/Leecher.js b/d2bs/kolbot/libs/core/Common/Leecher.js new file mode 100644 index 000000000..f7a65262e --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Leecher.js @@ -0,0 +1,51 @@ +/** +* @filename Leecher.js +* @author theBGuy +* @desc Leecher tools +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + Object.defineProperty(Common, "Leecher", { + value: { + leadTick: 0, + leader: null, + killLeaderTracker: false, + currentScript: "", + nextScriptAreas: [sdk.areas.TowerCellarLvl5, sdk.areas.PitLvl1, sdk.areas.PitLvl2, sdk.areas.BurialGrounds, + sdk.areas.CatacombsLvl4, sdk.areas.MooMooFarm, sdk.areas.DuranceofHateLvl3, + sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber + ], + + leaderTracker: function () { + if (Common.Leecher.killLeaderTracker) return false; + // check every 3 seconds + if (getTickCount() - Common.Leecher.leadTick < 3000) return true; + Common.Leecher.leadTick = getTickCount(); + + // check again in another 3 seconds if game wasn't ready + if (!me.gameReady) return true; + if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); + + let party = getParty(Common.Leecher.leader); + + if (party) { + // Player has moved on to another script + if (Common.Leecher.nextScriptAreas.includes(party.area)) { + if (Loader.scriptName() === Common.Leecher.currentScript) { + Common.Leecher.killLeaderTracker = true; + throw new Error("Party leader is running a new script"); + } else { + // kill process + return false; + } + } + } + + return true; + } + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Common/Smith.js b/d2bs/kolbot/libs/core/Common/Smith.js new file mode 100644 index 000000000..f709cebb8 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Smith.js @@ -0,0 +1,29 @@ +/** +* @filename Smith.js +* @author theBGuy +* @desc Complete smith quest +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + Object.defineProperty(Common, "Smith", { + value: function () { + if (!Pather.moveToPreset(sdk.areas.Barracks, sdk.unittype.Object, sdk.quest.chest.MalusHolder)) { + throw new Error("Failed to move to the Smith"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.TheSmith)); + let malusChest = Game.getObject(sdk.quest.chest.MalusHolder); + !!malusChest && malusChest.distance > 5 && Pather.moveToUnit(malusChest); + Misc.openChest(malusChest); + let malus = Misc.poll(() => Game.getItem(sdk.quest.item.HoradricMalus), 1000, 100); + Pickit.pickItem(malus); + Town.goToTown(); + Town.npcInteract("Charsi"); + + return !!Misc.checkQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete); + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Common/Tools.js b/d2bs/kolbot/libs/core/Common/Tools.js new file mode 100644 index 000000000..33de53473 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Tools.js @@ -0,0 +1,361 @@ +/** +* @filename Tools.js +* @author theBGuy +* @desc Tools for Toolsthread and its variations (MapToolsThread, ect) +* +*/ + +(function (Common) { + typeof Common !== "object" && (Common = {}); + Object.defineProperty(Common, "Toolsthread", { + value: new function Toolsthread () { + this.pots = { + Health: 0, + Mana: 1, + Rejuv: 2, + MercHealth: 3, + MercRejuv: 4 + }; + this.pingTimer = []; + this.pauseScripts = []; + this.stopScripts = []; + this.timerLastDrink = []; + this.cloneWalked = false; + + /** + * @param {boolean} print + * @returns {boolean} + */ + this.checkPing = function (print = true) { + // Quit after at least 5 seconds in game + if (getTickCount() - me.gamestarttime < 5000 || !me.gameReady) return false; + + for (let i = 0; i < Config.PingQuit.length; i += 1) { + if (Config.PingQuit[i].Ping > 0) { + if (me.ping >= Config.PingQuit[i].Ping) { + me.overhead("High Ping"); + + if (this.pingTimer[i] === undefined || this.pingTimer[i] === 0) { + this.pingTimer[i] = getTickCount(); + } + + if (getTickCount() - this.pingTimer[i] >= Config.PingQuit[i].Duration * 1000) { + if (print) { + D2Bot.printToConsole( + "High ping (" + me.ping + "/" + Config.PingQuit[i].Ping + ") - leaving game.", + sdk.colors.D2Bot.Red + ); + } + scriptBroadcast("pingquit"); + scriptBroadcast("quit"); + + return true; + } + } else { + this.pingTimer[i] = 0; + } + } + } + + return false; + }, + + this.initQuitList = function () { + let temp = []; + + for (let profile of Config.QuitList) { + if (FileTools.exists("data/" + profile + ".json")) { + let string = FileAction.read("data/" + profile + ".json"); + + if (string) { + let obj = JSON.parse(string); + + if (obj && obj.hasOwnProperty("name")) { + temp.push(obj.name); + } + } + } + } + + Config.QuitList = temp.slice(0); + }; + + this.togglePause = function () { + for (let curr of this.pauseScripts) { + let script = getScript(curr); + + if (script) { + if (script.running) { + curr === "default.dbj" && console.log("ÿc1Pausing."); + script.pause(); + } else { + if (curr === "default.dbj") { + console.log("ÿc2Resuming."); + } + script.resume(); + } + } + } + + return true; + }, + + this.stopDefault = function () { + for (let curr of this.stopScripts) { + try { + let script = getScript(curr); + if (!!script && script.running) { + script.stop(); + while (script.running) { + delay(3); + } + } + } catch (e) { + console.error(e); + } + } + + return true; + }; + + this.exit = function (chickenExit = false) { + chickenExit && D2Bot.updateChickens(); + Config.LogExperience && Experience.log(); + // clearAllEvents(); + console.log("ÿc8Run duration ÿc2" + (Time.format(getTickCount() - me.gamestarttime))); + this.stopDefault(); + quit(); + }; + + /** + * @param {number} pottype + * @param {number} type + * @returns {ItemUnit | false} + */ + this.getPotion = function (pottype, type) { + if (!pottype) return false; + if (!me.gameReady) return false; + + let items = me.getItemsEx() + .filter(function (item) { + return item.itemType === pottype; + }); + if (items.length === 0) return false; + + // Get highest id = highest potion first + items.sort(function (a, b) { + return b.classid - a.classid; + }); + + for (let item of items) { + if (type < this.pots.MercHealth && item.isInInventory && item.itemType === pottype) { + console.log("ÿc2Drinking potion from inventory."); + return item; + } + + if (item.isInBelt && item.itemType === pottype) { + console.log("ÿc2" + (type > 2 ? "Giving Merc" : "Drinking") + " potion from belt."); + return item; + } + } + + return false; + }; + + /** + * @param {number} type + * @returns {boolean} + * @todo add stamina/thawing/antidote pot drinking here + */ + this.drinkPotion = function (type) { + if (type === undefined) return false; + if (!me.gameReady) return false; + let tNow = getTickCount(); + + switch (type) { + case this.pots.Health: + case this.pots.Mana: + if ((this.timerLastDrink[type] + && (tNow - this.timerLastDrink[type] < 1000)) + || me.getState(type === this.pots.Health ? sdk.states.HealthPot : sdk.states.ManaPot)) { + return false; + } + + break; + case this.pots.Rejuv: + // small delay for juvs just to prevent using more at once + if (this.timerLastDrink[type] && (tNow - this.timerLastDrink[type] < 300)) { + return false; + } + + break; + case this.pots.MercRejuv: + // larger delay for juvs just to prevent using more at once, considering merc update rate + if (this.timerLastDrink[type] && (tNow - this.timerLastDrink[type] < 2000)) { + return false; + } + + break; + default: + if (this.timerLastDrink[type] && (tNow - this.timerLastDrink[type] < 8000)) { + return false; + } + + break; + } + + let pottype = (() => { + switch (type) { + case this.pots.Health: + case this.pots.MercHealth: + return sdk.items.type.HealingPotion; + case this.pots.Mana: + return sdk.items.type.ManaPotion; + default: + return sdk.items.type.RejuvPotion; + } + })(); + + let potion = this.getPotion(pottype, type); + + if (potion) { + if (me.dead) return false; + + if (me.mode === sdk.player.mode.SkillActionSequence) { + while (me.mode === sdk.player.mode.SkillActionSequence) { + delay (25); + } + } + + try { + type < this.pots.MercHealth + ? potion.interact() + : Packet.useBeltItemForMerc(potion); + } catch (e) { + console.error(e); + } + + this.timerLastDrink[type] = getTickCount(); + + return true; + } + + return false; + }; + + this.checkVipers = function () { + let monster = Game.getMonster(sdk.monsters.TombViper2); + + if (monster) { + do { + if (monster.getState(sdk.states.Revive)) { + let owner = monster.getParent(); + + if (owner && owner.name !== me.name) { + D2Bot.printToConsole("Revived Tomb Vipers found. Leaving game.", sdk.colors.D2Bot.Red); + + return true; + } + } + } while (monster.getNext()); + } + + return false; + }; + + this.getIronGolem = function () { + let golem = Game.getMonster(sdk.summons.IronGolem); + + if (golem) { + do { + let owner = golem.getParent(); + + if (owner && owner.name === me.name) { + return copyUnit(golem); + } + } while (golem.getNext()); + } + + return false; + }; + + this.getNearestPreset = function () { + let id; + /** @type {Array} */ + let presets = getPresetUnits(me.area); + let dist = 99; + + for (let unit of presets) { + let coords = unit.realCoords(); + if (getDistance(me, coords.x, coords.y) < dist) { + dist = getDistance(me, coords.x, coords.y); + id = unit.type + " " + unit.id; + } + } + + return id || ""; + }, + + /** + * @param {MeType | MercUnit} unit + * @returns {string} + */ + this.getStatsString = function (unit) { + let realFCR = unit.getStat(sdk.stats.FCR); + let realIAS = unit.getStat(sdk.stats.IAS); + let realFBR = unit.getStat(sdk.stats.FBR); + let realFHR = unit.getStat(sdk.stats.FHR); + // me.getStat(sdk.stats.FasterCastRate) will return real FCR from gear + Config.FCR from char cfg + + if (unit === me) { + realFCR -= Config.FCR; + realIAS -= Config.IAS; + realFBR -= Config.FBR; + realFHR -= Config.FHR; + } + + let maxHellFireRes = 75 + unit.getStat(sdk.stats.MaxFireResist); + let hellFireRes = unit.getRes(sdk.stats.FireResist, sdk.difficulty.Hell); + hellFireRes > maxHellFireRes && (hellFireRes = maxHellFireRes); + + let maxHellColdRes = 75 + unit.getStat(sdk.stats.MaxColdResist); + let hellColdRes = unit.getRes(sdk.stats.ColdResist, sdk.difficulty.Hell); + hellColdRes > maxHellColdRes && (hellColdRes = maxHellColdRes); + + let maxHellLightRes = 75 + unit.getStat(sdk.stats.MaxLightResist); + let hellLightRes = unit.getRes(sdk.stats.LightResist, sdk.difficulty.Hell); + hellLightRes > maxHellLightRes && (hellLightRes = maxHellLightRes); + + let maxHellPoisonRes = 75 + unit.getStat(sdk.stats.MaxPoisonResist); + let hellPoisonRes = unit.getRes(sdk.stats.PoisonResist, sdk.difficulty.Hell); + hellPoisonRes > maxHellPoisonRes && (hellPoisonRes = maxHellPoisonRes); + + let str = + "ÿc4Character Level: ÿc0" + unit.charlvl + + (unit === me ? " ÿc4Difficulty: ÿc0" + sdk.difficulty.nameOf(me.diff) + + " ÿc4HighestActAvailable: ÿc0" + me.highestAct : "") + "\n" + + "ÿc1FR: ÿc0" + unit.getStat(sdk.stats.FireResist) + "ÿc1 Applied FR: ÿc0" + unit.fireRes + + "/ÿc3 CR: ÿc0" + unit.getStat(sdk.stats.ColdResist) + "ÿc3 Applied CR: ÿc0" + unit.coldRes + + "/ÿc9 LR: ÿc0" + unit.getStat(sdk.stats.LightResist) + "ÿc9 Applied LR: ÿc0" + unit.lightRes + + "/ÿc2 PR: ÿc0" + unit.getStat(sdk.stats.PoisonResist) + "ÿc2 Applied PR: ÿc0" + unit.poisonRes + "\n" + + (!me.hell + ? "Hell res: ÿc1" + hellFireRes + + "ÿc0/ÿc3" + hellColdRes + "ÿc0/ÿc9" + hellLightRes + "ÿc0/ÿc2" + hellPoisonRes + "ÿc0\n" : "") + + "ÿc4MF: ÿc0" + unit.getStat(sdk.stats.MagicBonus) + "ÿc4 GF: ÿc0" + unit.getStat(sdk.stats.GoldBonus) + + " ÿc4FCR: ÿc0" + realFCR + " ÿc4IAS: ÿc0" + realIAS + " ÿc4FBR: ÿc0" + realFBR + + " ÿc4FHR: ÿc0" + realFHR + " ÿc4FRW: ÿc0" + unit.getStat(sdk.stats.FRW) + "\n" + + "ÿc4CB: ÿc0" + unit.getStat(sdk.stats.CrushingBlow) + " ÿc4DS: ÿc0" + unit.getStat(sdk.stats.DeadlyStrike) + + " ÿc4OW: ÿc0" + unit.getStat(sdk.stats.OpenWounds) + + " ÿc1LL: ÿc0" + unit.getStat(sdk.stats.LifeLeech) + " ÿc3ML: ÿc0" + unit.getStat(sdk.stats.ManaLeech) + + " ÿc8DR: ÿc0" + unit.getStat(sdk.stats.DamageResist) + + "% + " + unit.getStat(sdk.stats.NormalDamageReduction) + + " ÿc8MDR: ÿc0" + unit.getStat(sdk.stats.MagicResist) + + "% + " + unit.getStat(sdk.stats.MagicDamageReduction) + "\n" + + (unit.getStat(sdk.stats.CannotbeFrozen) > 0 ? "ÿc3Cannot be Frozenÿc1\n" : "\n"); + + return str; + }; + }, + configurable: true, + }); +})(Common); diff --git a/d2bs/kolbot/libs/core/Config.js b/d2bs/kolbot/libs/core/Config.js new file mode 100644 index 000000000..025ee987b --- /dev/null +++ b/d2bs/kolbot/libs/core/Config.js @@ -0,0 +1,767 @@ +/** +* @filename Config.js +* @author kolton, theBGuy +* @desc config loading and default config values storage +* +*/ + +/** @type {Record} */ +const Scripts = {}; + +let Config = { + init: function (notify = true) { + const className = sdk.player.class.nameOf(me.classid); + const formats = ((className, profile, charname, realm) => ({ + // Class.Profile.js + 1: className + "." + profile + ".js", + // Realm.Class.Charname.js + 2: realm + "." + className + "." + charname + ".js", + // Class.Charname.js + 3: className + "." + charname + ".js", + // Profile.js + 4: profile + ".js", + // Class.js + 5: className + ".js", + }))(className, me.profile, me.charname, me.realm); + let configFilename = ""; + + for (let i = 0; i < 5; i++) { + switch (i) { + case 0: // Custom config + includeIfNotIncluded("config/_customconfig.js"); + + for (let n in CustomConfig) { + if (CustomConfig.hasOwnProperty(n) && CustomConfig[n].includes(me.profile)) { + notify && console.log("ÿc2Loading custom config: ÿc9" + n + ".js"); + configFilename = n + ".js"; + + break; + } + } + + break; + default: + configFilename = formats[i]; + + break; + } + + if (configFilename && FileTools.exists("libs/config/" + configFilename)) { + break; + } + } + + if (FileTools.exists("libs/config/" + configFilename)) { + try { + if (!include("config/" + configFilename)) { + throw new Error(); + } + } catch (e1) { + throw new Error("Failed to load character config."); + } + } else { + if (notify) { + console.log("ÿc1" + className + "." + me.charname + ".js not found!"); // Use the primary format + console.log("ÿc1Loading default config."); + } + + // Try to find default config + if (!FileTools.exists("libs/config/" + className + ".js")) { + D2Bot.printToConsole("Not going well? Read the guides: https://github.com/blizzhackers/documentation"); + throw new Error("ÿc1Default config not found. \nÿc9 Try reading the kolbot guides."); + } + + try { + if (!include("config/" + className + ".js")) { + throw new Error(); + } + Config._defaultLoaded = true; + } catch (e) { + throw new Error("ÿc1Failed to load default config."); + } + } + + try { + LoadConfig.call(); + Config.Loaded = true; + } catch (e2) { + if (notify) { + console.error(e2); + + throw new Error("Config.init: Error in character config."); + } + } + + // Always set the orginal say function + global._say = global.say; + if (Config.Silence && !Config.LocalChat.Enabled) { + // Override the say function with print, so it just gets printed to console + global.say = (what) => console.log("Tryed to say: " + what); + } + + try { + if (Config.AutoBuild.Enabled === true && includeIfNotIncluded("core/Auto/AutoBuild.js")) { + AutoBuild.initialize(); + } + } catch (e3) { + console.log("ÿc8Error in libs/core/AutoBuild.js (AutoBuild system is not active!)"); + console.error(e3); + } + }, + + // dev + _defaultLoaded: false, + Loaded: false, + DebugMode: { + Path: false, + Stack: false, + Memory: false, + Skill: false, + Town: false, + }, + + // Time + StartDelay: 0, + PickDelay: 0, + AreaDelay: 0, + MinGameTime: 0, + MaxGameTime: 0, + + // Healing and chicken + LifeChicken: 0, + ManaChicken: 0, + UseHP: 0, + UseMP: 0, + UseRejuvHP: 0, + UseRejuvMP: 0, + UseMercHP: 0, + UseMercRejuv: 0, + MercChicken: 0, + IronGolemChicken: 0, + HealHP: 0, + HealMP: 0, + HealStatus: false, + TownHP: 0, + TownMP: 0, + + // special pots + StackThawingPots: { + enabled: false, + quantity: 12, + }, + StackAntidotePots: { + enabled: false, + quantity: 12, + }, + StackStaminaPots: { + enabled: false, + quantity: 12, + }, + + // General + AutoMap: false, + LastMessage: "", + UseMerc: false, + MercWatch: false, + LowGold: 0, + StashGold: 0, + FieldID: { + Enabled: false, + PacketID: true, + UsedSpace: 90, + }, + DroppedItemsAnnounce: { + Enable: false, + Quality: [], + LogToOOG: false, + OOGQuality: [] + }, + CainID: { + Enable: false, + MinGold: 0, + MinUnids: 0 + }, + Inventory: [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ], + SortSettings: { + SortInventory: true, + SortStash: true, + PlugYStash: false, + ItemsSortedFromLeft: [], // default: everything not in Config.ItemsSortedFromRight + ItemsSortedFromRight: [ + // (NOTE: default pickit is fastest if the left side is open) + sdk.items.SmallCharm, sdk.items.LargeCharm, sdk.items.GrandCharm, // sort charms from the right + sdk.items.TomeofIdentify, sdk.items.TomeofTownPortal, sdk.items.Key, // sort tomes and keys to the right + // sort all inventory potions from the right + sdk.items.RejuvenationPotion, sdk.items.FullRejuvenationPotion, + sdk.items.MinorHealingPotion, sdk.items.LightHealingPotion, + sdk.items.HealingPotion, sdk.items.GreaterHealingPotion, sdk.items.SuperHealingPotion, + sdk.items.MinorManaPotion, sdk.items.LightManaPotion, + sdk.items.ManaPotion, sdk.items.GreaterManaPotion, sdk.items.SuperManaPotion + ], + PrioritySorting: true, + ItemsSortedFromLeftPriority: [/*605, 604, 603, 519, 518*/], // (NOTE: the earlier in the index, the further to the Left) + ItemsSortedFromRightPriority: [ + // (NOTE: the earlier in the index, the further to the Right) + // sort charms from the right, GC > LC > SC + sdk.items.GrandCharm, sdk.items.LargeCharm, sdk.items.SmallCharm, + sdk.items.TomeofIdentify, sdk.items.TomeofTownPortal, sdk.items.Key + ], + }, + LocalChat: { + Enabled: false, + Toggle: false, + Mode: 0 + }, + Silence: false, + PublicMode: false, + PartyAfterScript: false, + AnnounceGameTimeRemaing: false, + + /** @type {string[]} */ + Greetings: [], + + /** @type {string[]} */ + DeathMessages: [], + + /** @type {string[]} */ + Congratulations: [], + ShitList: false, + UnpartyShitlisted: false, + Leader: "", + QuitList: [], + QuitListMode: 0, + QuitListDelay: [], + HPBuffer: 0, + MPBuffer: 0, + RejuvBuffer: 0, + PickRange: 40, + MakeRoom: true, + ClearInvOnStart: false, + FastPick: false, + FastPickRange: 0, + ManualPlayPick: false, + OpenChests: { + Enabled: false, + Range: 15, + Types: ["chest", "chest3", "armorstand", "weaponrack"] + }, + /** + * Each entry should be a tuple of [nipline, filename] + * @example [["[name] == ThulRune # # [maxquantity] == 1", "HeartOfTheOak"]] + * @type {[string, string][]} + */ + PickitLines: [], + PickitFiles: [], + BeltColumn: [], + MinColumn: [], + SkipId: [], + SkipEnchant: [], + SkipImmune: [], + SkipAura: [], + SkipException: [], + /** @type {number[]} */ + ScanShrines: [], + AutoShriner: false, + UseWells: { + HpPercent: 0, + MpPercent: 0, + StaminaPercent: 0, + StatusEffects: false, + }, + Debug: false, + + AutoMule: { + Trigger: [], + Force: [], + Exclude: [] + }, + + ItemInfo: false, + ItemInfoQuality: [], + + LogKeys: false, + LogOrgans: true, + LogLowRunes: false, + LogMiddleRunes: false, + LogHighRunes: true, + LogLowGems: false, + LogHighGems: false, + SkipLogging: [], + ShowCubingInfo: true, + + Cubing: false, + CubeRepair: false, + RepairPercent: 40, + Recipes: [], + MakeRunewords: false, + /** + * @type {[runeword, string | number, ?boolean][]} + */ + Runewords: [], + KeepRunewords: [], + LadderOveride: false, + Gamble: false, + GambleItems: [], + GambleGoldStart: 0, + GambleGoldStop: 0, + MiniShopBot: false, + TeleSwitch: false, + MFSwitchPercent: 0, + PrimarySlot: -1, + LogExperience: false, + TownCheck: false, + PingQuit: [{ Ping: 0, Duration: 0 }], + PacketShopping: false, + + // Fastmod + FCR: 0, + FHR: 0, + FBR: 0, + IAS: 0, + PacketCasting: 0, + WaypointMenu: true, + + // Anti-hostile + AntiHostile: false, + RandomPrecast: false, + HostileAction: 0, + TownOnHostile: false, + ViperCheck: false, + + // DClone + StopOnDClone: false, + SoJWaitTime: 0, + KillDclone: false, + DCloneQuit: false, + DCloneWaitTime: 30, + + // Experimental + FastParty: false, + AutoEquip: false, + + // GameData + ChampionBias: 60, + + UseCta: true, + ForcePrecast: false, + + // Attack specific + Dodge: false, + DodgeRange: 15, + DodgeHP: 100, + AttackSkill: [], + LowManaSkill: [], + /** @type {Record} */ + CustomAttack: {}, + /** @type {Record} */ + CustomPreAttack: {}, + /** @type {{ check: (unit: Monster) => boolean, attack: [number, number] }[]} */ + AdvancedCustomAttack: [], + TeleStomp: false, + NoTele: false, + ClearType: false, + ClearPath: false, + BossPriority: false, + MaxAttackCount: 300, + ChargeCast: { + skill: -1, + spectype: 0x7, + /** @type {(number|string)[]} */ + classids: [], + }, + + // Amazon specific + LightningFuryDelay: 0, + UseInnerSight: false, + UseSlowMissiles: false, + UseDecoy: false, + SummonValkyrie: false, + + // Sorceress specific + UseTelekinesis: false, + CastStatic: false, + StaticList: [], + UseEnergyShield: false, + UseColdArmor: true, + + // Necromancer specific + Golem: 0, + ActiveSummon: false, + Skeletons: 0, + SkeletonMages: 0, + Revives: 0, + ReviveUnstackable: false, + PoisonNovaDelay: 2000, + Curse: [], + CustomCurse: [], + ExplodeCorpses: 0, + + // Paladin speficic + Redemption: [0, 0], + Charge: false, + Vigor: false, + RunningAura: -1, + AvoidDolls: false, + + // Barbarian specific + FindItem: false, + FastFindItem: false, + FindItemSwitch: false, + UseWarcries: true, + + // Druid specific + Wereform: 0, + SummonRaven: 0, + SummonAnimal: 0, + SummonVine: 0, + SummonSpirit: 0, + + // Assassin specific + UseTraps: false, + Traps: [], + BossTraps: [], + UseFade: false, + UseBoS: false, + UseVenom: false, + UseBladeShield: false, + UseCloakofShadows: false, + AggressiveCloak: false, + SummonShadow: false, + + // Custom Attack + CustomClassAttack: "", // If set it loads core/Attack/[CustomClassAttack].js + + MapMode: { + UseOwnItemFilter: false, + }, + + Advertise: { + Enabled: false, + Message: "", + Interval: [0, 0], + }, + + // Script specific + MFLeader: false, + Mausoleum: { + KillBishibosh: false, + KillBloodRaven: false, + ClearCrypt: false + }, + Cows: { + DontMakePortal: false, + JustMakePortal: false, + KillKing: false + }, + Tombs: { + KillDuriel: false, + WalkClear: false, + }, + Eldritch: { + OpenChest: false, + KillSharptooth: false, + KillShenk: false, + KillDacFarren: false + }, + Pindleskin: { + UseWaypoint: false, + KillNihlathak: false, + ViperQuit: false + }, + Nihlathak: { + ViperQuit: false, + UseWaypoint: false, + }, + Pit: { + ClearPath: false, + ClearPit1: false + }, + Snapchip: { + ClearIcyCellar: false + }, + Frozenstein: { + ClearFrozenRiver: false + }, + Rakanishu: { + KillGriswold: false + }, + AutoBaal: { + Leader: "", + FindShrine: false, + LeechSpot: [15115, 5050], + LongRangeSupport: false + }, + KurastChests: { + LowerKurast: false, + Bazaar: false, + Sewers1: false, + Sewers2: false + }, + Countess: { + KillGhosts: false + }, + Baal: { + DollQuit: false, + SoulQuit: false, + KillBaal: false, + HotTPMessage: "Hot TP!", + SafeTPMessage: "Safe TP!", + BaalMessage: "Baal!" + }, + BaalAssistant: { + KillNihlathak: false, + FastChaos: false, + Wait: 120, + Helper: false, + GetShrine: false, + GetShrineWaitForHotTP: false, + DollQuit: false, + SoulQuit: false, + SkipTP: false, + WaitForSafeTP: false, + KillBaal: false, + HotTPMessage: [], + SafeTPMessage: [], + BaalMessage: [], + NextGameMessage: [] + }, + BaalHelper: { + Wait: 120, + KillNihlathak: false, + FastChaos: false, + DollQuit: false, + KillBaal: false, + SkipTP: false + }, + Corpsefire: { + ClearDen: false + }, + Hephasto: { + ClearRiver: false, + ClearType: false + }, + Diablo: { + WalkClear: false, + Entrance: false, + JustViz: false, + SealLeader: false, + Fast: false, + SealWarning: "Leave the seals alone!", + EntranceTP: "Entrance TP up", + StarTP: "Star TP up", + DiabloMsg: "Diablo", + ClearRadius: 30, + SealOrder: ["vizier", "seis", "infector"] + }, + DiabloHelper: { + Wait: 120, + Entrance: false, + SkipIfBaal: false, + SkipTP: false, + OpenSeals: false, + SafePrecast: true, + ClearRadius: 30, + SealOrder: ["vizier", "seis", "infector"], + RecheckSeals: false + }, + MFHelper: { + BreakClearLevel: false + }, + Wakka: { + Wait: 1, + StopAtLevel: 99, + StopProfile: false, + SkipIfBaal: true, + }, + BattleOrders: { + Mode: 0, + Getters: [], + Idle: false, + QuitOnFailure: false, + SkipIfTardy: true, + Wait: 10 + }, + BoBarbHelper: { + Mode: -1, + Wp: 35 + }, + Idle: { + Advertise: false, + AdvertiseMessage: "", + MaxGameLength: 0, + }, + ControlBot: { + Bo: false, + DropGold: false, + Cows: { + MakeCows: false, + GetLeg: false, + }, + Chant: { + Enchant: false, + AutoEnchant: false, + }, + Wps: { + GiveWps: false, + SecurePortal: false, + }, + Rush: { + Bloodraven: false, + Smith: false, + Andy: false, + Cube: false, + Radament: false, + Amulet: false, + Staff: false, + Summoner: false, + Duriel: false, + Gidbinn: false, + LamEsen: false, + Eye: false, + Heart: false, + Brain: false, + Travincal: false, + Mephisto: false, + Izual: false, + Diablo: false, + Shenk: false, + Anya: false, + Ancients: false, + Baal: false, + }, + EndMessage: "", + GameLength: 20 + }, + IPHunter: { + IPList: [], + GameLength: 3 + }, + Follower: { + Leader: "" + }, + Mephisto: { + MoatTrick: false, + KillCouncil: false, + TakeRedPortal: false + }, + ShopBot: { + ScanIDs: [], + ShopNPC: "anya", + CycleDelay: 0, + QuitOnMatch: false + }, + Coldworm: { + KillBeetleburst: false, + ClearMaggotLair: false + }, + Summoner: { + FireEye: false + }, + AncientTunnels: { + OpenChest: false, + KillDarkElder: false + }, + OrgTorch: { + WaitForKeys: false, + WaitTimeout: 0, + UseSalvation: false, + GetFade: false, + MakeTorch: true, + PreGame: { + Thawing: { Drink: 0, At: [] }, + Antidote: { Drink: 0, At: [] }, + } + }, + Synch: { + WaitFor: [] + }, + TristramLeech: { + Leader: "", + Helper: false, + Wait: 5 + }, + TombLeech: { + Leader: "", + Helper: false, + Wait: 5 + }, + TravincalLeech: { + Leader: "", + Helper: false, + Wait: 5 + }, + Tristram: { + PortalLeech: false, + WalkClear: false + }, + Travincal: { + PortalLeech: false + }, + SkillStat: { + Skills: [] + }, + Bonesaw: { + ClearDrifterCavern: false + }, + ChestMania: { + Act1: [], + Act2: [], + Act3: [], + Act4: [], + Act5: [] + }, + ClearAnyArea: { + AreaList: [] + }, + Rusher: { + WaitPlayerCount: 0, + Cain: false, + Radament: false, + LamEsen: false, + Izual: false, + Shenk: false, + Anya: false, + HellAncients: false, + GiveWps: false, + LastRun: "" + }, + Rushee: { + Quester: false, + Bumper: false + }, + Questing: { + StopProfile: false + }, + GetEssences: { + MoatMeph: false, + FastDiablo: false, + RunDuriel: false, + }, + GemHunter: { + AreaList: [], + GemList: [] + }, + AutoSkill: { + Enabled: false, + Build: [], + Save: 0 + }, + AutoStat: { + Enabled: false, + Build: [], + Save: 0, + BlockChance: 0, + UseBulk: true + }, + AutoBuild: { + Enabled: false, + Template: "", + Verbose: false, + DebugMode: false + } +}; diff --git a/d2bs/kolbot/libs/core/Cubing.js b/d2bs/kolbot/libs/core/Cubing.js new file mode 100644 index 000000000..8acef6f11 --- /dev/null +++ b/d2bs/kolbot/libs/core/Cubing.js @@ -0,0 +1,1584 @@ +/** +* @filename Cubing.js +* @author kolton, theBGuy +* @desc transmute Horadric Cube recipes +* +*/ + +const Roll = { + All: 0, + Eth: 1, + NonEth: 2 +}; + +/** + * @todo Fix/refactor this, these numbers are all arbitrary anyway + */ +const Recipe = { + Gem: 0, + HitPower: { + Helm: 1, + Boots: 2, + Gloves: 3, + Belt: 4, + Shield: 5, + Body: 6, + Amulet: 7, + Ring: 8, + Weapon: 9 + }, + Blood: { + Helm: 10, + Boots: 11, + Gloves: 12, + Belt: 13, + Shield: 14, + Body: 15, + Amulet: 16, + Ring: 17, + Weapon: 18 + }, + Caster: { + Helm: 19, + Boots: 20, + Gloves: 21, + Belt: 22, + Shield: 23, + Body: 24, + Amulet: 25, + Ring: 26, + Weapon: 27 + }, + Safety: { + Helm: 28, + Boots: 29, + Gloves: 30, + Belt: 31, + Shield: 32, + Body: 33, + Amulet: 34, + Ring: 35, + Weapon: 36 + }, + Unique: { + Weapon: { + ToExceptional: 37, + ToElite: 38 + }, + Armor: { + ToExceptional: 39, + ToElite: 40 + } + }, + Rare: { + Weapon: { + ToExceptional: 41, + ToElite: 42 + }, + Armor: { + ToExceptional: 43, + ToElite: 44 + } + }, + Socket: { + Shield: 45, + Weapon: 46, + Armor: 47, + Helm: 48, + Magic: { + LowWeapon: 59, + HighWeapon: 60, + }, + Rare: 61, + }, + Reroll: { + Magic: 49, + Rare: 50, + HighRare: 51, + Charm: { + Small: 56, + Large: 57, + Grand: 58, + LowGrand: 64 + }, + }, + Rune: 52, + Token: 53, + LowToNorm: { + Armor: 54, + Weapon: 55 + }, + Rejuv: 62, + FullRejuv: 63, +}; + +/** + * @memberof Recipe + * @function ingredients + * @returns {number[]} + */ +Object.defineProperty(Recipe, "ingredients", { + /** + * Get list of ingredients needed for certain recipe + * @param {number} index - Index of recipe to check + * @param {number} [keyItem] - Key item in cubing recipe + * @returns {number[]} + */ + value: function (index, keyItem) { + switch (index) { + case Recipe.Gem: + return [keyItem - 1, keyItem - 1, keyItem - 1]; + // Crafting Recipes---------------------------------------------------------------------// + case Recipe.HitPower.Helm: + return [keyItem, sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Boots: + return [keyItem, sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Gloves: + return [keyItem, sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Belt: + return [keyItem, sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Shield: + return [keyItem, sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Body: + return [keyItem, sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Amulet: + return [sdk.items.Amulet, sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Ring: + return [sdk.items.Ring, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.HitPower.Weapon: + return [keyItem, sdk.items.runes.Tir, sdk.items.Jewel, sdk.items.gems.Perfect.Sapphire]; + case Recipe.Blood.Helm: + return [keyItem, sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Boots: + return [keyItem, sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Gloves: + return [keyItem, sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Belt: + return [keyItem, sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Shield: + return [keyItem, sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Body: + return [keyItem, sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Amulet: + return [sdk.items.Amulet, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Ring: + return [sdk.items.Ring, sdk.items.runes.Sol, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Blood.Weapon: + return [keyItem, sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Ruby]; + case Recipe.Caster.Helm: + return [keyItem, sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Boots: + return [keyItem, sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Gloves: + return [keyItem, sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Belt: + return [keyItem, sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Shield: + return [keyItem, sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Body: + return [keyItem, sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Amulet: + return [sdk.items.Amulet, sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Ring: + return [sdk.items.Ring, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Caster.Weapon: + return [keyItem, sdk.items.runes.Tir, sdk.items.Jewel, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Safety.Helm: + return [keyItem, sdk.items.runes.Ith, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Boots: + return [keyItem, sdk.items.runes.Ort, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Gloves: + return [keyItem, sdk.items.runes.Ral, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Belt: + return [keyItem, sdk.items.runes.Tal, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Shield: + return [keyItem, sdk.items.runes.Nef, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Body: + return [keyItem, sdk.items.runes.Eth, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Amulet: + return [sdk.items.Amulet, sdk.items.runes.Thul, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Ring: + return [sdk.items.Ring, sdk.items.runes.Amn, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + case Recipe.Safety.Weapon: + return [keyItem, sdk.items.runes.Sol, sdk.items.Jewel, sdk.items.gems.Perfect.Emerald]; + // Upgrading Recipes-----------------------------------------------------------------------------// + case Recipe.Unique.Weapon.ToExceptional: + return [keyItem, sdk.items.runes.Ral, sdk.items.runes.Sol, sdk.items.gems.Perfect.Emerald]; + case Recipe.Unique.Weapon.ToElite: // Ladder only + return [keyItem, sdk.items.runes.Lum, sdk.items.runes.Pul, sdk.items.gems.Perfect.Emerald]; + case Recipe.Unique.Armor.ToExceptional: + return [keyItem, sdk.items.runes.Tal, sdk.items.runes.Shael, sdk.items.gems.Perfect.Diamond]; + case Recipe.Unique.Armor.ToElite: // Ladder only + return [keyItem, sdk.items.runes.Lem, sdk.items.runes.Ko, sdk.items.gems.Perfect.Diamond]; + case Recipe.Rare.Weapon.ToExceptional: + return [keyItem, sdk.items.runes.Ort, sdk.items.runes.Amn, sdk.items.gems.Perfect.Sapphire]; + case Recipe.Rare.Weapon.ToElite: + return [keyItem, sdk.items.runes.Fal, sdk.items.runes.Um, sdk.items.gems.Perfect.Sapphire]; + case Recipe.Rare.Armor.ToExceptional: + return [keyItem, sdk.items.runes.Ral, sdk.items.runes.Thul, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Rare.Armor.ToElite: + return [keyItem, sdk.items.runes.Ko, sdk.items.runes.Pul, sdk.items.gems.Perfect.Amethyst]; + // Socketing Recipes-------------------------------------------------------------------------------// + case Recipe.Socket.Shield: + return [keyItem, sdk.items.runes.Tal, sdk.items.runes.Amn, sdk.items.gems.Perfect.Ruby]; + case Recipe.Socket.Weapon: + return [keyItem, sdk.items.runes.Ral, sdk.items.runes.Amn, sdk.items.gems.Perfect.Amethyst]; + case Recipe.Socket.Armor: + return [keyItem, sdk.items.runes.Tal, sdk.items.runes.Thul, sdk.items.gems.Perfect.Topaz]; + case Recipe.Socket.Helm: + return [keyItem, sdk.items.runes.Ral, sdk.items.runes.Thul, sdk.items.gems.Perfect.Sapphire]; + case Recipe.Socket.Magic.LowWeapon: + return [keyItem, "cgem", "cgem", "cgem"]; + case Recipe.Socket.Magic.HighWeapon: + return [keyItem, "fgem", "fgem", "fgem"]; + case Recipe.Socket.Rare: + return [ + keyItem, sdk.items.Ring, sdk.items.gems.Perfect.Skull, + sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull + ]; + // Re-rolling Recipes-------------------------------------------------------------------------------// + case Recipe.Reroll.Charm.Small: + return [sdk.items.SmallCharm, "pgem", "pgem", "pgem"]; + case Recipe.Reroll.Charm.Large: + return [sdk.items.LargeCharm, "pgem", "pgem", "pgem"]; + case Recipe.Reroll.Charm.LowGrand: + case Recipe.Reroll.Charm.Grand: + return [sdk.items.GrandCharm, "pgem", "pgem", "pgem"]; + case Recipe.Reroll.Magic: // Hacky solution ftw + return [keyItem, "pgem", "pgem", "pgem"]; + case Recipe.Reroll.Rare: + return [ + keyItem, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, + sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, + sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull + ]; + case Recipe.Reroll.HighRare: + return [keyItem, sdk.items.gems.Perfect.Skull, sdk.items.Ring]; + case Recipe.LowToNorm.Weapon: + return [keyItem, sdk.items.runes.Eld, "cgem"]; + case Recipe.LowToNorm.Armor: + return [keyItem, sdk.items.runes.El, "cgem"]; + // Rune Recipes--------------------------------------------------------------------------------------// + case Recipe.Rune: + switch (keyItem) { + case sdk.items.runes.Eld: + case sdk.items.runes.Tir: + case sdk.items.runes.Nef: + case sdk.items.runes.Eth: + case sdk.items.runes.Ith: + case sdk.items.runes.Tal: + case sdk.items.runes.Ral: + case sdk.items.runes.Ort: + return [keyItem - 1, keyItem - 1, keyItem - 1]; + case sdk.items.runes.Amn: // thul->amn + return [sdk.items.runes.Thul, sdk.items.runes.Thul, sdk.items.runes.Thul, sdk.items.gems.Chipped.Topaz]; + case sdk.items.runes.Sol: // amn->sol + return [sdk.items.runes.Amn, sdk.items.runes.Amn, sdk.items.runes.Amn, sdk.items.gems.Chipped.Amethyst]; + case sdk.items.runes.Shael: // sol->shael + return [sdk.items.runes.Sol, sdk.items.runes.Sol, sdk.items.runes.Sol, sdk.items.gems.Chipped.Sapphire]; + case sdk.items.runes.Dol: // shael->dol + return [sdk.items.runes.Shael, sdk.items.runes.Shael, sdk.items.runes.Shael, sdk.items.gems.Chipped.Ruby]; + case sdk.items.runes.Hel: // dol->hel + return [sdk.items.runes.Dol, sdk.items.runes.Dol, sdk.items.runes.Dol, sdk.items.gems.Chipped.Emerald]; + case sdk.items.runes.Io: // hel->io + return [sdk.items.runes.Hel, sdk.items.runes.Hel, sdk.items.runes.Hel, sdk.items.gems.Chipped.Diamond]; + case sdk.items.runes.Lum: // io->lum + return [sdk.items.runes.Io, sdk.items.runes.Io, sdk.items.runes.Io, sdk.items.gems.Flawed.Topaz]; + case sdk.items.runes.Ko: // lum->ko + return [sdk.items.runes.Lum, sdk.items.runes.Lum, sdk.items.runes.Lum, sdk.items.gems.Flawed.Amethyst]; + case sdk.items.runes.Fal: // ko->fal + return [sdk.items.runes.Ko, sdk.items.runes.Ko, sdk.items.runes.Ko, sdk.items.gems.Flawed.Sapphire]; + case sdk.items.runes.Lem: // fal->lem + return [sdk.items.runes.Fal, sdk.items.runes.Fal, sdk.items.runes.Fal, sdk.items.gems.Flawed.Ruby]; + case sdk.items.runes.Pul: // lem->pul + return [sdk.items.runes.Lem, sdk.items.runes.Lem, sdk.items.runes.Lem, sdk.items.gems.Flawed.Emerald]; + case sdk.items.runes.Um: // pul->um + return [sdk.items.runes.Pul, sdk.items.runes.Pul, sdk.items.gems.Flawed.Diamond]; + case sdk.items.runes.Mal: // um->mal + return [sdk.items.runes.Um, sdk.items.runes.Um, sdk.items.gems.Normal.Topaz]; + case sdk.items.runes.Ist: // mal->ist + return [sdk.items.runes.Mal, sdk.items.runes.Mal, sdk.items.gems.Normal.Amethyst]; + case sdk.items.runes.Gul: // ist->gul + return [sdk.items.runes.Ist, sdk.items.runes.Ist, sdk.items.gems.Normal.Sapphire]; + case sdk.items.runes.Vex: // gul->vex + return [sdk.items.runes.Gul, sdk.items.runes.Gul, sdk.items.gems.Normal.Ruby]; + case sdk.items.runes.Ohm: // vex->ohm + return [sdk.items.runes.Vex, sdk.items.runes.Vex, sdk.items.gems.Normal.Emerald]; + case sdk.items.runes.Lo: // ohm->lo + return [sdk.items.runes.Ohm, sdk.items.runes.Ohm, sdk.items.gems.Normal.Diamond]; + case sdk.items.runes.Sur: // lo->sur + return [sdk.items.runes.Lo, sdk.items.runes.Lo, sdk.items.gems.Flawless.Topaz]; + case sdk.items.runes.Ber: // sur->ber + return [sdk.items.runes.Sur, sdk.items.runes.Sur, sdk.items.gems.Flawless.Amethyst]; + case sdk.items.runes.Jah: // ber->jah + return [sdk.items.runes.Ber, sdk.items.runes.Ber, sdk.items.gems.Flawless.Sapphire]; + case sdk.items.runes.Cham: // jah->cham + return [sdk.items.runes.Jah, sdk.items.runes.Jah, sdk.items.gems.Flawless.Ruby]; + case sdk.items.runes.Zod: // cham->zod + return [sdk.items.runes.Cham, sdk.items.runes.Cham, sdk.items.gems.Flawless.Emerald]; + } + + break; + case Recipe.Token: + return [ + sdk.quest.item.TwistedEssenceofSuffering, sdk.quest.item.ChargedEssenceofHatred, + sdk.quest.item.BurningEssenceofTerror, sdk.quest.item.FesteringEssenceofDestruction + ]; + case Recipe.Rejuv: + return ["cgem", "hpot", "hpot", "hpot", "mpot", "mpot", "mpot"]; + case Recipe.FullRejuv: + return ["gem", "hpot", "hpot", "hpot", "mpot", "mpot", "mpot"]; + } + return []; + }, + enumerable: false, +}); + +const Cubing = { + /** @type {recipeObj[]} */ + recipes: [], + gemList: [], + gems: (() => ({ + chipped: Object.values(sdk.items.gems.Chipped), + flawed: Object.values(sdk.items.gems.Flawed), + normal: Object.values(sdk.items.gems.Normal), + flawless: Object.values(sdk.items.gems.Flawless), + perfect: Object.values(sdk.items.gems.Perfect), + }))(), + pots: { + healing: [ + sdk.items.MinorHealingPotion, sdk.items.LightHealingPotion, + sdk.items.HealingPotion, sdk.items.GreaterHealingPotion + ], + mana: [ + sdk.items.MinorManaPotion, sdk.items.LightManaPotion, + sdk.items.ManaPotion, sdk.items.GreaterManaPotion + ], + }, + + init: function () { + if (!Config.Cubing) return; + // console.log("We have " + Config.Recipes.length + " cubing recipe(s)."); + + /** @type {Set} */ + const uniqueRecipes = new Set(); + + for (let i = 0; i < Config.Recipes.length; i += 1) { + if (Config.Recipes[i].length > 1 && isNaN(Config.Recipes[i][1])) { + const formattedName = Config.Recipes[i][1].replace(/\s+/g, "").toLowerCase(); + if (NTIPAliasClassID.hasOwnProperty(formattedName)) { + Config.Recipes[i][1] = NTIPAliasClassID[formattedName]; + } else { + Misc.errorReport("ÿc1Invalid cubing entry:ÿc0 " + Config.Recipes[i][1]); + Config.Recipes.splice(i, 1); + + i -= 1; + } + } + + let stringifiedRecipe = JSON.stringify(Config.Recipes[i]); + if (uniqueRecipes.has(stringifiedRecipe)) { + Config.Recipes.splice(i, 1); + i -= 1; + } else { + uniqueRecipes.add(stringifiedRecipe); + } + } + + this.buildRecipes(); + this.buildGemList(); + this.buildLists(); + }, + + buildGemList: function () { + let gemList = Cubing.gems.perfect.slice(); + + for (let i = 0; i < this.recipes.length; i += 1) { + // Skip gems and other magic rerolling recipes + if ([Recipe.Gem, Recipe.Reroll.Magic].indexOf(this.recipes[i].Index) === -1) { + for (let j = 0; j < this.recipes[i].Ingredients.length; j += 1) { + if (gemList.includes(this.recipes[i].Ingredients[j])) { + gemList.splice(gemList.indexOf(this.recipes[i].Ingredients[j]), 1); + } + } + } + } + + Cubing.gemList = gemList.slice(0); + + return true; + }, + + /** + * @typedef recipeObj + * @property {number[] | string[]} Ingredients + * @property {number} Index + * @property {number} [Level] + * @property {number} [Ethereal] + * @property {boolean} [Enabled] + * @property {boolean} [AlwaysEnabled] + * @property {number} [MainRecipe] + * + * + * @todo + * - Allow passing in ilvl + */ + buildRecipes: function () { + Cubing.recipes = []; + + for (let i = 0; i < Config.Recipes.length; i += 1) { + if (typeof Config.Recipes[i] !== "object" + || (Config.Recipes[i].length > 2 && typeof Config.Recipes[i][2] !== "number") + || Config.Recipes[i].length < 1) { + throw new Error("Cubing.buildRecipes: Invalid recipe format."); + } + + /** @type {number[]} */ + let [index, keyItem] = Config.Recipes[i]; + const ingredients = Recipe.ingredients(index, keyItem); + + switch (index) { + case Recipe.Gem: + this.recipes.push({ Ingredients: ingredients, Index: index, AlwaysEnabled: true }); + + break; + // Crafting Recipes--------------------------------------------------------------// + case Recipe.HitPower.Helm: + this.recipes.push({ Ingredients: ingredients, Level: 84, Index: index }); + + break; + case Recipe.HitPower.Boots: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.HitPower.Gloves: + this.recipes.push({ Ingredients: ingredients, Level: 79, Index: index }); + + break; + case Recipe.HitPower.Belt: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.HitPower.Shield: + this.recipes.push({ Ingredients: ingredients, Level: 82, Index: index }); + + break; + case Recipe.HitPower.Body: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + case Recipe.HitPower.Amulet: + this.recipes.push({ Ingredients: ingredients, Level: 90, Index: index }); + + break; + case Recipe.HitPower.Ring: + this.recipes.push({ Ingredients: ingredients, Level: 77, Index: index }); + + break; + case Recipe.HitPower.Weapon: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + case Recipe.Blood.Helm: + this.recipes.push({ Ingredients: ingredients, Level: 84, Index: index }); + + break; + case Recipe.Blood.Boots: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.Blood.Gloves: + this.recipes.push({ Ingredients: ingredients, Level: 79, Index: index }); + + break; + case Recipe.Blood.Belt: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.Blood.Shield: + this.recipes.push({ Ingredients: ingredients, Level: 82, Index: index }); + + break; + case Recipe.Blood.Body: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + case Recipe.Blood.Amulet: + this.recipes.push({ Ingredients: ingredients, Level: 90, Index: index }); + + break; + case Recipe.Blood.Ring: + this.recipes.push({ Ingredients: ingredients, Level: 77, Index: index }); + + break; + case Recipe.Blood.Weapon: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + case Recipe.Caster.Helm: + this.recipes.push({ Ingredients: ingredients, Level: 84, Index: index }); + + break; + case Recipe.Caster.Boots: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.Caster.Gloves: + this.recipes.push({ Ingredients: ingredients, Level: 79, Index: index }); + + break; + case Recipe.Caster.Belt: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.Caster.Shield: + this.recipes.push({ Ingredients: ingredients, Level: 82, Index: index }); + + break; + case Recipe.Caster.Body: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + case Recipe.Caster.Amulet: + this.recipes.push({ Ingredients: ingredients, Level: 90, Index: index }); + + break; + case Recipe.Caster.Ring: + this.recipes.push({ Ingredients: ingredients, Level: 77, Index: index }); + + break; + case Recipe.Caster.Weapon: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + case Recipe.Safety.Helm: + this.recipes.push({ Ingredients: ingredients, Level: 84, Index: index }); + + break; + case Recipe.Safety.Boots: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.Safety.Gloves: + this.recipes.push({ Ingredients: ingredients, Level: 79, Index: index }); + + break; + case Recipe.Safety.Belt: + this.recipes.push({ Ingredients: ingredients, Level: 71, Index: index }); + + break; + case Recipe.Safety.Shield: + this.recipes.push({ Ingredients: ingredients, Level: 82, Index: index }); + + break; + case Recipe.Safety.Body: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + case Recipe.Safety.Amulet: + this.recipes.push({ Ingredients: ingredients, Level: 90, Index: index }); + + break; + case Recipe.Safety.Ring: + this.recipes.push({ Ingredients: ingredients, Level: 77, Index: index }); + + break; + case Recipe.Safety.Weapon: + this.recipes.push({ Ingredients: ingredients, Level: 85, Index: index }); + + break; + // Upgrading Recipes------------------------------------------------------------------------// + case Recipe.Unique.Weapon.ToExceptional: + this.recipes.push({ Ingredients: ingredients, Index: index, Ethereal: Config.Recipes[i][2] }); + + break; + case Recipe.Unique.Weapon.ToElite: // Ladder only + if (me.ladder) { + this.recipes.push({ Ingredients: ingredients, Index: index, Ethereal: Config.Recipes[i][2] }); + } + + break; + case Recipe.Unique.Armor.ToExceptional: + this.recipes.push({ Ingredients: ingredients, Index: index, Ethereal: Config.Recipes[i][2] }); + + break; + case Recipe.Unique.Armor.ToElite: // Ladder only + if (me.ladder) { + this.recipes.push({ Ingredients: ingredients, Index: index, Ethereal: Config.Recipes[i][2] }); + } + + break; + case Recipe.Rare.Weapon.ToExceptional: + case Recipe.Rare.Weapon.ToElite: + case Recipe.Rare.Armor.ToExceptional: + case Recipe.Rare.Armor.ToElite: + case Recipe.Socket.Shield: + case Recipe.Socket.Weapon: + case Recipe.Socket.Armor: + case Recipe.Socket.Helm: + case Recipe.Socket.Rare: + this.recipes.push({ Ingredients: ingredients, Index: index, Ethereal: Config.Recipes[i][2] }); + + break; + case Recipe.Socket.Magic.LowWeapon: + // ilvl < 30 + this.recipes.push({ Ingredients: ingredients, Level: 30, Index: index, Ethereal: Config.Recipes[i][2] }); + + break; + case Recipe.Socket.Magic.HighWeapon: + // ilvl >= 30 + this.recipes.push({ Ingredients: ingredients, Level: 30, Index: index, Ethereal: Config.Recipes[i][2] }); + + break; + case Recipe.Reroll.Charm.Small: + case Recipe.Reroll.Charm.Large: + case Recipe.Reroll.Charm.LowGrand: + case Recipe.Reroll.Charm.Grand: + case Recipe.Reroll.Magic: // Hacky solution ftw + /** + * Charm ilvls based on https://diablo2.diablowiki.net/Guide:Charms_v1.10,_by_Kronos + */ + if (index === Recipe.Reroll.Charm.Small) { + this.recipes.push({ Ingredients: ingredients, Level: 94, Index: index }); + } else if (index === Recipe.Reroll.Charm.Large) { + this.recipes.push({ Ingredients: ingredients, Level: 76, Index: index }); + } else if (index === Recipe.Reroll.Charm.LowGrand) { + this.recipes.push({ Ingredients: ingredients, Level: 50, Index: index }); + } else if (index === Recipe.Reroll.Charm.Grand) { + this.recipes.push({ Ingredients: ingredients, Level: 77, Index: index }); + } else { + this.recipes.push({ Ingredients: ingredients, Level: 91, Index: index }); + } + + break; + case Recipe.Reroll.Rare: + this.recipes.push({ Ingredients: ingredients, Index: index }); + + break; + case Recipe.Reroll.HighRare: + this.recipes.push({ Ingredients: ingredients, Index: index, Enabled: false }); + + break; + case Recipe.LowToNorm.Weapon: + case Recipe.LowToNorm.Armor: + this.recipes.push({ Ingredients: ingredients, Index: index }); + + break; + case Recipe.Rune: + switch (Config.Recipes[i][1]) { + case sdk.items.runes.Eld: + case sdk.items.runes.Tir: + case sdk.items.runes.Nef: + case sdk.items.runes.Eth: + case sdk.items.runes.Ith: + case sdk.items.runes.Tal: + case sdk.items.runes.Ral: + case sdk.items.runes.Ort: + this.recipes.push({ Ingredients: ingredients, Index: index, AlwaysEnabled: true }); + + break; + case sdk.items.runes.Thul: + case sdk.items.runes.Amn: + case sdk.items.runes.Sol: + case sdk.items.runes.Shael: + case sdk.items.runes.Dol: + this.recipes.push({ Ingredients: ingredients, Index: index }); + + break; + case sdk.items.runes.Hel: + case sdk.items.runes.Io: + case sdk.items.runes.Lum: + case sdk.items.runes.Ko: + case sdk.items.runes.Fal: + case sdk.items.runes.Lem: + case sdk.items.runes.Pul: + case sdk.items.runes.Um: + case sdk.items.runes.Mal: + case sdk.items.runes.Ist: + case sdk.items.runes.Gul: + case sdk.items.runes.Vex: + case sdk.items.runes.Ohm: + case sdk.items.runes.Lo: + case sdk.items.runes.Sur: + case sdk.items.runes.Ber: + case sdk.items.runes.Jah: + case sdk.items.runes.Cham: + case sdk.items.runes.Zod: + if (me.ladder || !me.realm) { + this.recipes.push({ Ingredients: ingredients, Index: index }); + } + + break; + } + + break; + case Recipe.Token: + this.recipes.push({ Ingredients: ingredients, Index: index, AlwaysEnabled: true }); + + break; + case Recipe.Rejuv: + case Recipe.FullRejuv: + this.recipes.push({ Ingredients: ingredients, Index: index }); + + break; + } + } + }, + + /** @type {ItemUnit[]} */ + validIngredients: [], // What we have + neededIngredients: [], // What we need + subRecipes: [], + + buildLists: function () { + CraftingSystem.checkSubrecipes(); + + Cubing.validIngredients = []; + Cubing.neededIngredients = []; + let items = me.findItems(-1, sdk.items.mode.inStorage); + + for (let i = 0; i < this.recipes.length; i += 1) { + // Set default Enabled property - true if recipe is always enabled, false otherwise + this.recipes[i].Enabled = this.recipes[i].hasOwnProperty("AlwaysEnabled"); + + IngredientLoop: + for (let j = 0; j < this.recipes[i].Ingredients.length; j += 1) { + const currIngred = this.recipes[i].Ingredients[j]; + for (let k = 0; k < items.length; k += 1) { + if (((currIngred === "pgem" && this.gemList.includes(items[k].classid)) + || (currIngred === "fgem" && this.gems.flawless.includes(items[k].classid)) + || (currIngred === "gem" && this.gems.normal.includes(items[k].classid)) + || (currIngred === "cgem" && this.gems.chipped.includes(items[k].classid)) + || (currIngred === "hpot" && this.pots.healing.includes(items[k].classid)) + || (currIngred === "mpot" && this.pots.mana.includes(items[k].classid)) + || items[k].classid === currIngred) && this.validItem(items[k], this.recipes[i])) { + + // push the item's info into the valid ingredients array. this will be used to find items when checking recipes + this.validIngredients.push({ classid: items[k].classid, gid: items[k].gid }); + + // Remove from item list to prevent counting the same item more than once + items.splice(k, 1); + k -= 1; + + // Enable recipes for gem/jewel pickup + if (this.recipes[i].Index !== Recipe.Rune + || ([Recipe.Rune, Recipe.Rejuv, Recipe.FullRejuv].includes(this.recipes[i].Index) && j >= 1)) { + // Enable rune recipe after 2 bases are found + this.recipes[i].Enabled = true; + } + + continue IngredientLoop; + } + } + + // add the item to needed list - enable pickup + this.neededIngredients.push({ classid: currIngred, recipe: this.recipes[i] }); + + // skip flawless gems adding if we don't have the main item (Recipe.Gem and Recipe.Rune for el-ort are always enabled) + if (!this.recipes[i].Enabled) { + break; + } + + // if the recipe is enabled (we have the main item), add gem recipes (if needed) - TODO: make this work + // if (!this.recipes[i].hasOwnProperty("MainRecipe")) { + // // make sure we don't add a subrecipe to a subrecipe + // for (let gType of Object.values(Cubing.gems)) { + // // skip over cgems - can't cube them + // if (gType.includes(sdk.items.gems.Chipped.Amethyst)) continue; + // for (let gem of gType) { + // if (this.subRecipes.indexOf(gem) === -1 + // && (this.recipes[i].Ingredients[j] === gem + // || (this.recipes[i].Ingredients[j] === "pgem" && Cubing.gemList.includes(gem)))) { + // this.recipes.push({ + // Ingredients: [gem - 1, gem - 1, gem - 1], + // Index: Recipe.Gem, + // AlwaysEnabled: true, + // MainRecipe: this.recipes[i].Index + // }); + // this.subRecipes.push(gem); + // } + // } + // } + // } + + // If the recipe is enabled (we have the main item), add flawless gem recipes (if needed) - old method + /** + * @param {number} gemId + * @param {number} mainRecipe + * @returns {recipeObj} + */ + const gemRecipe = function (gemId, mainRecipe) { + return { + Ingredients: [gemId, gemId, gemId], + Index: Recipe.Gem, + AlwaysEnabled: true, + MainRecipe: mainRecipe + }; + }; + // Make perf amethyst + if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Amethyst) === -1 + && ( + currIngred === sdk.items.gems.Perfect.Amethyst + || (currIngred === "pgem" && this.gemList.includes(sdk.items.gems.Perfect.Amethyst)) + )) { + this.recipes.push(gemRecipe(sdk.items.gems.Flawless.Amethyst, this.recipes[i].Index)); + this.subRecipes.push(sdk.items.gems.Perfect.Amethyst); + } + + // Make perf topaz + if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Topaz) === -1 + && ( + currIngred === sdk.items.gems.Perfect.Topaz + || (currIngred === "pgem" && this.gemList.includes(sdk.items.gems.Perfect.Topaz)) + )) { + this.recipes.push(gemRecipe(sdk.items.gems.Flawless.Topaz, this.recipes[i].Index)); + this.subRecipes.push(sdk.items.gems.Perfect.Topaz); + } + + // Make perf sapphire + if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Sapphire) === -1 + && ( + currIngred === sdk.items.gems.Perfect.Sapphire + || (currIngred === "pgem" && this.gemList.includes(sdk.items.gems.Perfect.Sapphire)) + )) { + this.recipes.push(gemRecipe(sdk.items.gems.Flawless.Sapphire, this.recipes[i].Index)); + this.subRecipes.push(sdk.items.gems.Perfect.Sapphire); + } + + // Make perf emerald + if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Emerald) === -1 + && ( + currIngred === sdk.items.gems.Perfect.Emerald + || (currIngred === "pgem" && this.gemList.includes(sdk.items.gems.Perfect.Emerald)) + )) { + this.recipes.push(gemRecipe(sdk.items.gems.Flawless.Emerald, this.recipes[i].Index)); + this.subRecipes.push(sdk.items.gems.Perfect.Emerald); + } + + // Make perf ruby + if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Ruby) === -1 + && ( + currIngred === sdk.items.gems.Perfect.Ruby + || (currIngred === "pgem" && this.gemList.includes(sdk.items.gems.Perfect.Ruby)) + )) { + this.recipes.push(gemRecipe(sdk.items.gems.Flawless.Ruby, this.recipes[i].Index)); + this.subRecipes.push(sdk.items.gems.Perfect.Ruby); + } + + // Make perf diamond + if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Diamond) === -1 + && ( + currIngred === sdk.items.gems.Perfect.Diamond + || (currIngred === "pgem" && this.gemList.includes(sdk.items.gems.Perfect.Diamond)) + )) { + this.recipes.push(gemRecipe(sdk.items.gems.Flawless.Diamond, this.recipes[i].Index)); + this.subRecipes.push(sdk.items.gems.Perfect.Diamond); + } + + // Make perf skull + if (this.subRecipes.indexOf(sdk.items.gems.Perfect.Skull) === -1 + && ( + currIngred === sdk.items.gems.Perfect.Skull + || (currIngred === "pgem" && this.gemList.includes(sdk.items.gems.Perfect.Skull)) + )) { + this.recipes.push(gemRecipe(sdk.items.gems.Flawless.Skull, this.recipes[i].Index)); + this.subRecipes.push(sdk.items.gems.Perfect.Skull); + } + } + } + }, + + // Remove unneeded flawless gem recipes + clearSubRecipes: function () { + Cubing.subRecipes = []; + + for (let i = 0; i < this.recipes.length; i += 1) { + if (this.recipes[i].hasOwnProperty("MainRecipe")) { + this.recipes.splice(i, 1); + + i -= 1; + } + } + }, + + update: function () { + this.clearSubRecipes(); + this.buildLists(); + }, + + /** + * @param {recipeObj} recipe + * @returns {boolean} + */ + checkRecipe: function (recipe) { + let usedGids = []; + let matchList = []; + + for (let i = 0; i < recipe.Ingredients.length; i += 1) { + for (let ingredient of Cubing.validIngredients) { + if (usedGids.indexOf(ingredient.gid) === -1 && ( + ingredient.classid === recipe.Ingredients[i] + || (recipe.Ingredients[i] === "pgem" && Cubing.gemList.includes(ingredient.classid)) + || (recipe.Ingredients[i] === "fgem" && Cubing.gems.flawless.includes(ingredient.classid)) + || (recipe.Ingredients[i] === "gem" && Cubing.gems.normal.includes(ingredient.classid)) + || (recipe.Ingredients[i] === "cgem" && Cubing.gems.chipped.includes(ingredient.classid)) + || (recipe.Ingredients[i] === "hpot" && Cubing.pots.healing.includes(ingredient.classid)) + || (recipe.Ingredients[i] === "mpot" && Cubing.pots.mana.includes(ingredient.classid)) + )) { + let item = me.getItem(ingredient.classid, -1, ingredient.gid); + + // 26.11.2012. check if the item actually belongs to the given recipe + if (item && Cubing.validItem(item, recipe)) { + // don't repeat the same item + usedGids.push(ingredient.gid); + // push the item into the match list + matchList.push(copyUnit(item)); + + break; + } + } + } + + // no new items in the match list = not enough ingredients + if (matchList.length !== i + 1) return false; + } + + // return the match list. these items go to cube + return matchList; + }, + + /** + * debug function - get what each recipe needs + * @param {number} index + * @returns {string} + */ + getRecipeNeeds: function (index) { + let rval = " ["; + + for (let i = 0; i < this.neededIngredients.length; i += 1) { + if (this.neededIngredients[i].recipe.Index === index) { + rval += this.neededIngredients[i].classid + (i === this.neededIngredients.length - 1 ? "" : " "); + } + } + + rval += "]"; + + return rval; + }, + + /** + * Check an item on ground for pickup + * @param {ItemUnit} unit + * @returns {boolean} + */ + checkItem: function (unit) { + if (!Config.Cubing) return false; + if (this.keepItem(unit)) return true; + + for (let i = 0; i < this.neededIngredients.length; i += 1) { + if (unit.classid === this.neededIngredients[i].classid + && this.validItem(unit, this.neededIngredients[i].recipe)) { + //debugLog("Cubing: " + unit.name + " " + this.neededIngredients[i].recipe.Index + " " + (this.neededIngredients[i].recipe.hasOwnProperty("MainRecipe") ? this.neededIngredients[i].recipe.MainRecipe : "") + this.getRecipeNeeds(this.neededIngredients[i].recipe.Index)); + return true; + } + } + + return false; + }, + + /** + * Don't drop an item from inventory if it's a part of cubing recipe + * @param {ItemUnit} unit + * @returns {boolean} + */ + keepItem: function (unit) { + if (!Config.Cubing) return false; + + for (let i = 0; i < this.validIngredients.length; i += 1) { + if (unit.mode === sdk.items.mode.inStorage && unit.gid === this.validIngredients[i].gid) { + return true; + } + } + + return false; + }, + + /** + * Check if this item is valid for a given recipe + * @param {ItemUnit} unit + * @param {recipeObj} recipe + * @returns {boolean} + */ + validItem: function (unit, recipe) { + // Excluded items + // Don't use items in locked inventory space - or wanted by other systems + if ((unit.isInInventory && Storage.Inventory.IsLocked(unit, Config.Inventory) + || Runewords.validGids.includes(unit.gid) || CraftingSystem.validGids.includes(unit.gid))) { + return false; + } + + const rIndex = recipe.Index; + + // Pots and Gems - for Rejuv recipes + if ([Recipe.Rejuv, Recipe.FullRejuv].includes(rIndex)) { + /** + * @todo do this better, hacky fix for now + */ + if (!recipe.Enabled) { + if (rIndex === Recipe.Rejuv && this.gems.chipped.includes(unit.classid)) return true; + if (rIndex === Recipe.FullRejuv && this.gems.normal.includes(unit.classid)) return true; + return false; + } + + if (rIndex === Recipe.Rejuv && this.gems.chipped.includes(unit.classid)) return true; + if (rIndex === Recipe.FullRejuv && this.gems.normal.includes(unit.classid)) return true; + if ([].concat(Cubing.pots.healing, Cubing.pots.mana).includes(unit.classid)) { + return true; + } + + return false; + } + + // Gems and runes + if ((unit.itemType >= sdk.items.type.Amethyst + && unit.itemType <= sdk.items.type.Skull) || unit.itemType === sdk.items.type.Rune) { + if (!recipe.Enabled && recipe.Ingredients[0] !== unit.classid && recipe.Ingredients[1] !== unit.classid) { + return false; + } + + return true; + } + + // Token + if (rIndex === Recipe.Token) return true; + + // START + const ntipResult = NTIP.CheckItem(unit); + + if (rIndex >= Recipe.HitPower.Helm && rIndex <= Recipe.Safety.Weapon) { + // Junk jewels (NOT matching a pickit entry) + if (unit.itemType === sdk.items.type.Jewel) { + if (recipe.Enabled && ntipResult === Pickit.Result.UNWANTED) { + return true; + } + // Main item, NOT matching a pickit entry + } else if (unit.magic && Math.floor(me.charlvl / 2) + Math.floor(unit.ilvl / 2) >= recipe.Level + && ntipResult === Pickit.Result.UNWANTED) { + return true; + } + + return false; + } + + let upgradeUnique = rIndex >= Recipe.Unique.Weapon.ToExceptional && rIndex <= Recipe.Unique.Armor.ToElite; + let upgradeRare = rIndex >= Recipe.Rare.Weapon.ToExceptional && rIndex <= Recipe.Rare.Armor.ToElite; + let socketNormal = rIndex >= Recipe.Socket.Shield && rIndex <= Recipe.Socket.Helm; + let socketMagic = [Recipe.Socket.Magic.LowWeapon, Recipe.Socket.Magic.HighWeapon].includes(rIndex); + let socketRare = rIndex === Recipe.Socket.Rare; + + if (socketRare && recipe.Enabled && recipe.Ingredients[2] === unit.classid && unit.itemType === sdk.items.type.Ring + && unit.getStat(sdk.stats.MaxManaPercent) && !Storage.Inventory.IsLocked(unit, Config.Inventory)) { + return true; + } + + if (upgradeUnique || upgradeRare || socketNormal || socketRare) { + switch (true) { + case upgradeUnique && unit.unique && ntipResult === Pickit.Result.WANTED: // Unique item matching pickit entry + case upgradeRare && unit.rare && ntipResult === Pickit.Result.WANTED: // Rare item matching pickit entry + case socketNormal && unit.normal && unit.sockets === 0: // Normal item matching pickit entry, no sockets + case socketMagic && unit.magic && unit.sockets === 0: // Magic item matching pickit entry, no sockets + case socketRare && unit.rare && unit.sockets === 0: // Rare item matching pickit entry, no sockets + if (socketMagic) { + if (rIndex === Recipe.Socket.Magic.LowWeapon && unit.ilvl > recipe.Level) return false; + if (rIndex === Recipe.Socket.Magic.HighWeapon && unit.ilvl < recipe.Level) return false; + } + if (recipe.Ethereal === undefined) return ntipResult === Pickit.Result.WANTED; + switch (recipe.Ethereal) { + case Roll.All: + return ntipResult === Pickit.Result.WANTED; + case Roll.Eth: + return unit.ethereal && ntipResult === Pickit.Result.WANTED; + case Roll.NonEth: + return !unit.ethereal && ntipResult === Pickit.Result.WANTED; + } + + return false; + } + + return false; + } + + if (rIndex === Recipe.Reroll.Magic + || (rIndex >= Recipe.Reroll.Charm.Small && rIndex <= Recipe.Reroll.Charm.Grand)) { + return (unit.magic && unit.ilvl >= recipe.Level && ntipResult === Pickit.Result.UNWANTED); + } + + if (rIndex === Recipe.Reroll.Rare) { + return (unit.rare && ntipResult === Pickit.Result.UNWANTED); + } + + if (rIndex === Recipe.Reroll.HighRare) { + if (recipe.Ingredients[0] === unit.classid && unit.rare && ntipResult === Pickit.Result.UNWANTED) { + recipe.Enabled = true; + + return true; + } + + if (recipe.Enabled && recipe.Ingredients[2] === unit.classid && unit.itemType === sdk.items.type.Ring + && unit.getStat(sdk.stats.MaxManaPercent) && !Storage.Inventory.IsLocked(unit, Config.Inventory)) { + return true; + } + + return false; + } + + if (rIndex === Recipe.LowToNorm.Armor || rIndex === Recipe.LowToNorm.Weapon) { + return (unit.lowquality && ntipResult === Pickit.Result.UNWANTED); + } + + return false; + }, + + doCubing: function () { + if (!Config.Cubing) return false; + if (!me.getItem(sdk.quest.item.Cube)) return false; + + this.update(); + // Randomize the recipe array to prevent recipe blocking (multiple caster items etc.) + let tempArray = this.recipes.slice().shuffle(); + + for (let i = 0; i < tempArray.length; i += 1) { + let string = "Transmuting: "; + let items = this.checkRecipe(tempArray[i]); + if (!Array.isArray(items) || !items.length) continue; + + // If cube isn't open, attempt to open stash (the function returns true if stash is already open) + if ((!getUIFlag(sdk.uiflags.Cube) && !Town.openStash()) || !this.emptyCube()) return false; + + this.cursorCheck(); + + i = -1; + + let itemsToCubeCount = items.length; + + while (items.length) { + string += (items[0].name.trim() + (items.length > 1 ? " + " : "")); + Storage.Cube.MoveTo(items[0]); + items.shift(); + } + + const itemsInCube = me.getItemsEx().filter(function (el) { + return el.isInCube; + }); + if (itemsInCube.length !== itemsToCubeCount) { + console.warn("Failed to move all necesary items to cube"); + itemsInCube.forEach(function (item) { + if (Storage.Inventory.CanFit(item) && Storage.Inventory.MoveTo(item)) return; + if (Storage.Stash.CanFit(item) && Storage.Stash.MoveTo(item)) return; + }); + return false; + } + + if (!this.openCube()) return false; + + transmute(); + delay(700 + me.ping); + console.log("ÿc4Cubing: " + string); + Config.ShowCubingInfo && D2Bot.printToConsole(string, sdk.colors.D2Bot.Green); + this.update(); + + let cubeItems = me.findItems(-1, -1, sdk.storage.Cube); + + if (items) { + for (let cubeItem of cubeItems) { + let result = Pickit.checkItem(cubeItem); + + /** + * @todo + * - build better method of updating cubelist so if a item we cube is wanted by cubing we + * can update our list without clearing and rebuilding the whole thing + */ + + switch (result.result) { + case Pickit.Result.UNWANTED: + Item.logger("Dropped", cubeItem, "doCubing"); + cubeItem.drop(); + + break; + case Pickit.Result.WANTED: + Item.logger("Cubing Kept", cubeItem); + Item.logItem("Cubing Kept", cubeItem, result.line); + + break; + case Pickit.Result.RUNEWORD: + Runewords.update(cubeItem.classid, cubeItem.gid); + + break; + case Pickit.Result.CRAFTING: + CraftingSystem.update(cubeItem); + + break; + } + } + } + + if (!this.emptyCube()) { + break; + } + } + + /** + * For now, until I write a better update method, give a recursive call to doCubing if after building list + * we find we can still cube + */ + Cubing.update(); + let checkList = this.recipes.slice().shuffle(); + if (checkList.some(Cubing.checkRecipe)) { + // we can still cube so recursive call to doCubing + return Cubing.doCubing(); + } + + if (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { + delay(1000); + + while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { + me.cancel(); + delay(300); + } + } + + return true; + }, + + cursorCheck: function () { + if (me.itemoncursor) { + let item = Game.getCursorUnit(); + + if (item) { + if (Storage.Inventory.CanFit(item) && Storage.Inventory.MoveTo(item)) return true; + if (Storage.Stash.CanFit(item) && Storage.Stash.MoveTo(item)) return true; + + if (item.drop()) { + Item.logger("Dropped", item, "cursorCheck"); + return true; + } + } + + return false; + } + + return true; + }, + + openCube: function () { + if (getUIFlag(sdk.uiflags.Cube)) return true; + + let cube = me.getItem(sdk.quest.item.Cube); + if (!cube) return false; + + if (cube.isInStash && !Town.openStash()) return false; + const cubeOpened = function () { + return getUIFlag(sdk.uiflags.Cube); + }; + + for (let i = 0; i < 5 && !cubeOpened(); i++) { + cube.interact(); + + if (Misc.poll(cubeOpened, (Time.seconds(1) * (i + 1)), 100)) { + delay(100 + me.ping * 2); // allow UI to initialize + + return true; + } + } + + return cubeOpened(); + }, + + closeCube: function (closeToStash = false) { + if (!getUIFlag(sdk.uiflags.Cube)) return true; + const cubeClosed = function () { + return !getUIFlag(sdk.uiflags.Cube); + }; + + const closeBtn = me.screensize + ? { x: 373, y: 469 } + : { x: 285, y: 394 }; + + for (let i = 0; i < 5 && !cubeClosed(); i++) { + closeToStash ? sendClick(closeBtn.x, closeBtn.y) : me.cancel(); + + if (Misc.poll(cubeClosed, (Time.seconds(1) * (i + 1)), 100)) { + delay(250 + me.ping * 2); // allow UI to initialize + + return true; + } + } + + return cubeClosed(); + }, + + emptyCube: function () { + let cube = me.getItem(sdk.quest.item.Cube); + if (!cube) return false; + + let items = me.findItems(-1, -1, sdk.storage.Cube); + if (!items) return true; + + /** @param {ItemUnit} item */ + const prettyPrint = function (item) { + return item && item.prettyPrint; + }; + + let sorted = false; + + while (items.length) { + !getUIFlag(sdk.uiflags.Cube) && Cubing.openCube(); + + if (!Storage.Stash.MoveTo(items[0]) && !Storage.Inventory.MoveTo(items[0])) { + // attempt to sort inventory first then try again + if (!sorted && Storage.Inventory.SortItems()) { + sorted = true; + continue; + } + + console.warn("Failed to empty cube. Items still in cube :: ", items.map(prettyPrint)); + return false; + } + + items.shift(); + } + + this.closeCube(); + + return true; + }, + + makeRevPots: function () { + let locations = { + Belt: 2, + Inventory: 3, + Cube: 6, + Stash: 7, + }; + let origin = [], cube = me.getItem(sdk.quest.item.Cube), cubeInStash; + + // Get a list of all items - Filter out all those rev pots + let revpots = me.getItemsEx().filter(item => item.classid === sdk.items.RejuvenationPotion); + + // Stop if less as 3 pots + if (revpots.length < 3) { + return; + } + + // Go to town and open stash + Town.goToTown() && Town.moveToSpot("stash"); + Town.openStash(); + + // For reasons unclear, cubing goes wrong in stash in my test, so for ease, i put cube in inventory + (cubeInStash = cube.location !== locations.Inventory) && Storage.Inventory.MoveTo(cube); + me.cancel(); + me.cancel(); + + // clear the cube, otherwise we cant transmute + Cubing.emptyCube(); + + // Remove excessive pots from the list. (only groups of 3) + revpots.length -= revpots.length % 3; + + // Call this function for each pot + revpots.forEach(function (pot, index) { + + // Add this to the original location array + origin.push({ location: pot.location, x: pot.x, y: pot.y }); + + Town.openStash(); + + // Move to inventory first (to avoid bugs) + Storage.Inventory.MoveTo(pot); + me.cancel(); // remove inventory/cube window + me.cancel(); // remove inventory window (if it was cube) + + // Move the current pot to the cube + Storage.Cube.MoveTo(pot); + // For every third pot, excluding the first + if (!index || (1 + index) % 3 !== 0) { + me.cancel(); // remove cube window + me.cancel(); // remove stash window + } else { + // press the transmute button + Cubing.openCube() && transmute(); + + // high delay here to avoid issues with ping spikes + delay(me.ping * 5 + 1000); // <-- probably can be less + + // Find all items in the cube. (the full rev pot) + let fullrev = me.findItem(-1, -1, sdk.storage.Cube); + + // Sort the original locations of the pots. Put a low location first (belt = 2, rest is higher). + origin.sort((a, b) => a.location - b.location).some(function (orgin) { // Loop over all the original spots. + + // Loop trough all possible locations + for (let i in locations) { + // If location is matched with its orgin, we know the name of the spot + locations[i] === orgin.location && (orgin.location = i); // Store the name of the location + } + + Storage.Inventory.MoveTo(fullrev); // First put to inventory; + me.cancel(); // cube + me.cancel(); // inventory + + // If the storage location is known, put the pot to this location + Storage[orgin.location] && Storage[orgin.location].MoveTo(fullrev); + + // If returned true, the prototype some stops looping. + return fullrev.location !== locations.Cube; + }); + + // empty the array + origin.length = 0; + + // Cube should be empty, but lets be sure + Cubing.emptyCube(); + } + }); + // Put cube back in stash, if it was when we started + cubeInStash && Storage.Stash.MoveTo(cube); + + me.cancel(); + me.cancel(); + }, + + /** + * @todo Add chipped/flawed gems for recharging a item + * @param {ItemUnit} item - Rune + */ + repairIngredientCheck: function (item) { + if (!Config.CubeRepair) return false; + if (item.classid !== sdk.items.runes.Ral && item.classid !== sdk.items.runes.Ort) { + return false; + } + + let [have, needRal, needOrt] = [0, 0, 0]; + let items = me.getItemsForRepair(Config.RepairPercent, false); + + if (items.length) { + while (items.length > 0) { + let runeNeeded = Item.getRepairIngred(items.shift()); + + if (runeNeeded === sdk.items.runes.Ral) { + needRal += 1; + } else if (runeNeeded === sdk.items.runes.Ort) { + needOrt += 1; + } + } + } + + switch (item.classid) { + case sdk.items.runes.Ral: + needRal && (have = me.findItems(sdk.items.runes.Ral).length); + + return (!have || have < needRal); + case sdk.items.runes.Ort: + needOrt && (have = me.findItems(sdk.items.runes.Ort).length); + + return (!have || have < needOrt); + default: + return false; + } + }, + + /** + * @todo Allow cube-repairing items from stash/invo + * @todo Repair & Recharge + * @param {ItemUnit} item + * @returns {boolean} + */ + repairItem: function (item) { + if (!item || !item.isEquipped) return false; + + const neededRune = Item.repairIngred(item); + const rune = me.getItem(neededRune); + const bodyLoc = item.bodylocation; + + if (!rune || !Cubing.emptyCube()) return false; + + for (let i = 0; i < 5; i++) { + if (!rune.isInCube) { + console.log("Moving rune to cube..."); + if (!Storage.Cube.MoveTo(rune)) continue; + } + if (!item.isInCube) { + console.log("Moving item to cube..."); + Storage.Cube.MoveTo(item); + } + if (rune.isInCube && item.isInCube && Cubing.openCube()) break; + } + + if (!rune.isInCube || !item.isInCube) { + console.log("Failed to move rune or item to cube."); + // If item was equipped try reequipping it + if (bodyLoc && !item.isEquipped) { + item.isInCube && Cubing.openCube(); + item.equip(bodyLoc); + delay(me.ping * 2 + 500); + me.cancelUIFlags(); + } + return false; + } + + for (let i = 0; i < 100; i += 1) { + let cubeItems = me.findItems(-1, -1, sdk.storage.Cube); + + if (!me.itemoncursor && cubeItems.length === 2) { + console.log("Transmuting..." + i); + transmute(); + delay(1000 + me.ping); + + cubeItems = me.findItems(-1, -1, sdk.storage.Cube); + + // We expect only one item in cube + console.log("Cube contents: " + cubeItems.map(i => i.name).join(", ")); + cubeItems.length === 1 && cubeItems[0].toCursor(); + } + + if (me.itemoncursor) { + const cubeItem = Game.getCursorUnit(); + for (let i = 0; i < 3; i++) { + clickItem(sdk.clicktypes.click.item.Left, bodyLoc); + delay(me.ping * 2 + 500); + + if (cubeItem.bodylocation === bodyLoc) { + console.log(cubeItem.prettyPrint + " successfully repaired and equipped."); + D2Bot.printToConsole(cubeItem.prettyPrint + " successfully repaired and equipped.", sdk.colors.D2Bot.Green); + me.cancelUIFlags(); + + return true; + } + } + } + + delay(200); + } + + // error report is good but do we really need to stop? + Misc.errorReport("Failed to put repaired item back on."); + D2Bot.stop(); + + return false; + }, + + doRepairs: function () { + if (!Config.CubeRepair || !me.cube) return false; + + let items = me.getItemsForRepair(Config.RepairPercent, false) + .sort(function (a, b) { + return a.durabilityPercent - b.durabilityPercent; + }); + + while (items.length > 0) { + Cubing.repairItem(items.shift()); + } + + return true; + }, +}; diff --git a/d2bs/kolbot/libs/core/Experience.js b/d2bs/kolbot/libs/core/Experience.js new file mode 100644 index 000000000..4d6a73185 --- /dev/null +++ b/d2bs/kolbot/libs/core/Experience.js @@ -0,0 +1,139 @@ +/* eslint-disable max-len */ +/** +* @filename Experience.js +* @author kolton +* @desc Experience library +* +*/ + +const Experience = { + /** + * @todo combine this and nextExp into key-value pairs 1-99 + * Experience[me.charlvl].total and Experience[me.charlvl].next + */ + totalExp: [ + 0, 0, 500, 1500, 3750, 7875, 14175, 22680, 32886, 44396, 57715, 72144, 90180, 112725, + 140906, 176132, 220165, 275207, 344008, 430010, 537513, 671891, 839864, 1049830, 1312287, + 1640359, 2050449, 2563061, 3203826, 3902260, 4663553, 5493363, 6397855, 7383752, 8458379, + 9629723, 10906488, 12298162, 13815086, 15468534, 17270791, 19235252, 21376515, 23710491, + 26254525, 29027522, 32050088, 35344686, 38935798, 42850109, 47116709, 51767302, 56836449, + 62361819, 68384473, 74949165, 82104680, 89904191, 98405658, 107672256, 117772849, 128782495, + 140783010, 153863570, 168121381, 183662396, 200602101, 219066380, 239192444, 261129853, + 285041630, 311105466, 339515048, 370481492, 404234916, 441026148, 481128591, 524840254, + 572485967, 624419793, 681027665, 742730244, 809986056, 883294891, 963201521, 1050299747, + 1145236814, 1248718217, 1361512946, 1484459201, 1618470619, 1764543065, 1923762030, + 2097310703, 2286478756, 2492671933, 2717422497, 2962400612, 3229426756, 3520485254, 0, 0 + ], + nextExp: [ + 0, 500, 1000, 2250, 4125, 6300, 8505, 10206, 11510, 13319, 14429, 18036, 22545, 28181, + 35226, 44033, 55042, 68801, 86002, 107503, 134378, 167973, 209966, 262457, 328072, 410090, + 512612, 640765, 698434, 761293, 829810, 904492, 985897, 1074627, 1171344, 1276765, 1391674, + 1516924, 1653448, 1802257, 1964461, 2141263, 2333976, 2544034, 2772997, 3022566, 3294598, + 3591112, 3914311, 4266600, 4650593, 5069147, 5525370, 6022654, 6564692, 7155515, 7799511, + 8501467, 9266598, 10100593, 11009646, 12000515, 13080560, 14257811, 15541015, 16939705, + 18464279, 20126064, 21937409, 23911777, 26063836, 28409582, 30966444, 33753424, 36791232, + 40102443, 43711663, 47645713, 51933826, 56607872, 61702579, 67255812, 73308835, 79906630, + 87098226, 94937067, 103481403, 112794729, 122946255, 134011418, 146072446, 159218965, 173548673, + 189168053, 206193177, 224750564, 244978115, 267026144, 291058498, 0, 0 + ], + expCurve: [13, 16, 110, 159, 207, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 225, 174, 92, 38, 5], + expPenalty: [1024, 976, 928, 880, 832, 784, 736, 688, 640, 592, 544, 496, 448, 400, 352, 304, 256, 192, 144, 108, 81, 61, 46, 35, 26, 20, 15, 11, 8, 6, 5], + monsterExp: [ + [1, 1, 1], [30, 78, 117], [40, 104, 156], [50, 131, 197], [60, 156, 234], [70, 182, 273], [80, 207, 311], [90, 234, 351], [100, 260, 390], [110, 285, 428], [120, 312, 468], + [130, 338, 507], [140, 363, 545], [154, 401, 602], [169, 440, 660], [186, 482, 723], [205, 533, 800], [225, 584, 876], [248, 644, 966], [273, 708, 1062], [300, 779, 1169], + [330, 857, 1286], [363, 942, 1413], [399, 1035, 1553], [439, 1139, 1709], [470, 1220, 1830], [503, 1305, 1958], [538, 1397, 2096], [576, 1494, 2241], [616, 1598, 2397], + [659, 1709, 2564], [706, 1832, 2748], [755, 1958, 2937], [808, 2097, 3146], [864, 2241, 3362], [925, 2399, 3599], [990, 2568, 3852], [1059, 2745, 4118], [1133, 2939, 4409], + [1212, 3144, 4716], [1297, 3365, 5048], [1388, 3600, 5400], [1485, 3852, 5778], [1589, 4121, 6182], [1693, 4409, 6614], [1797, 4718, 7077], [1901, 5051, 7577], + [2005, 5402, 8103], [2109, 5783, 8675], [2213, 6186, 9279], [2317, 6618, 9927], [2421, 7080, 10620], [2525, 7506, 11259], [2629, 7956, 11934], [2733, 8435, 12653], + [2837, 8942, 13413], [2941, 9477, 14216], [3045, 10044, 15066], [3149, 10647, 15971], [3253, 11286, 16929], [3357, 11964, 17946], [3461, 12680, 19020], + [3565, 13442, 20163], [3669, 14249, 21374], [3773, 15104, 22656], [3877, 16010, 24015], [3981, 16916, 25374], [4085, 17822, 26733], [4189, 18728, 28092], + [4293, 19634, 29451], [4397, 20540, 30810], [4501, 21446, 32169], [4605, 22352, 33528], [4709, 23258, 34887], [4813, 24164, 36246], [4917, 25070, 37605], + [5021, 25976, 38964], [5125, 26882, 40323], [5229, 27788, 41682], [5333, 28694, 43041], [5437, 29600, 44400], [5541, 30506, 45759], [5645, 31412, 47118], + [5749, 32318, 48477], [5853, 33224, 49836], [5957, 34130, 51195], [6061, 35036, 52554], [6165, 35942, 53913], [6269, 36848, 55272], [6373, 37754, 56631], + [6477, 38660, 57990], [6581, 39566, 59349], [6685, 40472, 60708], [6789, 41378, 62067], [6893, 42284, 63426], [6997, 43190, 64785], [7101, 44096, 66144], + [7205, 45002, 67503], [7309, 45908, 68862], [7413, 46814, 70221], [7517, 47720, 71580], [7621, 48626, 72939], [7725, 49532, 74298], [7829, 50438, 75657], + [7933, 51344, 77016], [8037, 52250, 78375], [8141, 53156, 79734], [8245, 54062, 81093], [8349, 54968, 82452], [8453, 55874, 83811], [160000, 160000, 160000] + ], + /** + * Percent progress into the current level. Format: xx.xx% + */ + progress: function () { + return me.getStat(sdk.stats.Level) === 99 ? 0 : (((me.getStat(sdk.stats.Experience) - this.totalExp[me.getStat(sdk.stats.Level)]) / this.nextExp[me.getStat(sdk.stats.Level)]) * 100).toFixed(2); + }, + + /** + * Total experience gained in current run + */ + gain: function () { + return (me.getStat(sdk.stats.Experience) - DataFile.getStats().experience); + }, + + /** + * Percent experience gained in current run + */ + gainPercent: function () { + return me.getStat(sdk.stats.Level) === 99 ? 0 : (this.gain() * 100 / this.nextExp[me.getStat(sdk.stats.Level)]).toFixed(6); + }, + + /** + * Runs until next level + */ + runsToLevel: function () { + return Math.round(((100 - this.progress()) / 100) * this.nextExp[me.getStat(sdk.stats.Level)] / this.gain()); + }, + + /** + * Total runs needed for next level (not counting current progress) + */ + totalRunsToLevel: function () { + return Math.round(this.nextExp[me.getStat(sdk.stats.Level)] / this.gain()); + }, + + /** + * Total time till next level + */ + timeToLevel: function () { + let tTLrawSeconds = (Math.floor((getTickCount() - me.gamestarttime) / 1000)).toString(); + let tTLrawtimeToLevel = this.runsToLevel() * tTLrawSeconds; + let tTLDays = Math.floor(tTLrawtimeToLevel / 86400); + let tTLHours = Math.floor((tTLrawtimeToLevel % 86400) / 3600); + let tTLMinutes = Math.floor(((tTLrawtimeToLevel % 86400) % 3600) / 60); + //let tTLSeconds = ((tTLrawtimeToLevel % 86400) % 3600) % 60; + + //return tDays + "d " + tTLHours + "h " + tTLMinutes + "m " + tTLSeconds + "s"; + //return tTLDays + "d " + tTLHours + "h " + tTLMinutes + "m"; + return (tTLDays ? tTLDays + " d " : "") + (tTLHours ? tTLHours + " h " : "") + (tTLMinutes ? tTLMinutes + " m" : ""); + }, + + /** + * Get Game Time + */ + getGameTime: function () { + let rawMinutes = Math.floor((getTickCount() - me.gamestarttime) / 60000).toString(); + let rawSeconds = (Math.floor((getTickCount() - me.gamestarttime) / 1000) % 60).toString(); + + rawMinutes <= 9 && (rawMinutes = "0" + rawMinutes); + rawSeconds <= 9 && (rawSeconds = "0" + rawSeconds); + + return " (" + rawMinutes + ":" + rawSeconds + ")"; + }, + + /** + * Log to manager + */ + log: function () { + let gain = this.gain(); + let progress = this.progress(); + let runsToLevel = this.runsToLevel(); + let getGameTime = this.getGameTime(); + let string = "[Game: " + me.gamename + (me.gamepassword ? "//" + me.gamepassword : "") + getGameTime + "] [Level: " + me.getStat(sdk.stats.Level) + " (" + progress + "%)] [XP: " + gain + "] [Games ETA: " + runsToLevel + "]"; + + if (gain) { + D2Bot.printToConsole(string, sdk.colors.D2Bot.Blue); + + if (me.getStat(sdk.stats.Level) > DataFile.getStats().level) { + D2Bot.printToConsole("Congrats! You gained a level. Current level:" + me.getStat(sdk.stats.Level), sdk.colors.D2Bot.Green); + } + } + } +}; diff --git a/d2bs/kolbot/libs/core/GameData/AreaData.js b/d2bs/kolbot/libs/core/GameData/AreaData.js new file mode 100644 index 000000000..230b78d98 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/AreaData.js @@ -0,0 +1,248 @@ +/** +* @filename AreaData.js +* @author Nishimura-Katsuo, theBGuy +* @desc area data library +* +*/ +(function (module, require) { + const MonsterData = require("./MonsterData"); + const SUPER = [ + 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, + 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 1, 0, 1, 4, 0, 2, 3, 1, 0, 1, 1, 0, 0, 0, + 1, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5, 1, 1, 1, 1, 3 + ]; + const AREA_LOCALE_STRING = [ + 5389, 5055, 5054, 5053, 5052, 5051, 5050, 5049, 5048, + 5047, 5046, 5045, 5044, 5043, 5042, 5041, 5040, 5039, + 5038, 5037, 5036, 5035, 5034, 5033, 5032, 5031, 5030, + 5029, 5028, 5027, 5026, 5025, 5024, 5023, 5022, 5021, + 5020, 5019, 5018, 788, 852, 851, 850, 849, 848, 847, + 846, 845, 844, 843, 842, 841, 840, 839, 838, 837, 836, + 835, 834, 833, 832, 831, 830, 829, 828, 827, 826, 826, + 826, 826, 826, 826, 826, 825, 824, 820, 819, 818, 817, + 816, 815, 814, 813, 812, 810, 811, 809, 808, 806, 805, + 807, 804, 845, 844, 803, 802, 801, 800, 799, 798, 797, + 796, 795, 790, 792, 793, 794, 791, 789, 22646, 22647, + 22648, 22649, 22650, 22651, 22652, 22653, 22654, 22655, + 22656, 22657, 22658, 22659, 22660, 22662, 21865, 21866, + 21867, 22663, 22664, 22665, 22667, 22666, 5389, 5389, 5389, 5018 + ]; + const MONSTER_KEYS = [ + ["mon1", "mon2", "mon3", "mon4", "mon5", "mon6", "mon7", "mon8", "mon9", "mon10"], + ["nmon1", "nmon2", "nmon3", "nmon4", "nmon5", "nmon6", "nmon7", "nmon8", "nmon9", "nmon10"], + ][me.diff && 1]; // mon is for normal, nmon is for nm/hell, umon is specific to picking champion/uniques in normal + const LocaleStringName = require("./LocaleStringID").LocaleStringName; + const AREA_INDEX_COUNT = 137; + + /** + * @typedef AreaDataObj + * @type {object} + * @property {number} Super = number of super uniques present in this area + * @property {number} Index = areaID + * @property {number} Act = act this area is in [0-4] + * @property {number} MonsterDensity = value used to determine monster population density + * @property {number} ChampionPacks.Min = minimum number of champion or unique packs that spawn here + * @property {number} ChampionPacks.Max = maximum number of champion or unique packs that spawn here + * @property {number} Waypoint = number in waypoint menu that leads to this area + * @property {number} Level = level of area (use GameData.areaLevel) + * @property {number} Size.x = width of area + * @property {number} Size.y = depth of area + * @property {number} Monsters = array of monsters that can spawn in this area + * @property {number} LocaleString = locale string index for getLocaleString + */ + + /** @type {AreaDataObj[]} */ + const AreaData = new Array(AREA_INDEX_COUNT); + + for (let i = 0; i < AreaData.length; i++) { + let index = i; + AreaData[i] = ({ + Super: SUPER[index], + Index: index, + Act: getBaseStat("levels", index, "Act"), + MonsterDensity: getBaseStat("levels", index, ["MonDen", "MonDen(N)", "MonDen(H)"][me.diff]), + ChampionPacks: ({ + Min: getBaseStat("levels", index, ["MonUMin", "MonUMin(N)", "MonUMin(H)"][me.diff]), + Max: getBaseStat("levels", index, ["MonUMax", "MonUMax(N)", "MonUMax(H)"][me.diff]) + }), + Waypoint: getBaseStat("levels", index, "Waypoint"), + Level: getBaseStat("levels", index, ["MonLvl1Ex", "MonLvl2Ex", "MonLvl3Ex"][me.diff]), + Size: (() => { + if (index === 111) { // frigid highlands doesn't specify size, manual measurement + return { x: 210, y: 710 }; + } + + if (index === 112) { // arreat plateau doesn't specify size, manual measurement + return { x: 690, y: 230 }; + } + + return { + x: getBaseStat("leveldefs", index, ["SizeX", "SizeX(N)", "SizeX(H)"][me.diff]), + y: getBaseStat("leveldefs", index, ["SizeY", "SizeY(N)", "SizeY(H)"][me.diff]) + }; + })(), + Monsters: (MONSTER_KEYS.map(key => getBaseStat("levels", index, key)).filter(key => key !== 65535)), + /** + * Check if this area has a monster of a certain type + * @function + * @param {number} type - monster type to check for + * @returns {boolean} + */ + hasMonsterType: function (type) { + return this.Monsters.some(monId => MonsterData[monId].Type === type); + }, + /** + * Iterate through each monster in this area and apply a callback function + * @function + * @param {function} cb - callback function to apply to each monster + */ + forEachMonster: function (cb) { + if (typeof cb === "function") { + this.Monsters.forEach(monID => { + cb( + MonsterData[monID], + MonsterData[monID].Rarity * (MonsterData[monID].GroupCount.Min + MonsterData[monID].GroupCount.Max) / 2 + ); + }); + } + }, + /** + * Iterate through each monster and minion in this area and apply a callback function + * @function + * @param {function} cb - callback function to apply to each monster + */ + forEachMonsterAndMinion: function (cb) { + if (typeof cb === "function") { + this.Monsters.forEach(monID => { + let rarity = (MonsterData[monID].Rarity + * (MonsterData[monID].GroupCount.Min + MonsterData[monID].GroupCount.Max) / 2); + cb(MonsterData[monID], rarity, null); + MonsterData[monID].Minions.forEach(minionID => { + let minionrarity = (MonsterData[monID].Rarity + * (MonsterData[monID].MinionCount.Min + + MonsterData[monID].MinionCount.Max) / 2 / MonsterData[monID].Minions.length); + cb(MonsterData[minionID], minionrarity, MonsterData[monID]); + }); + }); + } + }, + LocaleString: getLocaleString(AREA_LOCALE_STRING[index]), + InternalName: LocaleStringName[AREA_LOCALE_STRING[index]], + /** + * Check if area is a town area + * @function + */ + townArea: function () { + return AreaData[ + [ + sdk.areas.RogueEncampment, sdk.areas.LutGholein, + sdk.areas.KurastDocktown, sdk.areas.PandemoniumFortress, sdk.areas.Harrogath + ][this.Act]]; + }, + /** + * @function + */ + haveWaypoint: function () { + // get the last area that got a WP + let wpArea = this.nearestWaypointArea(); + + // If you dont need a wp, we want at least the town's wp + return getWaypoint(Pather.wpAreas.indexOf(wpArea || this.townArea().Index)); + }, + /** + * Find nearest waypoint in area + * @function + */ + nearestWaypointArea: function () { + // plot toward this are + const plot = Pather.plotCourse(this.Index, this.townArea().Index); + + // get the last area that got a WP + return plot.course.filter(el => Pather.wpAreas.indexOf(el) > -1).last(); + }, + /** + * @function + * @return {PresetUnit|undefined} + */ + waypointPreset: function () { + const wpIDs = [119, 145, 156, 157, 237, 238, 288, 323, 324, 398, 402, 429, 494, 496, 511, 539]; + for (let i = 0, preset, wpArea = this.nearestWaypointArea(); i < wpIDs.length || preset; i++) { + if ((preset = Game.getPresetObject(wpArea, wpIDs[i]))) { + return preset; + } + } + + return undefined; + }, + }); + } + + /** + * @property {function} AreaData.findByName + * @param {string} whatToFind + * @returns + */ + AreaData.findByName = function (whatToFind) { + let matches = AreaData + .map(area => [Math.min(whatToFind.diffCount(area.LocaleString), whatToFind.diffCount(area.InternalName)), area]) + .sort((a, b) => a[0] - b[0]); + + return matches[0][1]; + }; + + AreaData.dungeons = { + DenOfEvil: [sdk.areas.DenofEvil], + + Hole: [sdk.areas.HoleLvl1, sdk.areas.HoleLvl2, ], + + Pit: [sdk.areas.PitLvl1, sdk.areas.PitLvl2], + + Cave: [sdk.areas.CaveLvl1, sdk.areas.CaveLvl2], + + UndergroundPassage: [sdk.areas.UndergroundPassageLvl1, sdk.areas.UndergroundPassageLvl2, ], + + Cellar: [ + sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, + sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TowerCellarLvl5, + ], + + // act 2 + A2Sewers: [sdk.areas.A2SewersLvl1, sdk.areas.A2SewersLvl2, sdk.areas.A2SewersLvl3, ], + + StonyTomb: [sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, ], + + HallsOfDead: [sdk.areas.HallsoftheDeadLvl1, sdk.areas.HallsoftheDeadLvl2, sdk.areas.HallsoftheDeadLvl3, ], + + ClawViperTemple: [sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2, ], + + MaggotLair: [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3, ], + + Tombs: [ + sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7, + ], + + // act 3 + Swamp: [sdk.areas.SwampyPitLvl1, sdk.areas.SwampyPitLvl2, sdk.areas.SwampyPitLvl3, ], + + FlayerDungeon: [sdk.areas.FlayerDungeonLvl1, sdk.areas.FlayerDungeonLvl2, sdk.areas.FlayerDungeonLvl3, ], + + A3Sewers: [sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, ], + + HighLevelForgottenTemples: [sdk.areas.ForgottenTemple, sdk.areas.RuinedFane, sdk.areas.DisusedReliquary], + + LowLevelForgottenTemples: [sdk.areas.RuinedTemple, sdk.areas.DisusedFane, sdk.areas.ForgottenReliquary], + + // act 4 has no areas like that + + // act 5 + RedPortalPits: [sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit, ], + }; + + module.exports = AreaData; +})(module, require); diff --git a/d2bs/kolbot/libs/core/GameData/GameData.js b/d2bs/kolbot/libs/core/GameData/GameData.js new file mode 100644 index 000000000..034b04361 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/GameData.js @@ -0,0 +1,169 @@ +/* eslint-disable max-len */ +/** +* @filename GameData.js +* @author Nishimura-Katsuo +* @desc game data library +* +*/ + + +(function (module, require) { + const MonsterData = require("./MonsterData"); + const AreaData = require("./AreaData"); + + const GameData = { + townAreas: [0, 1, 40, 75, 103, 109], + monsterLevel: function (monsterID, areaID) { + if (me.diff) { // levels on nm/hell are determined by area, not by monster data + return AreaData[areaID].Level; + } + + return MonsterData[monsterID].Level; + }, + monsterExp: function (monsterID, areaID) { + return Experience.monsterExp[this.monsterLevel(monsterID, areaID)][me.diff] * MonsterData[monsterID].ExperienceModifier / 100; + }, + areaLevel: function (areaID) { + let levels = 0, total = 0; + + if (me.diff) { // levels on nm/hell are determined by area, not by monster data + return AreaData[areaID].Level; + } + + AreaData[areaID].Monsters.forEach(mon => { + levels += MonsterData[mon].Level * MonsterData[mon].Rarity; + total += MonsterData[mon].Rarity; + }); + + return Math.round(levels / total); + }, + areaImmunities: function (areaID) { + let resists = { Physical: 0, Magic: 0, Fire: 0, Lightning: 0, Cold: 0, Poison: 0 }; + + function checkmon (monID) { + for (let k in resists) { + resists[k] = Math.max(resists[k], MonsterData[monID][k]); + } + } + + AreaData[areaID].Monsters.forEach(mon => { + checkmon(mon); + MonsterData[mon].Minions.forEach(checkmon); + }); + + return Object.keys(resists).filter(key => resists[key] >= 100); + }, + levelModifier: function (clvl, mlvl) { + let bonus; + + if (clvl < 25 || mlvl < clvl) { + bonus = Experience.expCurve[Math.min(20, Math.max(0, Math.floor(mlvl - clvl + 10)))] / 255; + } else { + bonus = clvl / mlvl; + } + + return bonus * Experience.expPenalty[Math.min(30, Math.max(0, Math.round(clvl - 69)))] / 1024; + }, + multiplayerModifier: function (count) { + if (!count) { + let party = getParty(me); + + if (!party) { + return 1; + } + + count = 1; + + while (party.getNext()) { + count++; + } + } + + return (count + 1) / 2; + }, + partyModifier: function (playerID) { + let party = getParty(me), partyid = -1, level = 0, total = 0; + + if (!party) { + return 1; + } + + partyid = party.partyid; + + do { + if (party.partyid === partyid) { + total += party.level; + + if (playerID === party.name || playerID === party.gid) { + level = party.level; + } + } + } while (party.getNext()); + + return level / total; + }, + killExp: function (playerID, monsterID, areaID) { + let exp = this.monsterExp(monsterID, areaID), party = getParty(me), partyid = -1, level = 0, total = 0, gamesize = 0; + + if (!party) { + return 0; + } + + partyid = party.partyid; + + do { + gamesize++; + + if (party.partyid === partyid) { + total += party.level; + + if (playerID === party.name || playerID === party.gid) { + level = party.level; + } + } + } while (party.getNext()); + + return Math.floor(exp * this.levelModifier(level, this.monsterLevel(monsterID, areaID)) * this.multiplayerModifier(gamesize) * level / total); + }, + areaPartyExp: function (areaID, exclude = null, onlytown = true, ignore = null) { // amount of total party exp gained per kill on average + let party = getParty(me), partyid = -1, partylevels = 0, gamesize = 0, exp = 0, playerexp = 0, poolsize = 0; + + if (!party) { + return 0; + } + + // very rough approximation of unique population ratio, could be approved but this works well enough + let uniqueratio = parseFloat(Config.ChampionBias) * (AreaData[areaID].ChampionPacks.Min + AreaData[areaID].ChampionPacks.Max + AreaData[areaID].Super * 2) / (AreaData[areaID].Size.x * AreaData[areaID].Size.y); + + partyid = party.partyid; + + do { + gamesize++; + + if (party.partyid === partyid && party.name !== exclude && party.gid !== exclude && (!onlytown || this.townAreas.indexOf(party.area) > -1) && (areaID < 128 || party.level >= (1 + me.diff) * 20)) { + partylevels += party.level; + + if (party.name !== ignore && party.gid !== ignore) { + poolsize = 0; + playerexp = 0; + + AreaData[areaID].Monsters.forEach(mon => { + if (MonsterData[mon].Rarity > 0) { + playerexp += ((1 - uniqueratio) + (3 * uniqueratio)) * this.monsterExp(mon, areaID) * this.levelModifier(party.level, this.monsterLevel(mon, areaID)) * MonsterData[mon].Rarity; + poolsize += MonsterData[mon].Rarity; + } + }); + + if (poolsize) { + exp += party.level * playerexp / poolsize; + } + } + } + } while (party.getNext()); + + return (partylevels ? exp * this.multiplayerModifier(gamesize) / partylevels : 0); + } + }; + + module.exports = GameData; +})(module, require); diff --git a/d2bs/kolbot/libs/core/GameData/LocaleStringID.js b/d2bs/kolbot/libs/core/GameData/LocaleStringID.js new file mode 100644 index 000000000..96332bc3e --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/LocaleStringID.js @@ -0,0 +1,7806 @@ +/** +* @filename LocaleStringID.js +* @author Nishimura-Katsuo +* @desc locale string indexes from NameStr ids +*/ +(function (module) { + let LocaleStringID = { + "WarrivAct1IntroGossip1": 0, + "WarrivAct1IntroPalGossip1": 1, + "WarrivGossip1": 2, + "WarrivGossip2": 3, + "WarrivGossip3": 4, + "WarrivGossip4": 5, + "WarrivGossip5": 6, + "WarrivGossip6": 7, + "WarrivGossip7": 8, + "WarrivGossip8": 9, + "WarrivGossip9": 10, + "AkaraIntroGossip1": 11, + "AkaraIntroSorGossip1": 12, + "AkaraGossip1": 13, + "AkaraGossip2": 14, + "AkaraGossip3": 15, + "AkaraGossip4": 16, + "AkaraGossip5": 17, + "AkaraGossip6": 18, + "AkaraGossip7": 19, + "AkaraGossip8": 20, + "AkaraGossip9": 21, + "AkaraGossip10": 22, + "AkaraGossip11": 23, + "KashyaIntroGossip1": 24, + "KashyaIntroAmaGossip1": 25, + "KashyaGossip1": 26, + "KashyaGossip2": 27, + "KashyaGossip3": 28, + "KashyaGossip4": 29, + "KashyaGossip5": 30, + "KashyaGossip6": 31, + "KashyaGossip7": 32, + "KashyaGossip8": 33, + "KashyaGossip9": 34, + "KashyaGossip10": 35, + "CharsiIntroGossip1": 36, + "CharsiIntroBarGossip1": 37, + "CharsiGossip1": 38, + "CharsiGossip2": 39, + "CharsiGossip3": 40, + "CharsiGossip4": 41, + "CharsiGossip5": 42, + "CharsiGossip6": 43, + "CharsiGossip7": 44, + "GheedIntroGossip1": 45, + "GheedIntroNecGossip1": 46, + "GheedGossip1": 47, + "GheedGossip2": 48, + "GheedGossip3": 49, + "GheedGossip4": 50, + "GheedGossip5": 51, + "GheedGossip6": 52, + "GheedGossip7": 53, + "CainGossip1": 54, + "CainGossip2": 55, + "CainGossip3": 56, + "CainGossip4": 57, + "CainGossip5": 58, + "RogueSignpostGossip1": 59, + "RogueSignpostGossip2": 60, + "RogueSignpostGossip3": 61, + "RogueSignpostGossip4": 62, + "RogueSignpostGossip5": 63, + "A1Q1InitAkara": 64, + "A1Q1AfterInitAkara": 65, + "A1Q1AfterInitKashya": 66, + "A1Q1AfterInitCharsiMain": 67, + "A1Q1AfterInitCharsiAlt": 68, + "A1Q1AfterInitGheed": 69, + "A1Q1AfterInitWarriv": 70, + "A1Q1EarlyReturnAkara": 71, + "A1Q1EarlyReturnKashya": 72, + "A1Q1EarlyReturnCharsi": 73, + "A1Q1EarlyReturnGheed": 74, + "A1Q1EarlyReturnWarriv": 75, + "A1Q1SuccessfulAkara": 76, + "A1Q1SuccessfulKashya": 77, + "A1Q1SuccessfulCharsi": 78, + "A1Q1SuccessfulGheed": 79, + "A1Q1SuccessfulWarriv": 80, + "A1Q2InitKashya": 81, + "A1Q2AfterInitKashya": 82, + "A1Q2AfterInitCharsi": 83, + "A1Q2AfterInitGheed": 84, + "A1Q2AfterInitAkara": 85, + "A1Q2AfterInitWarriv": 86, + "A1Q2EarlyReturnKashya": 87, + "A1Q2EarlyReturnAkara": 88, + "A1Q2EarlyReturnCharsi": 89, + "A1Q2EarlyReturnGheed": 90, + "A1Q2EarlyReturnWarriv": 91, + "A1Q2SuccessfulKashya": 92, + "A1Q2SuccessfulAkara": 93, + "A1Q2SuccessfulCharsi": 94, + "A1Q2SuccessfulGheed": 95, + "A1Q2SuccessfulWarriv": 96, + "A1Q4InitAkara": 97, + "A1Q4AfterInitScrollKashya": 98, + "A1Q4AfterInitScrollAkara": 99, + "A1Q4AfterInitScrollCharsi": 100, + "A1Q4AfterInitScrollWarriv": 101, + "A1Q4AfterInitScrollGheed": 102, + "A1Q4InstructionsCharsi": 103, + "A1Q4EarlyReturnSAkara": 104, + "A1Q4EarlyReturnSKashya": 105, + "A1Q4EarlyReturnSGheed": 106, + "A1Q4EarlyReturnSWarriv": 107, + "A1Q4SuccessfulScrollKashya": 108, + "A1Q4SuccessfulScrollCharsi": 109, + "A1Q4SuccessfulScrollGheed": 110, + "A1Q4SuccessfulScrollWarriv": 111, + "A1Q4InstructionsAkara": 112, + "A1Q4EarlyReturnKashya": 113, + "A1Q4EarlyReturnCharsi": 114, + "A1Q4EarlyReturnGheed": 115, + "A1Q4EarlyReturnWarriv": 116, + "A1Q4EarlyReturnAkara": 117, + "A1Q4QuestSuccessfulAkara": 118, + "A1Q4QuestSuccessfulKashya": 119, + "A1Q4QuestSuccessfulGheed": 120, + "A1Q4QuestSuccessfulCharsi": 121, + "A1Q4QuestSuccessfulWarriv": 122, + "A1Q4QuestSuccessfulCain": 123, + "A1Q4RescuedByHeroCain": 124, + "A1Q4RescuedByRoguesCain": 125, + "A1Q4TragedyOfTristramCain": 126, + "A1Q5InitQuestTome": 127, + "A1Q5AfterInitGheed": 128, + "A1Q5AfterInitCharsi": 129, + "A1Q5AfterInitAkara": 130, + "A1Q5AfterInitCain": 131, + "A1Q5AfterInitWarriv": 132, + "A1Q5AfterInitKashya": 133, + "A1Q5EarlyReturnKashya": 134, + "A1Q5EarlyReturnCain": 135, + "A1Q5EarlyReturnWarriv": 136, + "A1Q5EarlyReturnCharsi": 137, + "A1Q5EarlyReturnAkara": 138, + "A1Q5EarlyReturnGheed": 139, + "A1Q5SuccessfulKashya": 140, + "A1Q5SuccessfulWarriv": 141, + "A1Q5SuccessfulGheed": 142, + "A1Q5SuccessfulAkara": 143, + "A1Q5SuccessfulCharsi": 144, + "A1Q5SuccessfulCain": 145, + "A1Q3InitCharsi": 146, + "A1Q3AfterInitCain": 147, + "A1Q3AfterInitAkara": 148, + "A1Q3AfterInitKashya": 149, + "A1Q3AfterInitCharsi": 150, + "A1Q3AfterInitGheed": 151, + "A1Q3AfterInitGheedAlt": 152, + "A1Q3AfterInitWarriv": 153, + "A1Q3EarlyReturnCain": 154, + "A1Q3EarlyReturnAkara": 155, + "A1Q3EarlyReturnKashya": 156, + "A1Q3EarlyReturnCharsi": 157, + "A1Q3EarlyReturnGheed": 158, + "A1Q3EarlyReturnWarriv": 159, + "A1Q3SuccessfulCain": 160, + "A1Q3SuccessfulAkara": 161, + "A1Q3SuccessfulKashya": 162, + "A1Q3SuccessfulCharsi": 163, + "A1Q3SuccessfulGheed": 164, + "A1Q3SuccessfulWarriv": 165, + "A1Q6InitCain": 166, + "A1Q6AfterInitCain": 167, + "A1Q6AfterInitAkara": 168, + "A1Q6AfterInitCharsi": 169, + "A1Q6AfterInitGheed": 170, + "A1Q6AfterInitWarriv": 171, + "A1Q6AfterInitKashya": 172, + "A1Q6EarlyReturnCain": 173, + "A1Q6EarlyReturnAkara": 174, + "A1Q6EarlyReturnGheed": 175, + "A1Q6EarlyReturnCharsi": 176, + "A1Q6EarlyReturnWarriv": 177, + "A1Q6EarlyReturn2Kashya": 178, + "A1Q6SuccessfulAkara": 179, + "A1Q6SuccessfulCharsi": 180, + "A1Q6SuccessfulKashya": 181, + "A1Q6SuccessfulGheed": 182, + "A1Q6SuccessfulWarriv": 183, + "A1Q6SuccessfulCain": 184, + "PalaceGuardGossip1": 185, + "PalaceGuardGossip2": 186, + "PalaceGuardGossip3": 187, + "PalaceGuardGossip4": 188, + "PalaceGuardGossip5": 189, + "GriezIntroGossip1": 190, + "GriezGossip1": 191, + "GriezGossip2": 192, + "GriezGossip3": 193, + "GriezGossip4": 194, + "GriezGossip5": 195, + "GriezGossip6": 196, + "GriezGossip7": 197, + "GriezGossip8": 198, + "GriezGossip9": 199, + "GriezGossip10": 200, + "GriezGossip11": 201, + "GriezGossip12": 202, + "ElzixIntroGossip1": 203, + "ElzixIntroNecGossip1": 204, + "ElzixGossip1": 205, + "ElzixGossip2": 206, + "ElzixGossip3": 207, + "ElzixGossip4": 208, + "ElzixGossip5": 209, + "ElzixGossip6": 210, + "ElzixGossip7": 211, + "ElzixGossip8": 212, + "ElzixGossip9": 213, + "ElzixGossip10": 214, + "WarrivAct2IntroGossip1": 215, + "WarrivAct2Gossip1": 216, + "WarrivAct2Gossip2": 217, + "WarrivAct2Gossip3": 218, + "WarrivAct2Gossip4": 219, + "WarrivAct2Gossip5": 220, + "AtmaIntroGossip1": 221, + "AtmaGossip1": 222, + "AtmaGossip2": 223, + "AtmaGossip3": 224, + "AtmaGossip4": 225, + "AtmaGossip5": 226, + "AtmaGossip6": 227, + "AtmaGossip7": 228, + "AtmaGossip8": 229, + "GeglashIntroGossip1": 230, + "GeglashIntroBarGossip1": 231, + "GeglashGossip1": 232, + "GeglashGossip2": 233, + "GeglashGossip3": 234, + "GeglashGossip4": 235, + "GeglashGossip5": 236, + "GeglashGossip6": 237, + "GeglashGossip7": 238, + "GeglashGossip8": 239, + "GeglashGossip9": 240, + "MeshifIntroGossip1": 241, + "MeshifIntroAmaGossip1": 242, + "MeshifGossip1": 243, + "MeshifGossip2": 244, + "MeshifGossip3": 245, + "MeshifGossip4": 246, + "MeshifGossip5": 247, + "MeshifGossip6": 248, + "MeshifGossip7": 249, + "MeshifGossip8": 250, + "MeshifGossip9": 251, + "MeshifGossip10": 252, + "JerhynActIntroGossip1": 253, + "JerhynActIntroMoreGossip1": 254, + "JerhynIntroGossip1": 255, + "JerhynGossip1": 256, + "JerhynGossip2": 257, + "JerhynGossip3": 258, + "JerhynGossip4": 259, + "JerhynGossip5": 260, + "JerhynGossip6": 261, + "JerhynGossip7": 262, + "FaraIntroGossip1": 263, + "FaraIntroPalGossip1": 264, + "FaraGossip1": 265, + "FaraGossip2": 266, + "FaraGossip3": 267, + "FaraGossip4": 268, + "FaraGossip5": 269, + "FaraGossip6": 270, + "FaraGossip7": 271, + "FaraGossip8": 272, + "FaraGossip9": 273, + "LysanderIntroGossip1": 274, + "LysanderGossip1": 275, + "LysanderGossip2": 276, + "LysanderGossip3": 277, + "LysanderGossip4": 278, + "LysanderGossip5": 279, + "LysanderGossip6": 280, + "LysanderGossip7": 281, + "LysanderGossip8": 282, + "LysanderGossip9": 283, + "LysanderGossip10": 284, + "DrognanIntroGossip1": 285, + "DrognanIntroSorGossip1": 286, + "DrognanGossip1": 287, + "DrognanGossip2": 288, + "DrognanGossip3": 289, + "DrognanGossip4": 290, + "DrognanGossip5": 291, + "DrognanGossip6": 292, + "DrognanGossip7": 293, + "DrognanGossip8": 294, + "DrognanGossip9": 295, + "DrognanGossip10": 296, + "CainAct2Gossip1": 297, + "CainAct2Gossip2": 298, + "CainAct2Gossip3": 299, + "CainAct2Gossip4": 300, + "CainAct2Gossip5": 301, + "TyraelGossip1": 302, + "Desert2GuardGossip1": 303, + "A2Q1InitAtma": 304, + "A2Q1AfterInitGreiz": 305, + "A2Q1AfterInitElzix": 306, + "A2Q1AfterInitWarrivAct2": 307, + "A2Q1AfterInitGeglash": 308, + "A2Q1AfterInitFara": 309, + "A2Q1AfterInitAtma": 310, + "A2Q1AfterInitMeshif": 311, + "A2Q1AfterInitDrognan": 312, + "A2Q1AfterInitLysander": 313, + "A2Q1AfterInitCain": 314, + "A2Q1EarlyReturnWarrivAct2": 315, + "A2Q1EarlyReturnMeshif": 316, + "A2Q1EarlyReturnAtma": 317, + "A2Q1EarlyReturnGreiz": 318, + "A2Q1EarlyReturnGeglash": 319, + "A2Q1EarlyReturnElzix": 320, + "A2Q1EarlyReturnLysander": 321, + "A2Q1EarlyReturnDrognan": 322, + "A2Q1EarlyReturnFara": 323, + "A2Q1EarlyReturnCain": 324, + "A2Q1SuccessfulGreiz": 325, + "A2Q1SuccessfulDrognan": 326, + "A2Q1SuccessfulLysander": 327, + "A2Q1SuccessfulMeshif": 328, + "A2Q1SuccessfulGeglash": 329, + "A2Q1SuccessfulElzix": 330, + "A2Q1SuccessfulWarrivAct2": 331, + "A2Q1SuccessfulFara": 332, + "A2Q1SuccessfulCain": 333, + "A2Q1SuccessfulAtma": 334, + "A2Q2EarlyReturnScrollCain": 335, + "A2Q2EarlyReturnCapCain": 336, + "A2Q2EarlyReturnStaveCain": 337, + "A2Q2EarlyReturnCubeCain": 338, + "A2Q2SuccessfulStaffCain": 339, + "A2Q3AfterInitJerhyn": 340, + "A2Q3AfterInitGreiz": 341, + "A2Q3AfterInitElzix": 342, + "A2Q3AfterInitWarrivAct2": 343, + "A2Q3AfterInitAtma": 344, + "A2Q3AfterInitGeglash": 345, + "A2Q3AfterInitFara": 346, + "A2Q3AfterInitLysander": 347, + "A2Q3AfterInitDrognan": 348, + "A2Q3AfterInitMeshif": 349, + "A2Q3AfterInitCain": 350, + "A2Q3EarlyReturnJerhyn": 351, + "A2Q3EarlyReturnGreiz": 352, + "A2Q3EarlyReturnWarrivAct2": 353, + "A2Q3EarlyReturnGeglash": 354, + "A2Q3EarlyReturnMeshif": 355, + "A2Q3EarlyReturnFara": 356, + "A2Q3EarlyReturnLysander": 357, + "A2Q3EarlyReturnDrognan": 358, + "A2Q3EarlyReturnElzix": 359, + "A2Q3EarlyReturnCain": 360, + "A2Q3EarlyReturnAtma": 361, + "A2Q3SuccessfulJerhyn": 362, + "A2Q3SuccessfulGreiz": 363, + "A2Q3SuccessfulElzix": 364, + "A2Q3SuccessfulGeglash": 365, + "A2Q3SuccessfulWarrivAct2": 366, + "A2Q3SuccessfulMeshif": 367, + "A2Q3SuccessfulAtma": 368, + "A2Q3SuccessfulFara": 369, + "A2Q3SuccessfulLysander": 370, + "A2Q3SuccessfulDrognan": 371, + "A2Q3SuccessfulCain": 372, + "A2Q4InitDrognan": 373, + "A2Q4AfterInitFara": 374, + "A2Q4AfterInitGreiz": 375, + "A2Q4AfterInitElzix": 376, + "A2Q4AfterInitJerhyn": 377, + "A2Q4AfterInitCain": 378, + "A2Q4AfterInitGeglash": 379, + "A2Q4AfterInitAtma": 380, + "A2Q4AfterInitWarrivAct2": 381, + "A2Q4AfterInitLysander": 382, + "A2Q4AfterInitDrognan": 383, + "A2Q4AfterInitMeshif": 384, + "A2Q4EarlyReturnElzix": 385, + "A2Q4EarlyReturnJerhyn": 386, + "A2Q4EarlyReturnGreiz": 387, + "A2Q4EarlyReturnDrognan": 388, + "A2Q4EarlyReturnLysander": 389, + "A2Q4EarlyReturnFara": 390, + "A2Q4EarlyReturnGeglash": 391, + "A2Q4EarlyReturnMeshif": 392, + "A2Q4EarlyReturnAtma": 393, + "A2Q4EarlyReturnWarrivAct2": 394, + "A2Q4EarlyReturnCain": 395, + "A2Q4SuccessfulNarrator": 396, + "A2Q4SuccessfulGriez": 397, + "A2Q4SuccessfulJerhyn": 398, + "A2Q4SuccessfulDrognan": 399, + "A2Q4SuccessfulElzix": 400, + "A2Q4SuccessfulGeglash": 401, + "A2Q4SuccessfulMeshif": 402, + "A2Q4SuccessfulWarrivAct2": 403, + "A2Q4SuccessfulFara": 404, + "A2Q4SuccessfulLysander": 405, + "A2Q4SuccessfulAtma": 406, + "A2Q4SuccessfulCain": 407, + "A2Q5EarlyReturnGreiz": 408, + "A2Q5EarlyReturnJerhyn": 409, + "A2Q5EarlyReturnDrognan": 410, + "A2Q5EarlyReturnLysander": 411, + "A2Q5EarlyReturnMeshif": 412, + "A2Q5EarlyReturnWarrivAct2": 413, + "A2Q5EarlyReturnAtma": 414, + "A2Q5EarlyReturnGeglash": 415, + "A2Q5EarlyReturnFara": 416, + "A2Q5EarlyReturnElzix": 417, + "A2Q5EarlyReturnCain": 418, + "A2Q5SuccessfulGreiz": 419, + "A2Q5SuccessfulGeglash": 420, + "A2Q5SuccessfulJerhyn": 421, + "A2Q5SuccessfulDrognan": 422, + "A2Q5SuccessfulElzix": 423, + "A2Q5SuccessfulWarrivAct2": 424, + "A2Q5SuccessfulMeshif": 425, + "A2Q5SuccessfulLysander": 426, + "A2Q5SuccessfulAtma": 427, + "A2Q5SuccessfulFara": 428, + "A2Q5SuccessfulCain": 429, + "A2Q6InitJerhyn": 430, + "A2Q6AfterInitJerhyn": 431, + "A2Q6AfterInitElzix": 432, + "A2Q6AfterInitWarrivAct2": 433, + "A2Q6AfterInitAtma": 434, + "A2Q6AfterInitGeglash": 435, + "A2Q6AfterInitMeshif": 436, + "A2Q6AfterInitFara": 437, + "A2Q6AfterInitLysander": 438, + "A2Q6AfterInitDrognan": 439, + "A2Q6AfterInitCain": 440, + "A2Q6AfterInitGreiz": 441, + "A2Q6SuccessfulJerhyn": 442, + "A2Q6SuccessfulElzix": 443, + "A2Q6SuccessfulLysander": 444, + "A2Q6SuccessfulAtma": 445, + "A2Q6SuccessfulWarrivAct2": 446, + "A2Q6SuccessfulFara": 447, + "A2Q6SuccessfulGeglash": 448, + "A2Q6SuccessfulDrognan": 449, + "A2Q6SuccessfulMeshif": 450, + "A2Q6SuccessfulGreiz": 451, + "A2Q6SuccessfulCain": 452, + "NatalyaIntroGossip1": 453, + "NatalyaGossip1": 454, + "NatalyaGossip2": 455, + "NatalyaGossip3": 456, + "NatalyaGossip4": 457, + "CainAct3IntroGossip1": 458, + "CainAct3Gossip1": 459, + "CainAct3Gossip2": 460, + "CainAct3Gossip3": 461, + "CainAct3Gossip4": 462, + "CainAct3Gossip5": 463, + "CainAct3Gossip6": 464, + "HratliActIntroGossip1": 465, + "HratliActIntroSorGossip1": 466, + "HratliGossip1": 467, + "HratliGossip2": 468, + "HratliGossip3": 469, + "HratliGossip4": 470, + "HratliGossip5": 471, + "HratliGossip6": 472, + "HratliGossip7": 473, + "HratliGossip8": 474, + "HratliGossip9": 475, + "HratliGossip10": 476, + "HratliGossip11": 477, + "MeshifAct3IntroGossip1": 478, + "MeshifAct3IntroBarGossip1": 479, + "MeshifAct3Gossip1": 480, + "MeshifAct3Gossip2": 481, + "MeshifAct3Gossip3": 482, + "MeshifAct3Gossip4": 483, + "MeshifAct3Gossip5": 484, + "MeshifAct3Gossip6": 485, + "MeshifAct3Gossip7": 486, + "MeshifAct3Gossip8": 487, + "MeshifAct3Gossip9": 488, + "MeshifAct3Gossip10": 489, + "AshearaIntroGossip1": 490, + "AshearaIntroAmaGossip1": 491, + "AshearaGossip1": 492, + "AshearaGossip2": 493, + "AshearaGossip3": 494, + "AshearaGossip4": 495, + "AshearaGossip5": 496, + "AshearaGossip6": 497, + "AshearaGossip7": 498, + "AshearaGossip8": 499, + "AshearaGossip9": 500, + "AlkorIntroGossip1": 501, + "AlkorIntroNecGossip1": 502, + "AlkorGossip1": 503, + "AlkorGossip2": 504, + "AlkorGossip3": 505, + "AlkorGossip4": 506, + "AlkorGossip5": 507, + "AlkorGossip6": 508, + "AlkorGossip7": 509, + "AlkorGossip8": 510, + "AlkorGossip9": 511, + "AlkorGossip10": 512, + "AlkorGossip11": 513, + "OrmusIntroGossip1": 514, + "OrmusIntroPalGossip1": 515, + "OrmusGossip1": 516, + "OrmusGossip2": 517, + "OrmusGossip3": 518, + "OrmusGossip4": 519, + "OrmusGossip5": 520, + "OrmusGossip6": 521, + "OrmusGossip7": 522, + "OrmusGossip8": 523, + "OrmusGossip9": 524, + "OrmusGossip10": 525, + "OrmusGossip11": 526, + "A3Q4Init1CainAct3": 527, + "A3Q4Init1Asheara": 528, + "A3Q4Init2MeshifAct3": 529, + "A3Q4Init2Natalya": 530, + "A3Q4Init3CainAct3": 531, + "A3Q4Init3Hratli": 532, + "A3Q4Init3Asheara": 533, + "A3Q4AfterInitAlkor": 534, + "A3Q4AfterInitOrmus": 535, + "A3Q4AfterInitHratli": 536, + "A3Q4AfterInitNatalya": 537, + "A3Q4SuccessfulAlkor": 538, + "A3Q4SuccessfulMeshifAct3": 539, + "A3Q4SuccessfulCainAct3": 540, + "A3Q4SuccessfulOrmus": 541, + "A3Q4SuccessfulNatalya": 542, + "A3Q2InitCain": 543, + "A3Q2EarlyReturnHeartCain": 544, + "A3Q2EarlyReturnEyeCain": 545, + "A3Q2EarlyReturnBrainCain": 546, + "A3Q2EarlyReturnFlailCain": 547, + "A3Q2SuccessfulCain": 548, + "A3Q1InitAlkor": 549, + "A3Q1AfterInitAlkor": 550, + "A3Q1AfterInitOrmus": 551, + "A3Q1AfterInitMeshifAct3": 552, + "A3Q1AfterInitAsheara": 553, + "A3Q1AfterInitHratli": 554, + "A3Q1AfterInitCainAct3": 555, + "A3Q1AfterInitNatalya": 556, + "A3Q1EarlyReturnAlkor": 557, + "A3Q1EarlyReturnOrmus": 558, + "A3Q1EarlyReturnMeshifAct3": 559, + "A3Q1EarlyReturnAsheara": 560, + "A3Q1EarlyReturnHratli": 561, + "A3Q1EarlyReturnCainAct3": 562, + "A3Q1EarlyReturnNatalya": 563, + "A3Q1SuccessfulAlkor": 564, + "A3Q1SuccessfulOrmus": 565, + "A3Q1SuccessfulMeshifAct3": 566, + "A3Q1SuccessfulAsheara": 567, + "A3Q1SuccessfulHratli": 568, + "A3Q1SuccessfulCainAct3": 569, + "A3Q1SuccessfulNatalya": 570, + "A3Q3InitHratli": 571, + "A3Q3AfterInitAlkor": 572, + "A3Q3AfterInitOrmus": 573, + "A3Q3AfterInitMeshifAct3": 574, + "A3Q3AfterInitAsheara": 575, + "A3Q3AfterInitHratli": 576, + "A3Q3AfterInitCainAct3": 577, + "A3Q3AfterInitNatalya": 578, + "A3Q3EarlyReturnAlkor": 579, + "A3Q3EarlyReturnOrmus": 580, + "A3Q3EarlyReturnMeshifAct3": 581, + "A3Q3EarlyReturnAsheara": 582, + "A3Q3EarlyReturnHratli": 583, + "A3Q3EarlyReturnCainAct3": 584, + "A3Q3EarlyReturnNatalya": 585, + "A3Q3SuccessfulAlkor": 586, + "A3Q3SuccessfulOrmus": 587, + "A3Q3SuccessfulMeshifAct3": 588, + "A3Q3SuccessfulAsheara": 589, + "A3Q3SuccessfulHratli": 590, + "A3Q3SuccessfulCainAct3": 591, + "A3Q3SuccessfulNatalya": 592, + "A3Q3RewardOrmus": 593, + "A3Q5InitOrmus": 594, + "A3Q5AfterInitAlkor": 595, + "A3Q5AfterInitAlkorVA": 596, + "A3Q5AfterInitOrmus": 597, + "A3Q5AfterInitOrmusVA": 598, + "A3Q5AfterInitMeshifAct3": 599, + "A3Q5AfterInitMeshifAct3VA": 600, + "A3Q5AfterInitAsheara": 601, + "A3Q5AfterInitAshearaVA": 602, + "A3Q5AfterInitHratli": 603, + "A3Q5AfterInitHratliVA": 604, + "A3Q5AfterInitCainAct3": 605, + "A3Q5AfterInitCainAct3VA": 606, + "A3Q5AfterInitNatalya": 607, + "A3Q5AfterInitNatalyaVA": 608, + "A3Q5EarlyReturnAlkor": 609, + "A3Q5EarlyReturnAlkorVA": 610, + "A3Q5EarlyReturnOrmus": 611, + "A3Q5EarlyReturnMeshifAct3": 612, + "A3Q5EarlyReturnMeshifAct3VA": 613, + "A3Q5EarlyReturnAsheara": 614, + "A3Q5EarlyReturnAshearaVA": 615, + "A3Q5EarlyReturnHratli": 616, + "A3Q5EarlyReturnHratliVA": 617, + "A3Q5EarlyReturnCainAct3": 618, + "A3Q5EarlyReturnNatalya": 619, + "A3Q5EarlyReturnNatalyaVA": 620, + "A3Q5SuccessfulAlkor": 621, + "A3Q5SuccessfulOrmus": 622, + "A3Q5SuccessfulMeshifAct3": 623, + "A3Q5SuccessfulAsheara": 624, + "A3Q5SuccessfulHratli": 625, + "A3Q5SuccessfulCainAct3": 626, + "A3Q5SuccessfulNatalya": 627, + "A3Q6InitOrmus": 628, + "A3Q6AfterInitAlkor": 629, + "A3Q6AfterInitAlkorVA": 630, + "A3Q6AfterInitOrmus": 631, + "A3Q6AfterInitOrmusVA": 632, + "A3Q6AfterInitMeshifAct3": 633, + "A3Q6AfterInitMeshifAct3VA": 634, + "A3Q6AfterInitAsheara": 635, + "A3Q6AfterInitAshearaVA": 636, + "A3Q6AfterInitHratli": 637, + "A3Q6AfterInitHratliVA": 638, + "A3Q6AfterInitCainAct3": 639, + "A3Q6AfterInitCainAct3VA": 640, + "A3Q6AfterInitNatalya": 641, + "A3Q6AfterInitNatalyaVA": 642, + "A3Q6EarlyReturnAlkor": 643, + "A3Q6EarlyReturnAlkorVA": 644, + "A3Q6EarlyReturnOrmus": 645, + "A3Q6EarlyReturnOrmusVA": 646, + "A3Q6EarlyReturnMeshifAct3": 647, + "A3Q6EarlyReturnMeshifAct3VA": 648, + "A3Q6EarlyReturnAsheara": 649, + "A3Q6EarlyReturnAshearaVA": 650, + "A3Q6EarlyReturnHratli": 651, + "A3Q6EarlyReturnHratliVA": 652, + "A3Q6EarlyReturnCainAct3": 653, + "A3Q6EarlyReturnCainAct3VA": 654, + "A3Q6EarlyReturnNatalya": 655, + "A3Q6EarlyReturnNatalyaVA": 656, + "A3Q6SuccessfulAlkor": 657, + "A3Q6SuccessfulOrmus": 658, + "A3Q6SuccessfulMeshifAct3": 659, + "A3Q6SuccessfulAsheara": 660, + "A3Q6SuccessfulHratli": 661, + "A3Q6SuccessfulCainAct3": 662, + "A3Q6SuccessfulNatalya": 663, + "TyraelActIntroGossip1": 664, + "TyraelAct4Gossip1": 665, + "CainAct4IntroGossip1": 666, + "CainAct4Gossip1": 667, + "HellsAngelGossip1": 668, + "HellsAngelGossip2": 669, + "A4Q1InitTyrael": 670, + "A4Q1AfterInitTyrael": 671, + "A4Q1AfterInitCain": 672, + "A4Q1EarlyReturnTyrael": 673, + "A4Q1EarlyReturnCain": 674, + "A4Q1SuccessfulIzual": 675, + "A4Q1SuccessfulTyrael": 676, + "A4Q1SuccessfulCain": 677, + "A4Q3InitHasStoneCain": 678, + "A4Q3InitNoStoneCain": 679, + "A4Q3SuccessfulCain": 680, + "A4Q2InitTyrael": 681, + "A4Q2AfterInitCain": 682, + "A4Q2AfterInitTyrael": 683, + "A4Q2SuccessfulTyrael": 684, + "A4Q2SuccessfulCain": 685, + "D2bnetHelp50": 686, + "D2bnetHelp": 687, + "D2bnetHelp2a": 688, + "D2bnetHelpa": 689, + "D2bnetHelp1": 690, + "D2bnetHelp2": 691, + "D2bnetHelp3": 692, + "D2bnetHelp4": 693, + "D2bnetHelp5": 694, + "D2bnetHelp5a": 695, + "D2bnetHelp6": 696, + "D2bnetHelp7": 697, + "D2bnetHelp8": 698, + "D2bnetHelp9": 699, + "D2bnetHelp10": 700, + "D2bnetHelp11": 701, + "D2bnetHelp36": 702, + "D2bnetHelp36a": 703, + "D2bnetHelp37": 704, + "D2bnetHelp37a": 705, + "D2bnetHelp38": 706, + "D2bnetHelp39": 707, + "D2bnetHelp40": 708, + "D2bnetHelp41": 709, + "D2bnetHelp42": 710, + "D2bnetHelp42a": 711, + "D2bnetHelp43": 712, + "D2bnetHelp44": 713, + "D2bnetHelp44ab": 714, + "D2bnetHelp44a": 715, + "D2bnetHelp45": 716, + "D2bnetHelp45b": 717, + "D2bnetHelp45a": 718, + "D2bnetHel46": 719, + "D2bnetHelp46a": 720, + "D2bnetHelp47": 721, + "D2bnetHelp48": 722, + "D2bnetHelp49": 723, + "D2bnetHelp12": 724, + "D2bnetHelp12c": 725, + "D2bnetHelp12b": 726, + "D2bnetHelp12a": 727, + "D2bnetHelp13": 728, + "D2bnetHelp13b": 729, + "D2bnetHelp13a": 730, + "D2bnetHelp14": 731, + "D2bnetHelp14a": 732, + "D2bnetHelp15": 733, + "D2bnetHelp15b": 734, + "D2bnetHelp15a": 735, + "D2bnetHelp16": 736, + "D2bnetHelp16b": 737, + "D2bnetHelp16a": 738, + "D2bnetHelp17": 739, + "D2bnetHelp17a": 740, + "D2bnetHelp18": 741, + "D2bnetHelp18a": 742, + "D2bnetHelp19": 743, + "D2bnetHelp19a": 744, + "D2bnetHelp20": 745, + "D2bnetHelp20a": 746, + "D2bnetHelp21": 747, + "D2bnetHelp21a": 748, + "D2bnetHelp22": 749, + "D2bnetHelp22a": 750, + "D2bnetHelp23": 751, + "D2bnetHelp23a": 752, + "D2bnetHelp24": 753, + "D2bnetHelp24a": 754, + "D2bnetHelp25": 755, + "D2bnetHelp25a": 756, + "D2bnetHelp26": 757, + "D2bnetHelp26b": 758, + "D2bnetHelp26a": 759, + "D2bnetHelp27": 760, + "D2bnetHelp27a": 761, + "D2bnetHelp28": 762, + "D2bnetHelp28a": 763, + "D2bnetHelp29": 764, + "D2bnetHelp29a": 765, + "D2bnetHelp30": 766, + "D2bnetHelp30a": 767, + "D2bnetHelp31": 768, + "D2bnetHelp31a": 769, + "D2bnetHelp32": 770, + "D2bnetHelp32a": 771, + "D2bnetHelp33": 772, + "D2bnetHelp34": 773, + "D2bnetHelp35": 774, + "D2bnetHelp51": 775, + "D2bnetHelp52": 776, + "D2bnetHelp53": 777, + "D2bnetHelp54": 778, + "D2bnetHelp55": 779, + "D2bnetHelp56": 780, + "D2bnetHelp57": 781, + "D2bnetHelp58": 782, + "D2bnetHelp59": 783, + "D2bnetHelp60": 784, + "D2bnetHelp61": 785, + "D2bnetHelp62": 786, + "D2bnetHelp63": 787, + "Moo Moo Farm": 788, + "Chaos Sanctum": 789, + "The Pandemonium Fortress": 790, + "River of Flame": 791, + "Outer Steppes": 792, + "Plains of Despair": 793, + "City of the Damned": 794, + "Durance of Hate Level 3": 795, + "Durance of Hate Level 2": 796, + "Durance of Hate Level 1": 797, + "Disused Reliquary": 798, + "Ruined Fane": 799, + "Forgotten Temple": 800, + "Forgotten Reliquary": 801, + "Disused Fane": 802, + "Ruined Temple": 803, + "Flayer Dungeon Level 3": 804, + "Flayer Dungeon Level 2": 805, + "Flayer Dungeon Level 1": 806, + "Swampy Pit Level 3": 807, + "Swampy Pit Level 2": 808, + "Swampy Pit Level 1": 809, + "Spider Cave": 810, + "Spider Cavern": 811, + "Travincal": 812, + "Kurast Causeway": 813, + "Upper Kurast": 814, + "Kurast Bazaar": 815, + "Lower Kurast": 816, + "Flayer Jungle": 817, + "Great Marsh": 818, + "Spider Forest": 819, + "Kurast Docktown": 820, + "Durance of Hate": 821, + "Flayer Dungeon": 822, + "Swampy Pit": 823, + "Arcane Sanctuary": 824, + "Duriel's Lair": 825, + "Tal Rasha's Tomb": 826, + "Ancient Tunnels": 827, + "Maggot Lair Level 3": 828, + "Maggot Lair Level 2": 829, + "Maggot Lair Level 1": 830, + "Claw Viper Temple Level 2": 831, + "Halls of the Dead Level 3": 832, + "Stony Tomb Level 2": 833, + "Claw Viper Temple Level 1": 834, + "Halls of the Dead Level 2": 835, + "Halls of the Dead Level 1": 836, + "Stony Tomb Level 1": 837, + "Palace Cellar Level 3": 838, + "Palace Cellar Level 2": 839, + "Palace Cellar Level 1 \tPalace Cellar Level 1": 840, + "Harem Level 2": 841, + "Harem Level 1": 842, + "Sewers Level 3": 843, + "Sewers Level 2": 844, + "Sewers Level 1": 845, + "Canyon of the Magi": 846, + "Valley of Snakes": 847, + "Lost City": 848, + "Far Oasis": 849, + "Dry Hills": 850, + "Rocky Waste": 851, + "Lut Gholein": 852, + "Maggot Lair": 853, + "Claw Viper Temple": 854, + "Halls of the Dead": 855, + "Stony Tomb": 856, + "Palace Cellar": 857, + "Harem": 858, + "Sewers": 859, + "To The Moo Moo Farm": 860, + "To Chaos Sanctum": 861, + "To The River of Flame": 862, + "To The Outer Steppes": 863, + "To The Plains of Despair": 864, + "To The City of the Damned": 865, + "To The Pandemonium Fortress": 866, + "To The Durance of Hate Level 3": 867, + "To The Durance of Hate Level 2": 868, + "To The Durance of Hate Level 1": 869, + "To The Disused Reliquary": 870, + "To The Ruined Fane": 871, + "To The Forgotten Temple": 872, + "To The Forgotten Reliquary": 873, + "To The Disused Fane": 874, + "To The Ruined Temple": 875, + "To The Flayer Dungeon Level 1": 876, + "To The Flayer Dungeon Level 2": 877, + "To The Flayer Dungeon Level 3": 878, + "To The Swampy Pit Level 3": 879, + "To The Swampy Pit Level 2": 880, + "To The Swampy Pit Level 1": 881, + "To The Spider Cave": 882, + "To The Spider Cavern": 883, + "To Travincal": 884, + "To The Kurast Causeway": 885, + "To Upper Kurast": 886, + "To The Kurast Bazaar": 887, + "To Lower Kurast": 888, + "To The Flayer Jungle": 889, + "To The Great Marsh": 890, + "To The Spider Forest": 891, + "To The Kurast Docktown": 892, + "To The Arcane Sanctuary": 893, + "To Duriel's Lair": 894, + "To Tal Rasha's Tomb": 895, + "To The Ancient Tunnels": 896, + "To The Maggot Lair Level 3": 897, + "To The Maggot Lair Level 2": 898, + "To The Maggot Lair Level 1": 899, + "To The Claw Viper Temple Level 2": 900, + "To The Halls of the Dead Level 3": 901, + "To The Stony Tomb Level 2": 902, + "To The Claw Viper Temple Level 1": 903, + "To The Halls of the Dead Level 2": 904, + "To The Halls of the Dead Level 1": 905, + "To The Stony Tomb Level 1": 906, + "To The Palace Cellar Level 3": 907, + "To The Palace Cellar Level 2": 908, + "To The Palace Cellar Level 1 \tTo The Palace Cellar Level 1 ": 909, + "To The Harem Level 2": 910, + "To The Harem Level 1": 911, + "To The Sewers Level 3": 912, + "To The Sewers Level 2": 913, + "To The Sewers Level 1": 914, + "To The Canyon of the Magi": 915, + "To The Valley of Snakes": 916, + "To The Lost City": 917, + "To The Far Oasis": 918, + "To The Dry Hills": 919, + "To The Rocky Waste": 920, + "To Lut Gholein": 921, + "qstsa2q0": 922, + "qstsa2q1": 923, + "qstsa2q2": 924, + "qstsa2q3": 925, + "qstsa2q4": 926, + "qstsa2q5": 927, + "qstsa2q6": 928, + "qstsa3q0": 929, + "qstsa3q1": 930, + "qstsa3q2": 931, + "qstsa3q3": 932, + "qstsa3q4": 933, + "qstsa3q5": 934, + "qstsa3q6": 935, + "qstsa4q0": 936, + "qstsa4q1": 937, + "qstsa4q2": 938, + "qstsa4q3": 939, + "qstsa2q01": 940, + "qstsa2q11": 941, + "qstsa2q12": 942, + "qstsa2q13": 943, + "qstsa2q21": 944, + "qstsa2q22": 945, + "qstsa2q23": 946, + "qstsa2q24": 947, + "qstsa2q25": 948, + "qstsa2q31": 949, + "qstsa2q31a": 950, + "qstsa2q32": 951, + "qstsa2q33": 952, + "qstsa2q41": 953, + "qstsa2q41a": 954, + "qstsa2q42": 955, + "qstsa2q43": 956, + "qstsa2q51": 957, + "qstsa2q52": 958, + "qstsa2q53": 959, + "qstsa2q61": 960, + "qstsa2q61a": 961, + "qstsa2q62": 962, + "qstsa2q63": 963, + "qstsa2q63a": 964, + "qstsa2q64": 965, + "qstsa2q65": 966, + "qstsa3q01": 967, + "qstsa3q11": 968, + "qstsa3q12": 969, + "qstsa3q21": 970, + "qstsa3q22": 971, + "qstsa3q23": 972, + "qstsa3q24": 973, + "qstsa3q25": 974, + "qstsa3q26": 975, + "qstsa3q21a": 976, + "qstsa3q31": 977, + "qstsa3q32": 978, + "qstsa3q33": 979, + "qstsa3q34": 980, + "qstsa3q35": 981, + "qstsa3q41": 982, + "qstsa3q42": 983, + "qstsa3q43": 984, + "qstsa3q44": 985, + "qstsa3q45": 986, + "qstsa3q51": 987, + "qstsa3q52": 988, + "qstsa3q53": 989, + "qstsa3q61": 990, + "qstsa3q62": 991, + "qstsa3q63": 992, + "qstsa3q31a": 993, + "qstsa3q51a": 994, + "qstsa3q61a": 995, + "qstsa4q11": 996, + "qstsa4q12": 997, + "qstsa4q13a": 998, + "qstsa4q13": 999, + "qstsa4q31": 1000, + "qstsa4q32": 1001, + "qstsa4q33": 1002, + "qstsa4q34": 1003, + "qstsa4q21": 1004, + "qstsa4q22": 1005, + "qstsa4q23": 1006, + "qstsa4q24": 1007, + "asheara": 1008, + "hratli": 1009, + "alkor": 1010, + "ormus": 1011, + "nikita": 1012, + "tyrael": 1013, + "Izual": 1014, + "izual": 1015, + "Jamella": 1016, + "halbu": 1017, + "Malachai": 1018, + "merca201": 1019, + "merca202": 1020, + "merca203": 1021, + "merca204": 1022, + "merca205": 1023, + "merca206": 1024, + "merca207": 1025, + "merca208": 1026, + "merca209": 1027, + "merca210": 1028, + "merca211": 1029, + "merca212": 1030, + "merca213": 1031, + "merca214": 1032, + "merca215": 1033, + "merca216": 1034, + "merca217": 1035, + "merca218": 1036, + "merca219": 1037, + "merca220": 1038, + "merca221": 1039, + "merca222": 1040, + "merca223": 1041, + "merca224": 1042, + "merca225": 1043, + "merca226": 1044, + "merca227": 1045, + "merca228": 1046, + "merca229": 1047, + "merca230": 1048, + "merca231": 1049, + "merca232": 1050, + "merca233": 1051, + "merca234": 1052, + "merca235": 1053, + "merca236": 1054, + "merca237": 1055, + "merca238": 1056, + "merca239": 1057, + "merca240": 1058, + "merca241": 1059, + "qf1": 1060, + "qf2": 1061, + "KhalimFlail": 1062, + "SuperKhalimFlail": 1063, + "qey": 1064, + "qbr": 1065, + "qhr": 1066, + "The Feature Creep": 1067, + "Hell Bovine": 1068, + "Playersubtitles00": 1069, + "Playersubtitles01": 1070, + "Playersubtitles02": 1071, + "Playersubtitles03": 1072, + "Playersubtitles04": 1073, + "Playersubtitles05": 1074, + "Playersubtitles06": 1075, + "Playersubtitles07": 1076, + "Playersubtitles09": 1077, + "Playersubtitles10": 1078, + "Playersubtitles11": 1079, + "Playersubtitles12": 1080, + "Playersubtitles13": 1081, + "Playersubtitles14": 1082, + "Playersubtitles15": 1083, + "Playersubtitles16": 1084, + "Playersubtitles17": 1085, + "Playersubtitles18": 1086, + "Playersubtitles21": 1087, + "Playersubtitles22": 1088, + "Playersubtitles23": 1089, + "Playersubtitles24": 1090, + "Playersubtitles25": 1091, + "Playersubtitles26": 1092, + "Playersubtitles27": 1093, + "Playersubtitles28": 1094, + "LeaveCampAma": 1095, + "LeaveCampBar": 1096, + "LeaveCampPal": 1097, + "LeaveCampSor": 1098, + "LeaveCampNec": 1099, + "EnterDOEAma": 1100, + "EnterDOEBar": 1101, + "EnterDOEPal": 1102, + "EnterDOESor": 1103, + "EnterDOENec": 1104, + "EnterBurialAma": 1105, + "EnterBurialBar": 1106, + "EnterBurialPal": 1107, + "EnterBurialSor": 1108, + "EnterBurialNec": 1109, + "EnterMonasteryAma": 1110, + "EnterMonasteryBar": 1111, + "EnterMonasteryPal": 1112, + "EnterMonasterySor": 1113, + "EnterMonasteryNec": 1114, + "EnterForgottenTAma": 1115, + "EnterForgottenTBar": 1116, + "EnterForgottenTPal": 1117, + "EnterForgottenTSor": 1118, + "EnterForgottenTNec": 1119, + "EnterJailAma": 1120, + "EnterJailBar": 1121, + "EnterJailPal": 1122, + "EnterJailSor": 1123, + "EnterJailNec": 1124, + "Barracksremoved": 1129, + "EnterCatacombsAma": 1130, + "EnterCatacombsBar": 1131, + "EnterCatacombsPal": 1132, + "EnterCatacombsSor": 1133, + "EnterCatacombsNec": 1134, + "CompletingDOEAma": 1135, + "CompletingDOEBar": 1136, + "CompletingDOEPal": 1137, + "CompletingDOESor": 1138, + "CompletingDOENec": 1139, + "CompletingBurialAma": 1140, + "CompletingBurialBar": 1141, + "CompletingBurialPal": 1142, + "CompletingBurialSor": 1143, + "CompletingBurialNec": 1144, + "FindingInifusAma": 1145, + "FindingInifusBar": 1146, + "FindingInifusPal": 1147, + "FindingInifusSor": 1148, + "FindingInifusNec": 1149, + "FindingCairnAma": 1150, + "FindingCairnBar": 1151, + "FindingCairnPal": 1152, + "FindingCairnSor": 1153, + "FindingCairnNec": 1154, + "FindingTristramAma": 1155, + "FindingTristramBar": 1156, + "FindingTristramPal": 1157, + "FindingTristramSor": 1158, + "FindingTristramNec": 1159, + "RescueCainAma": 1160, + "RescueCainBar": 1161, + "RescueCainPal": 1162, + "RescueCainSor": 1163, + "RescueCainNec": 1164, + "HoradricMalusAma": 1165, + "HoradricMalusBar": 1166, + "HoradricMalusPal": 1167, + "HoradricMalusSor": 1168, + "HoradricMalusNec": 1169, + "CompletingForgottenTAma": 1170, + "CompletingForgottenTBar": 1171, + "CompletingForgottenTPal": 21924, + "CompletingForgottenTSor": 1173, + "CompletingForgottenTNec": 1174, + "CompletingAndarielAma": 1175, + "CompletingAndarielBar": 1176, + "CompletingAndarielPal": 1177, + "CompletingAndarielSor": 1178, + "CompletingAndarielNec": 1179, + "EnteringRadamentAma": 1180, + "EnteringRadamentBar": 1181, + "EnteringRadamentPal": 1182, + "EnteringRadamentSor": 1183, + "EnteringRadamentNec": 1184, + "CompletingRadamentAma": 1185, + "CompletingRadamentBar": 1186, + "CompletingRadamentPal": 1187, + "CompletingRadamentSor": 1188, + "CompletingRadamentNec": 1189, + "BeginTaintedSunAma": 1190, + "BeginTaintedSunBar": 1191, + "BeginTaintedSunPal": 1192, + "BeginTaintedSunSor": 1193, + "BeginTaintedSunNec": 1194, + "EnteringClawViperAma": 1195, + "EnteringClawViperBar": 1196, + "EnteringClawViperPal": 1197, + "EnteringClawViperSor": 1198, + "EnteringClawViperNec": 1199, + "CompletingTaintedSunAma": 1200, + "CompletingTaintedSunBar": 1201, + "CompletingTaintedSunPal": 1202, + "CompletingTaintedSunSor": 1203, + "CompletingTaintedSunNec": 1204, + "EnteringArcaneAma": 1205, + "EnteringArcaneBar": 1206, + "EnteringArcanePal": 1207, + "EnteringArcaneSor": 1208, + "EnteringArcaneNec": 1209, + "FindingSummonerAma": 1210, + "FindingSummonerBar": 1211, + "FindingSummonerPal": 1212, + "FindingSummonerSor": 1213, + "FindingSummonerNec": 1214, + "CompletingSummonerAma": 1215, + "CompletingSummonerBar": 1216, + "CompletingSummonerPal": 1217, + "CompletingSummonerSor": 1218, + "CompletingSummonerNec": 1219, + "FindingdecoyTombAma": 1220, + "FindingdecoyTombBar": 1221, + "FindingdecoyTombPal": 1222, + "FindingdecoyTombSor": 1223, + "FindingdecoyTombNec": 1224, + "FindingTrueTombAma": 1225, + "FindingTrueTombBar": 1226, + "FindingTrueTombPal": 1227, + "FindingTrueTombSor": 1228, + "FindingTrueTombNec": 1229, + "CompletingTombAma": 1230, + "CompletingTombBar": 1231, + "CompletingTombPal": 1232, + "CompletingTombSor": 1233, + "CompletingTombNec": 1234, + "nodarkwanderer": 1235, + "FindingLamEsenAma": 1236, + "FindingLamEsenBar": 1237, + "FindingLamEsenPal": 1238, + "FindingLamEsenSor": 1239, + "FindingLamEsenNec": 1240, + "CompletingLamEsenAma": 1241, + "CompletingLamEsenBar": 1242, + "CompletingLamEsenPal": 1243, + "CompletingLamEsenSor": 1244, + "CompletingLamEsenNec": 1245, + "FindingBeneathCityAma": 1246, + "FindingBeneathCityBar": 1247, + "FindingBeneathCityPal": 1248, + "FindingBeneathCitySor": 1249, + "FindingBeneathCityNec": 1250, + "FindingDrainLeverAma": 1251, + "FindingDrainLeverBar": 1252, + "FindingDrainLeverPal": 1253, + "FindingDrainLeverSor": 1254, + "FindingDrainLeverNec": 1255, + "CompletingBeneathCityAma": 1256, + "CompletingBeneathCityBar": 1257, + "CompletingBeneathCityPal": 1258, + "CompletingBeneathCitySor": 1259, + "CompletingBeneathCityNec": 1260, + "CompletingBladeAma": 1261, + "CompletingBladeBar": 1262, + "CompletingBladePal": 1263, + "CompletingBladeSor": 1264, + "CompletingBladeNec": 1265, + "FindingJadeFigAma": 1270, + "FindingTempleAma": 1271, + "FindingTempleBar": 1272, + "FindingTemplePal": 1273, + "FindingTempleSor": 1274, + "FindingTempleNec": 1275, + "CompletingTempleAma": 1276, + "CompletingTempleBar": 1277, + "CompletingTemplePal": 1278, + "CompletingTempleSor": 1279, + "CompletingTempleNec": 1280, + "FindingGuardianTowerAma": 1281, + "FindingGuardianTowerBar": 1282, + "FindingGuardianTowerPal": 1283, + "FindingGuardianTowerSor": 1284, + "FindingGuardianTowerNec": 1285, + "CompletingGuardianTowerAma": 1286, + "CompletingGuardianTowerBar": 1287, + "CompletingGuardianTowerPal": 1288, + "CompletingGuardianTowerSor": 1289, + "CompletingGuardianTowerNec": 1290, + "FreezingIzualAma": 21972, + "FreezingIzualBar": 1292, + "FreezingIzualPal": 1293, + "FreezingIzualSor": 1294, + "FreezingIzualNec": 1295, + "Eskillname0": 1296, + "Eskillsd0": 1297, + "Eskillld0": 1298, + "Eskillan0": 1299, + "EskillnameExp1": 1300, + "EskillsExpd1": 1301, + "EskilllExpd1": 1302, + "EskillExpan1": 1303, + "Eskillname2": 1304, + "Eskillsd2": 1305, + "Eskillld2": 1306, + "Eskillan2": 1307, + "Eskillname3": 1308, + "Eskillsd3": 1309, + "Eskillld3": 1310, + "Eskillan3": 1311, + "Eskillname4": 1312, + "Eskillsd4": 1313, + "Eskillld4": 1314, + "Eskillan4": 1315, + "Eskillname5": 1316, + "Eskillsd5": 1317, + "Eskillld5": 1318, + "Eskillan5": 1319, + "Eskillname6": 1320, + "Eskillsd6": 1321, + "Eskillld6": 1322, + "Eskillan6": 1323, + "Eskillname7": 1324, + "Eskillsd7": 1325, + "Eskillld7": 1326, + "Eskillan7": 1327, + "Eskillname8": 1328, + "Eskillsd8": 1329, + "Eskillld8": 1330, + "Eskillan8": 1331, + "Eskillname9": 1332, + "Eskillsd9": 1333, + "Eskillld9": 1334, + "Eskillan9": 1335, + "Eskillname10": 1336, + "Eskillsd10": 1337, + "Eskillld10": 1338, + "Eskillan10": 1339, + "Eskillname11": 1340, + "Eskillsd11": 1341, + "Eskillld11": 1342, + "Eskillan11": 1343, + "Eskillname12": 1344, + "Eskillsd12": 1345, + "Eskillld12": 1346, + "Eskillan12": 1347, + "Eskillname13": 1348, + "Eskillsd13": 1349, + "Eskillld13": 1350, + "Eskillan13": 1351, + "Eskillname14": 1352, + "Eskillsd14": 1353, + "Eskillld14": 1354, + "Eskillan14": 1355, + "Eskillname15": 1356, + "Eskillsd15": 1357, + "Eskillld15": 1358, + "Eskillan15": 1359, + "Eskillname16": 1360, + "Eskillsd16": 1361, + "Eskillld16": 1362, + "Eskillan16": 1363, + "Eskillname17": 1364, + "Eskillsd17": 1365, + "Eskillld17": 1366, + "Eskillan17": 1367, + "Eskillname18": 1368, + "Eskillsd18": 1369, + "Eskillld18": 1370, + "Eskillan18": 1371, + "Eskillname19": 1372, + "Eskillsd19": 1373, + "Eskillld19": 1374, + "Eskillan19": 1375, + "Eskillname20": 1376, + "Eskillsd20": 1377, + "Eskillld20": 1378, + "Eskillan20": 1379, + "Eskillname21": 1380, + "Eskillsd21": 1381, + "Eskillld21": 1382, + "Eskillan21": 1383, + "Eskillname22": 1384, + "Eskillsd22": 1385, + "Eskillld22": 1386, + "Eskillan22": 1387, + "Eskillname23": 1388, + "Eskillsd23": 1389, + "Eskillld23": 1390, + "Eskillan23": 1391, + "Eskillname24": 1392, + "Eskillsd24": 1393, + "Eskillld24": 1394, + "Eskillan24": 1395, + "Eskillname25": 1396, + "Eskillsd25": 1397, + "Eskillld25": 1398, + "Eskillan25": 1399, + "Eskillname26": 1400, + "Eskillsd26": 1401, + "Eskillld26": 1402, + "Eskillan26": 1403, + "Eskillname27": 1404, + "Eskillsd27": 1405, + "Eskillld27": 1406, + "Eskillan27": 1407, + "Eskillname28": 1408, + "Eskillsd28": 1409, + "Eskillld28": 1410, + "Eskillan28": 1411, + "Eskillname29": 1412, + "Eskillsd29": 1413, + "Eskillld29": 1414, + "Eskillan29": 1415, + "Eskillname30": 1416, + "Eskillsd30": 1417, + "Eskillld30": 1418, + "Eskillan30": 1419, + "Eskillname31": 1420, + "Eskillsd31": 1421, + "Eskillld31": 1422, + "Eskillan31": 1423, + "Eskillname32": 1424, + "Eskillsd32": 1425, + "Eskillld32": 1426, + "Eskillan32": 1427, + "Eskillname33": 1428, + "Eskillsd33": 1429, + "Eskillld33": 1430, + "Eskillan33": 1431, + "Eskillname34": 1432, + "Eskillsd34": 1433, + "Eskillld34": 1434, + "Eskillan34": 1435, + "Eskillname35": 1436, + "Eskillsd35": 1437, + "Eskillld35": 1438, + "Eskillan35": 1439, + "Eskillname36": 1440, + "Eskillsd36": 1441, + "Eskillld36": 1442, + "Eskillan36": 1443, + "Eskillname37": 1444, + "Eskillsd37": 1445, + "Eskillld37": 1446, + "Eskillan37": 1447, + "Eskillname38": 1448, + "Eskillsd38": 1449, + "Eskillld38": 1450, + "Eskillan38": 1451, + "Eskillname39": 1452, + "Eskillsd39": 1453, + "Eskillld39": 1454, + "Eskillan39": 1455, + "Eskillname40": 1456, + "Eskillsd40": 1457, + "Eskillld40": 1458, + "Eskillan40": 1459, + "Eskillname41": 1460, + "Eskillsd41": 1461, + "Eskillld41": 1462, + "Eskillan41": 1463, + "Eskillname42": 1464, + "Eskillsd42": 1465, + "Eskillld42": 1466, + "Eskillan42": 1467, + "Eskillname43": 1468, + "Eskillsd43": 1469, + "Eskillld43": 1470, + "Eskillan43": 1471, + "Eskillname44": 1472, + "Eskillsd44": 1473, + "Eskillld44": 1474, + "Eskillan44": 1475, + "Eskillname45": 1476, + "Eskillsd45": 1477, + "Eskillld45": 1478, + "Eskillan45": 1479, + "Eskillname46": 1480, + "Eskillsd46": 1481, + "Eskillld46": 1482, + "Eskillan46": 1483, + "Eskillname47": 1484, + "Eskillsd47": 1485, + "Eskillld47": 1486, + "Eskillan47": 1487, + "Eskillname48": 1488, + "Eskillsd48": 1489, + "Eskillld48": 1490, + "Eskillan48": 1491, + "Eskillname49": 1492, + "Eskillsd49": 1493, + "Eskillld49": 1494, + "Eskillan49": 1495, + "Eskillname50": 1496, + "Eskillsd50": 1497, + "Eskillld50": 1498, + "Eskillan50": 1499, + "Eskillname51": 1500, + "Eskillsd51": 1501, + "Eskillld51": 1502, + "Eskillan51": 1503, + "Eskillname52": 1504, + "Eskillsd52": 1505, + "Eskillld52": 1506, + "Eskillan52": 1507, + "Eskillname53": 1508, + "Eskillsd53": 1509, + "Eskillld53": 1510, + "Eskillan53": 1511, + "Eskillname54": 1512, + "Eskillsd54": 1513, + "Eskillld54": 1514, + "Eskillan54": 1515, + "Eskillname55": 1516, + "Eskillsd55": 1517, + "Eskillld55": 1518, + "Eskillan55": 1519, + "Eskillname56": 1520, + "Eskillsd56": 1521, + "Eskillld56": 1522, + "Eskillan56": 1523, + "Eskillname57": 1524, + "Eskillsd57": 1525, + "Eskillld57": 1526, + "Eskillan57": 1527, + "Eskillname58": 1528, + "Eskillsd58": 1529, + "Eskillld58": 1530, + "Eskillan58": 1531, + "Eskillname59": 1532, + "Eskillsd59": 1533, + "Eskillld59": 1534, + "Eskillan59": 1535, + "ESkillHawk": 22278, + "ESkillSpikes": 22279, + "ESkillStars": 22280, + "ESkillWolf": 22281, + "ESkillWolves": 22282, + "ESkillShoots": 22283, + "ESkillTimes": 22284, + "ESkillSpikes2": 22285, + "ob1": 20281, + "ob2": 20282, + "ob3": 20283, + "ob4": 20284, + "ob5": 21778, + "ne1": 20332, + "ne2": 20333, + "ne3": 20334, + "ne4": 20335, + "ne5": 20336, + "dr1": 20320, + "dr2": 20318, + "dr3": 20319, + "dr4": 20317, + "dr5": 20321, + "as1": 20285, + "as2": 20286, + "as3": 20287, + "as4": 20288, + "as5": 20289, + "as6": 20290, + "as7": 20291, + "AmaOnly": 20426, + "SorOnly": 20427, + "NecOnly": 20428, + "PalOnly": 20429, + "BarOnly": 20430, + "DruOnly": 20431, + "AssOnly": 20432, + "WeaponDescH2H": 21258, + "Seige Tower": 22352, + "RotWalker": 22353, + "ReanimatedHorde": 22354, + "ProwlingDead": 22355, + "UnholyCorpse": 22356, + "DefiledWarrior": 22357, + "Seige Beast": 1580, + "CrushBiest": 22359, + "BloodBringer": 22360, + "GoreBearer": 22361, + "DeamonSteed": 22362, + "WailingSpirit": 22363, + "LifeSeeker": 22364, + "LifeStealer": 22365, + "DeathlyVisage": 22366, + "BoundSpirit": 22367, + "BanishedSoul": 22368, + "Deathexp": 22369, + "Minionexp": 22370, + "Slayerexp": 22371, + "IceBoar": 22372, + "FireBoar": 22373, + "HellSpawn": 22374, + "IceSpawn": 22375, + "GreaterHellSpawn": 22376, + "GreaterIceSpawn": 22377, + "FanaticMinion": 22378, + "BerserkSlayer": 22379, + "ConsumedFireBoar": 22380, + "ConsumedIceBoar": 22381, + "FrenziedHellSpawn": 22382, + "FrenziedIceSpawn": 22383, + "InsaneHellSpawn": 22384, + "InsaneIceSpawn": 22385, + "Succubusexp": 22386, + "VileTemptress": 22387, + "StygianHarlot": 22388, + "BlightWing": 1611, + "BloodWitch": 1612, + "Dominus": 22391, + "VileWitch": 22392, + "StygianFury": 22393, + "MageWing": 1616, + "HellWitch": 1617, + "OverSeer": 22396, + "Lasher": 22397, + "OverLord": 22398, + "BloodBoss": 22399, + "HellWhip": 22400, + "MinionSpawner": 22401, + "MinionSlayerSpawner": 22402, + "MinionIce/fireBoarSpawner": 22403, + "Minionice/hellSpawnSpawner": 22404, + "MinionGreaterIce/hellSpawnSpawner": 22405, + "Imp1": 22406, + "Imp2": 22407, + "Imp3": 22408, + "Imp4": 22409, + "Imp5": 22410, + "CapsJoinMenu4": 1633, + "CapsJoinMenu5": 1634, + "Guild 1": 1635, + "Guild 2": 1636, + "Guild 3": 1637, + "Guild 4": 1638, + "Guild 5": 1639, + "To Guild 5": 1640, + "To Guild 4": 1641, + "To Guild 3": 1642, + "To Guild 2": 1643, + "To Guild 1": 1644, + "CapsBnet9": 1645, + "CapsBnet10": 1646, + "CapsBnet11": 1647, + "CapsBnet12": 1648, + "CapsBnet13": 1649, + "CapsBnet14": 1650, + "CapsBnet15": 1651, + "CapsGuildName": 1652, + "CapsGuildTag": 1653, + "GuildText1": 1654, + "GuildText2": 1655, + "Ladder3": 1656, + "Ladder7": 1657, + "gmGuildTitle": 1658, + "gmGuildName": 1659, + "gmGuildTag": 1660, + "gmWWW": 1661, + "gmGuildCharter": 1662, + "gmGuildCurrentGolds": 1663, + "gmGuildNextLevel": 1664, + "gmGuildMaster": 1665, + "gmOfficer": 1666, + "gmName": 1667, + "gmClass": 1668, + "gmLevel": 1669, + "gmDonate": 1670, + "gmRemove": 1671, + "gmPal": 1672, + "gmSor": 1673, + "gmAma": 1674, + "gmNec": 1675, + "gmBar": 1676, + "gmChangeSym": 1677, + "gmChangeCharter": 1678, + "gmChangeWebLink": 1679, + "Guild Portal": 1680, + "createdguildsuccess": 1681, + "createdguildfailure": 1682, + "inviteguildsuccess": 1683, + "inviteguildfailure": 1684, + "inviteguildins": 1685, + "joinedguildsuccess": 1686, + "joinedguildfailure": 1687, + "quitguildsuccess": 1688, + "quitguildfailure": 1689, + "guildentererror": 1690, + "strGuildMasterKicked": 1691, + "strGuildPerk1": 1692, + "strGuildPerk2": 1693, + "strGuildPerk3": 1694, + "strGuildPerk4": 1695, + "strGuildPerk5": 1696, + "strGuildPerk6": 1697, + "strGuildGoldDonated": 1698, + "strGuildDonateGold": 1699, + "gmGuildCurrentGoldPopup": 1700, + "gmGuildNextLevelPopup": 1701, + "gmGuildDonateGoldPopup": 1702, + "Message Board": 1703, + "Trophy Case": 1704, + "Guild Vault": 1705, + "Steeg Stone": 1706, + "guildaccepticon": 1707, + "guildmsgtext": 1708, + "ScrollFormat": 1709, + "BookFormat": 1710, + "HiqualityFormat": 1711, + "LowqualityFormat": 1712, + "HerbFormat": 1713, + "MagicFormat": 1714, + "GemmedNormalName": 1715, + "BodyPartsFormat": 1716, + "PlayerBodyPartFormat": 1717, + "RareFormat": 1718, + "SetItemFormat": 1719, + "ChampionFormat": 1720, + "Monster1Format": 1721, + "Monster2Format": 1722, + "Low Quality": 1723, + "Damaged": 1724, + "Cracked": 1725, + "Crude": 20910, + "Hiquality": 1727, + "Gemmed": 1728, + "Resiliant": 1729, + "Sturdy": 1730, + "Strong": 1731, + "Glorious": 1732, + "Blessed": 1733, + "Saintly": 1734, + "Holy": 1735, + "Devious": 1736, + "Fortified": 1737, + "Urgent": 1738, + "Fleet": 1739, + "Muscular": 1740, + "Jagged": 1741, + "Deadly": 1742, + "Vicious": 1743, + "Brutal": 1744, + "Massive": 1745, + "Savage": 1746, + "Merciless": 1747, + "Vulpine": 1748, + "Swift": 1749, + "Artful": 1750, + "Skillful": 1751, + "Adroit": 1752, + "Tireless": 1753, + "Rugged": 1754, + "Bronze": 1755, + "Iron": 1756, + "Steel": 1757, + "Silver": 1758, + "Gold": 1759, + "Platinum": 1760, + "Meteoric": 1761, + "Sharp": 1762, + "Fine": 1763, + "Warrior's": 1764, + "Soldier's": 1765, + "Knight's": 1766, + "Lord's": 1767, + "King's": 1768, + "Howling": 1769, + "Fortuitous": 1770, + "Brilliant": 1771, + "Omniscient": 1772, + "Sage": 1773, + "Shrewd": 1774, + "Vivid": 1775, + "Glimmering": 1776, + "Glowing": 1777, + "Bright": 1778, + "Solar": 1779, + "Lizard's": 1780, + "Forceful": 1781, + "Snake's": 1782, + "Serpent's": 1783, + "Drake's": 1784, + "Dragon's": 1785, + "Wyrm's": 1786, + "Dazzling": 1787, + "Facinating": 1788, + "Prismatic": 1789, + "Azure": 1790, + "Lapis": 1791, + "Cobalt": 1792, + "Indigo": 1793, + "Sapphire": 1794, + "Cerulean": 1795, + "Red": 1796, + "Crimson": 1797, + "Burgundy": 1798, + "Garnet": 1799, + "Russet": 1800, + "Ruby": 1801, + "Vermilion": 1802, + "Orange": 1803, + "Ocher": 1804, + "Tangerine": 1805, + "Coral": 1806, + "Crackling": 1807, + "Amber": 1808, + "Forked": 1809, + "Green": 20905, + "Beryl": 1811, + "Jade": 1812, + "Viridian": 1813, + "Vital": 1814, + "Emerald": 1815, + "Enduring": 1816, + "Fletcher's": 1817, + "Archer's": 1818, + "Monk's": 1819, + "Priest's": 1820, + "Summoner's": 1821, + "Necromancer's": 1822, + "Angel's": 1823, + "Arch-Angel's": 1824, + "Slayer's": 1825, + "Berserker's": 2507, + "Kicking": 1827, + "Triumphant": 1828, + "Mighty": 1829, + "Energizing": 1830, + "Strengthening": 1831, + "Empowering": 1832, + "Brisk": 1833, + "Tough": 1834, + "Hardy": 1835, + "Robust": 1836, + "of Health": 1837, + "of Protection": 1838, + "of Absorption": 1839, + "of Warding": 1840, + "of the Sentinel": 1841, + "of Guarding": 1842, + "of Negation": 1843, + "of Piercing": 1844, + "of Bashing": 1845, + "of Puncturing": 1846, + "of Thorns": 1847, + "of Spikes": 1848, + "of Readiness": 1849, + "of Alacrity": 1850, + "of Swiftness": 1851, + "of Quickness": 1852, + "of Blocking": 1853, + "of Deflecting": 1854, + "of the Apprentice": 1855, + "of the Magus": 1856, + "of Frost": 1857, + "of the Glacier": 1858, + "of Warmth": 1859, + "of Flame": 1860, + "of Fire": 1861, + "of Burning": 1862, + "of Shock": 1863, + "of Lightning": 1864, + "of Thunder": 1865, + "of Craftsmanship": 1866, + "of Quality": 1867, + "of Maiming": 1868, + "of Slaying": 1869, + "of Gore": 1870, + "of Carnage": 1871, + "of Slaughter": 1872, + "of Worth": 1873, + "of Measure": 1874, + "of Excellence": 1875, + "of Performance": 1876, + "of Blight": 1877, + "of Venom": 1878, + "of Pestilence": 1879, + "of Dexterity": 1880, + "of Skill": 1881, + "of Accuracy": 1882, + "of Precision": 1883, + "of Perfection": 1884, + "of Balance": 1885, + "of Stability": 1886, + "of the Horse": 1887, + "of Regeneration": 1888, + "of Regrowth": 1889, + "of Vileness": 1890, + "of Greed": 1891, + "of Wealth": 1892, + "of Chance": 1893, + "of Fortune": 1894, + "of Energy": 1895, + "of the Mind": 1896, + "of Brilliance": 1897, + "of Sorcery": 1898, + "of Wizardry": 1899, + "of the Bear": 1900, + "of Light": 1901, + "of Radiance": 1902, + "of the Sun": 1903, + "of Life": 1904, + "of the Jackal": 1905, + "of the Fox": 1906, + "of the Wolf": 1907, + "of the Tiger": 1908, + "of the Mammoth": 1909, + "of the Colosuss": 1910, + "of the Leech": 1911, + "of the Locust": 1912, + "of the Bat": 1913, + "of the Vampire": 1914, + "of Defiance": 1915, + "of Remedy": 1916, + "of Amelioration": 1917, + "of Ice": 1918, + "of Simplicity": 1919, + "of Ease": 1920, + "of the Mule": 1921, + "of Strength": 1922, + "of Might": 1923, + "of the Ox": 1924, + "of the Giant": 1925, + "of the Titan": 1926, + "of Pacing": 1927, + "of Haste": 1928, + "of Speed": 1929, + "cap": 1930, + "skp": 1931, + "hlm": 1932, + "fhl": 1933, + "ghm": 1934, + "crn": 1935, + "msk": 1936, + "qui": 1937, + "lea": 1938, + "hla": 1939, + "stu": 1940, + "rng": 1941, + "scl": 1942, + "chn": 1943, + "brs": 1944, + "spl": 1945, + "plt": 1946, + "fld": 1947, + "gth": 1948, + "ful": 1949, + "aar": 1950, + "ltp": 1951, + "buc": 1952, + "sml": 1953, + "lrg": 1954, + "kit": 1955, + "tow": 1956, + "gts": 1957, + "lgl": 1958, + "vgl": 1959, + "mgl": 1960, + "tgl": 1961, + "hgl": 1962, + "lbt": 1963, + "vbt": 1964, + "mbt": 1965, + "tbt": 1966, + "hbt": 1967, + "lbl": 1968, + "vbl": 1969, + "mbl": 1970, + "tbl": 1971, + "hbl": 1972, + "bhm": 1973, + "bsh": 1974, + "spk": 1975, + "hax": 1976, + "axe": 1977, + "2ax": 1978, + "mpi": 1979, + "wax": 1980, + "lax": 1981, + "bax": 1982, + "btx": 1983, + "gax": 1984, + "gix": 1985, + "wnd": 1986, + "ywn": 1987, + "bwn": 1988, + "gwn": 1989, + "clb": 1990, + "scp": 1991, + "gsc": 1992, + "wsp": 1993, + "spc": 1994, + "mac": 1995, + "mst": 1996, + "fla": 1997, + "whm": 1998, + "mau": 1999, + "gma": 2000, + "ssd": 2001, + "scm": 2002, + "sbr": 2003, + "flc": 2004, + "crs": 2005, + "bsd": 2006, + "lsd": 2007, + "wsd": 2008, + "2hs": 2009, + "clm": 2010, + "gis": 2011, + "bsw": 2012, + "flb": 2013, + "gsd": 2014, + "dgr": 2015, + "dir": 2016, + "kri": 2017, + "bld": 2018, + "tkf": 2019, + "tax": 2020, + "bkf": 2021, + "bal": 2022, + "jav": 2023, + "pil": 2024, + "ssp": 2025, + "glv": 2026, + "tsp": 2027, + "spr": 2028, + "tri": 2029, + "brn": 2030, + "spt": 2031, + "pik": 2032, + "bar": 2033, + "vou": 2034, + "scy": 2035, + "pax": 2036, + "hal": 2037, + "wsc": 2038, + "sst": 2039, + "lst": 2040, + "cst": 2041, + "bst": 2042, + "wst": 2043, + "sbw": 2044, + "hbw": 2045, + "lbw": 2046, + "cbw": 2047, + "sbb": 2048, + "lbb": 2049, + "swb": 2050, + "lwb": 2051, + "lxb": 2052, + "mxb": 2053, + "hxb": 2054, + "rxb": 2055, + "xpk": 2056, + "xsh": 2057, + "xh9": 2058, + "zhb": 2059, + "ztb": 2060, + "zmb": 2061, + "zvb": 2062, + "zlb": 2063, + "xhb": 2064, + "xtb": 2065, + "xmb": 2066, + "xvb": 2067, + "xlb": 2068, + "xhg": 2069, + "xtg": 2070, + "xmg": 2071, + "xvg": 2072, + "xlg": 2073, + "xts": 2074, + "xow": 2075, + "xit": 2076, + "xrg": 2077, + "xml": 2078, + "xuc": 2079, + "xtp": 2080, + "xar": 2081, + "xul": 2082, + "xth": 2083, + "xld": 2084, + "xlt": 2085, + "xpl": 2086, + "xrs": 2087, + "xhn": 2088, + "xcl": 2089, + "xng": 2090, + "xtu": 2091, + "xla": 2092, + "xea": 2093, + "xui": 2094, + "xsk": 2095, + "xrn": 2096, + "xhm": 2097, + "xhl": 2098, + "xlm": 2099, + "xkp": 2100, + "xap": 2101, + "8rx": 2102, + "8hx": 2103, + "8mx": 2104, + "8lx": 2105, + "8lw": 2106, + "8sw": 2107, + "8l8": 2108, + "8s8": 2109, + "8cb": 2110, + "8lb": 2111, + "8hb": 2112, + "8sb": 2113, + "8ws": 2114, + "8bs": 2115, + "8cs": 2116, + "8ls": 2117, + "8ss": 2118, + "9wc": 2119, + "9h9": 2120, + "9pa": 2121, + "9s8": 2122, + "9vo": 2123, + "9b7": 2124, + "9p9": 2125, + "9st": 2126, + "9br": 2127, + "9tr": 2128, + "9sr": 2129, + "9ts": 2130, + "9gl": 2131, + "9s9": 2132, + "9pi": 2133, + "9ja": 2134, + "9b8": 2135, + "9bk": 2136, + "9ta": 2137, + "9tk": 2138, + "9bl": 2139, + "9kr": 2140, + "9di": 2141, + "9dg": 2142, + "9gd": 2143, + "9fb": 2144, + "9gs": 2145, + "9cm": 2146, + "92h": 2147, + "9wd": 2148, + "9ls": 2149, + "9bs": 2150, + "9cr": 2151, + "9fc": 2152, + "9sb": 2153, + "9sm": 2154, + "9ss": 2155, + "9gm": 2156, + "9m9": 2157, + "9wh": 2158, + "9fl": 2159, + "9mt": 2160, + "9ma": 2161, + "9sp": 2162, + "9ws": 2163, + "9qs": 2164, + "9sc": 2165, + "9cl": 2166, + "9gw": 2167, + "9bw": 2168, + "9yw": 2169, + "9wn": 2170, + "9gi": 2171, + "9ga": 2172, + "9bt": 2173, + "9ba": 2174, + "9la": 2175, + "9wa": 2176, + "9mp": 2177, + "92a": 2178, + "9ax": 2179, + "9ha": 2180, + "9b9": 2181, + "gpl": 2182, + "opl": 2183, + "gpm": 2184, + "opm": 2185, + "gps": 2186, + "ops": 2187, + "gidbinn": 2188, + "g33": 2189, + "d33": 2190, + "leg": 2191, + "Malus": 2192, + "hdm": 2193, + "hfh": 2194, + "hst": 2195, + "msf": 2196, + "orifice": 2197, + "elx": 2198, + "tbk": 2199, + "tsc": 2200, + "ibk": 2201, + "isc": 2202, + "RightClicktoUse": 2203, + "RightClicktoOpen": 2204, + "RightClicktoRead": 2205, + "InsertScrolls": 2206, + "vps": 2207, + "yps": 2208, + "rvs": 2209, + "rvl": 2210, + "wms": 2211, + "amu": 2212, + "vip": 2213, + "rin": 2214, + "gld": 2215, + "bks": 2216, + "bkd": 2217, + "aqv": 2218, + "tch": 2219, + "cqv": 2220, + "Key": 2221, + "key": 2222, + "luv": 2223, + "xyz": 2224, + "shrine": 2225, + "teleport pad": 2226, + "j34": 2227, + "g34": 2228, + "bbb": 2229, + "LamTome": 2230, + "box": 2231, + "tr1": 2232, + "mss": 2233, + "ass": 2234, + "ear": 2235, + "gcv": 2236, + "gfv": 2237, + "gsv": 2238, + "gzv": 2239, + "gpv": 2240, + "gcy": 2241, + "gfy": 2242, + "gsy": 2243, + "gly": 2244, + "gpy": 2245, + "gcb": 2246, + "gfb": 2247, + "gsb": 2248, + "glb": 2249, + "gpb": 2250, + "gcg": 2251, + "gfg": 2252, + "glg": 2253, + "gsg": 2254, + "gpg": 2255, + "gcr": 2256, + "gfr": 2257, + "gsr": 2258, + "glr": 2259, + "gpr": 2260, + "gcw": 2261, + "gfw": 2262, + "gsw": 2263, + "glw": 2264, + "gpw": 2265, + "hp1": 2266, + "hp2": 2267, + "hp3": 2268, + "hp4": 2269, + "hp5": 2270, + "mp1": 2271, + "mp2": 2272, + "mp3": 2273, + "mp4": 2274, + "mp5": 2275, + "hrb": 20434, + "skc": 2277, + "skf": 2278, + "sku": 2279, + "skl": 2280, + "skz": 2281, + "Beast": 2282, + "Eagle": 2283, + "Raven": 2284, + "Viper": 2285, + "GhoulRI": 2286, + "Skull": 2287, + "Blood": 2288, + "Dread": 2289, + "Doom": 2290, + "Grim": 2291, + "Bone": 2292, + "Death": 2293, + "Shadow": 2294, + "Storm": 2295, + "Rune": 2296, + "PlagueRI": 2297, + "Stone": 2298, + "Wraith": 2989, + "Spirit": 2300, + "Demon": 2301, + "Cruel": 2302, + "Empyrion": 2303, + "Bramble": 2304, + "Pain": 2305, + "Loath": 2306, + "Glyph": 2307, + "Imp": 2308, + "Fiend": 2309, + "Hailstone": 2310, + "Gale": 2311, + "Dire": 2312, + "Soul": 2313, + "Brimstone": 2314, + "Corpse": 2315, + "Carrion": 2316, + "Holocaust": 2317, + "Havoc": 2318, + "Bitter": 2319, + "Entropy": 2320, + "Chaos": 2321, + "Order": 2322, + "Rift": 2323, + "Corruption": 2324, + "bite": 2325, + "scratch": 2326, + "scalpel": 2327, + "fang": 2328, + "gutter": 2329, + "thirst": 2330, + "razor": 2331, + "scythe": 2332, + "edge": 2333, + "saw": 2334, + "splitter": 2335, + "cleaver": 2336, + "sever": 2337, + "sunder": 2338, + "rend": 2339, + "mangler": 2340, + "slayer": 2341, + "reaver": 2342, + "Spawn": 2343, + "gnash": 2344, + "star": 2345, + "blow": 2346, + "smasher": 2347, + "Bane": 2348, + "crusher": 2349, + "breaker": 2350, + "grinder": 2351, + "crack": 2352, + "mallet": 2353, + "knell": 2354, + "lance": 2355, + "spike": 2356, + "impaler": 2357, + "skewer": 2358, + "prod": 2359, + "scourge": 2360, + "wand": 2361, + "wrack": 2362, + "barb": 2363, + "needle": 2364, + "dart": 2365, + "bolt": 2366, + "quarrel": 2367, + "fletch": 2368, + "flight": 2369, + "nock": 2370, + "horn": 2371, + "stinger": 2372, + "quill": 2373, + "goad": 2374, + "branch": 2375, + "spire": 2376, + "song": 2377, + "call": 2378, + "cry": 2379, + "spell": 2380, + "chant": 2381, + "weaver": 2382, + "gnarl": 2383, + "visage": 2384, + "crest": 2385, + "circlet": 2386, + "veil": 2387, + "hood": 2388, + "mask": 2389, + "brow": 2390, + "casque": 2391, + "visor": 2392, + "cowl": 2393, + "hide": 2394, + "Pelt": 2395, + "carapace": 2396, + "coat": 2397, + "wrap": 2398, + "suit": 2399, + "cloak": 2400, + "shroud": 2401, + "jack": 2402, + "mantle": 2403, + "guard": 2404, + "badge": 2405, + "rock": 2406, + "aegis": 2407, + "ward": 2408, + "tower": 2409, + "shield": 2410, + "wing": 2411, + "mark": 2412, + "emblem": 2413, + "hand": 2414, + "fist": 2415, + "claw": 2416, + "clutches": 2417, + "grip": 2418, + "grasp": 2419, + "hold": 2420, + "touch": 2421, + "finger": 2422, + "knuckle": 2423, + "shank": 2424, + "spur": 2425, + "tread": 2426, + "stalker": 2427, + "greave": 2428, + "blazer": 2429, + "nails": 2430, + "trample": 2431, + "Brogues": 2432, + "track": 2433, + "slippers": 2434, + "clasp": 2435, + "buckle": 2436, + "harness": 2437, + "lock": 2438, + "fringe": 2439, + "winding": 2440, + "chain": 2441, + "strap": 2442, + "lash": 2443, + "cord": 2444, + "knot": 2445, + "circle": 2446, + "loop": 2447, + "eye": 2448, + "turn": 2449, + "spiral": 2450, + "coil": 2451, + "gyre": 2452, + "band": 2453, + "whorl": 2454, + "talisman": 2455, + "heart": 2456, + "noose": 2457, + "necklace": 2458, + "collar": 2459, + "beads": 2460, + "torc": 2461, + "gorget": 2462, + "scarab": 2463, + "wood": 2464, + "brand": 2465, + "bludgeon": 2466, + "cudgel": 2467, + "loom": 2468, + "harp": 2469, + "master": 2470, + "barRI": 2471, + "hew": 2472, + "crook": 2473, + "mar": 2474, + "shell": 2475, + "stake": 2476, + "picket": 2477, + "pale": 2478, + "flange": 2479, + "Civerb's Vestments": 2480, + "Hsarus' Trim": 2481, + "Cleglaw's Brace": 2482, + "Iratha's Finery": 2483, + "Isenhart's Armory": 2484, + "Vidala's Rig": 2485, + "Milabrega's Regalia": 2486, + "Cathan's Traps": 2487, + "Tancred's Battlegear": 2488, + "Sigon's Complete Steel": 2489, + "Infernal Tools": 2490, + "Berserker's Garb": 2491, + "Death's Disguise": 2492, + "Angelical Raiment": 2493, + "Arctic Gear": 2494, + "Arcanna's Tricks": 2495, + "Civerb's": 2496, + "Hsarus'\tHsaru's": 2497, + "Cleglaw's": 2498, + "Iratha's": 2499, + "Isenhart's": 2500, + "Vidala's": 2501, + "Milabrega's": 2502, + "Cathan's": 2503, + "Tancred's": 2504, + "Sigon's": 2505, + "Infernal": 2506, + "Death's": 2508, + "Angelical": 2509, + "Arctic": 2510, + "Arcanna's": 2511, + "Ward": 2512, + "Iron Heel": 2513, + "Tooth": 2514, + "Collar": 2515, + "Lightbrand": 2516, + "Barb": 2517, + "Orb": 2518, + "Rule": 2519, + "Crowbill": 2520, + "Visor": 2521, + "Cranium": 2522, + "Headgear": 2523, + "Hand": 2524, + "Sickle": 2525, + "Horn": 2526, + "Sign": 2527, + "Icon": 2528, + "Iron Fist": 2529, + "Claw": 2530, + "Cuff": 2531, + "Parry": 2532, + "Fetlock": 2533, + "Rod": 2534, + "Mesh": 2535, + "Spine": 2536, + "Shelter": 2537, + "Torch": 2538, + "Hauberk": 2539, + "Guard": 2540, + "Mantle": 2541, + "Furs": 2542, + "Deathwand": 2543, + "CudgelSI3S": 2544, + "Iron Stay": 2545, + "Pincers": 2546, + "Coil": 2547, + "Case": 2548, + "Ambush": 2549, + "Diadem": 2550, + "Visage": 2551, + "Hobnails": 2552, + "Gage": 2553, + "SignSI3S": 2554, + "Hatchet": 2555, + "Touch": 2556, + "Halo": 2557, + "Binding": 2558, + "Head": 2559, + "Horns": 2560, + "Snare": 2561, + "Robe": 2562, + "Sigil": 2563, + "Weird": 2564, + "Sabot": 2565, + "Wings": 2566, + "Mitts": 2567, + "Flesh": 2568, + "Cord": 2569, + "Seal": 2570, + "SkullSI5S": 2571, + "Wrap": 2572, + "GuardSI6S": 2573, + "The Gnasher": 2574, + "Deathspade": 2575, + "Bladebone": 2576, + "Mindrend": 2577, + "Rakescar": 2578, + "Fechmars Axe": 2579, + "Goreshovel": 2580, + "The Chieftan": 2581, + "Brainhew": 2582, + "The Humongous": 2583, + "Iros Torch": 2584, + "Maelstromwrath": 2585, + "Gravenspine": 2586, + "Umes Lament": 2587, + "Felloak": 2588, + "Knell Striker": 2589, + "Rusthandle": 2590, + "Stormeye": 2591, + "Stoutnail": 2592, + "Crushflange": 2593, + "Bloodrise": 2594, + "The Generals Tan Do Li Ga": 2595, + "Ironstone": 2596, + "Bonesob": 2597, + "Steeldriver": 2598, + "Rixots Keen": 2599, + "Blood Crescent": 2600, + "Krintizs Skewer": 2601, + "Gleamscythe": 2602, + "Azurewrath": 2603, + "Griswolds Edge": 2604, + "Hellplague": 2605, + "Culwens Point": 2606, + "Shadowfang": 2607, + "Soulflay": 2608, + "Kinemils Awl": 2609, + "Blacktongue": 2610, + "Ripsaw": 2611, + "The Patriarch": 2612, + "Gull": 2613, + "The Diggler": 2614, + "The Jade Tan Do": 2615, + "Irices Shard": 2616, + "The Dragon Chang": 2617, + "Razortine": 2618, + "Bloodthief": 2619, + "Lance of Yaggai": 2620, + "The Tannr Gorerod": 2621, + "Dimoaks Hew": 2622, + "Steelgoad": 2623, + "Soul Harvest": 2624, + "The Battlebranch": 2625, + "Woestave": 2626, + "The Grim Reaper": 2627, + "Bane Ash": 2628, + "Serpent Lord": 2629, + "Lazarus Spire": 2630, + "The Salamander": 2631, + "The Iron Jang Bong": 2632, + "Pluckeye": 2633, + "Witherstring": 2634, + "Rimeraven": 2635, + "Piercerib": 2636, + "Pullspite": 2637, + "Wizendraw": 2638, + "Hellclap": 2639, + "Blastbark": 2640, + "Leadcrow": 2641, + "Ichorsting": 2642, + "Hellcast": 2643, + "Doomspittle": 2644, + "War Bonnet": 2645, + "Tarnhelm": 2646, + "Coif of Glory": 2647, + "Duskdeep": 2648, + "Wormskull": 2649, + "Howltusk": 2650, + "Undead Crown": 2651, + "The Face of Horror": 2652, + "Greyform": 2653, + "Blinkbats Form": 2654, + "The Centurion": 2655, + "Twitchthroe": 2656, + "Darkglow": 2657, + "Hawkmail": 2658, + "Sparking Mail": 2659, + "Venomsward": 2660, + "Iceblink": 2661, + "Boneflesh": 2662, + "Rockfleece": 2663, + "Rattlecage": 2664, + "Goldskin": 2665, + "Victors Silk": 2666, + "Heavenly Garb": 2667, + "Pelta Lunata": 2668, + "Umbral Disk": 2669, + "Stormguild": 2670, + "Wall of the Eyeless": 2671, + "Swordback Hold": 2672, + "Steelclash": 2673, + "Bverrit Keep": 2674, + "The Ward": 2675, + "The Hand of Broc": 2676, + "Bloodfist": 2677, + "Chance Guards": 2678, + "Magefist": 2679, + "Frostburn": 2680, + "Hotspur": 2681, + "Gorefoot": 2682, + "Treads of Cthon": 2683, + "Goblin Toe": 2684, + "Tearhaunch": 2685, + "Lenyms Cord": 2686, + "Snakecord": 2687, + "Nightsmoke": 2688, + "Goldwrap": 2689, + "Bladebuckle": 2690, + "Nokozan Relic": 2691, + "The Eye of Etlich": 2692, + "The Mahim-Oak Curio": 2693, + "Nagelring": 2694, + "Manald Heal": 2695, + "Gorgethroat": 2696, + "Amulet of the Viper": 2697, + "Staff of Kings": 2698, + "Horadric Staff": 2699, + "Hell Forge Hammer": 2700, + "The Stone of Jordan": 2701, + "GloomUM": 2702, + "Gray": 2703, + "DireUM": 2704, + "Black": 2705, + "ShadowUM": 2706, + "Haze": 2707, + "Wind": 2708, + "StormUM": 2709, + "Warp": 2710, + "Night": 2711, + "Moon": 2712, + "Star": 2713, + "Pit": 2714, + "Fire": 2715, + "Cold": 2716, + "Seethe": 2717, + "SharpUM": 2718, + "AshUM": 2719, + "Blade": 2720, + "SteelUM": 2721, + "StoneUM": 2722, + "Rust": 2723, + "Mold": 2724, + "Blight": 2725, + "Plague": 2726, + "Rot": 2727, + "Ooze": 2728, + "Puke": 2729, + "Snot": 2730, + "Bile": 2731, + "BloodUM": 2732, + "Pulse": 2733, + "Gut": 2734, + "Gore": 2735, + "FleshUM": 2736, + "BoneUM": 2737, + "SpineUM": 2738, + "Mind": 2739, + "SpiritUM": 2740, + "SoulUM": 2741, + "Wrath": 2742, + "GriefUM": 2743, + "Foul": 2744, + "Vile": 2745, + "Sin": 2746, + "ChaosUM": 2747, + "DreadUM": 2748, + "DoomUM": 2749, + "BaneUM": 2750, + "DeathUM": 2751, + "ViperUM": 2752, + "Dragon": 2753, + "Devil": 2754, + "touchUM": 2755, + "spellUM": 2756, + "feast": 2757, + "wound": 2758, + "grin": 2759, + "maim": 2760, + "hack": 2761, + "biteUM": 2762, + "rendUM": 2763, + "burn": 2764, + "rip": 2765, + "kill": 2766, + "callUM": 2767, + "vex": 2768, + "jade": 2769, + "web": 2770, + "shieldUM": 2771, + "KillerUM": 2772, + "RazorUM": 2773, + "drinker": 2774, + "shifter": 2775, + "crawler": 2776, + "dancer": 2777, + "bender": 2778, + "weaverUM": 2779, + "eater": 2780, + "widow": 2781, + "maggot": 2782, + "spawn": 2783, + "wight": 2784, + "GrumbleUM": 2785, + "GrowlerUM": 2786, + "SnarlUM": 2787, + "wolf": 2788, + "crow": 2789, + "raven": 2790, + "hawk": 2791, + "cloud": 2792, + "BangUM": 2793, + "head": 2794, + "skullUM": 2795, + "browUM": 2796, + "eyeUM": 2797, + "maw": 2798, + "tongue": 2799, + "fangUM": 2800, + "hornUM": 2801, + "thorn": 2802, + "clawUM": 2803, + "fistUM": 2804, + "heartUM": 2805, + "shankUM": 2806, + "skinUM": 2807, + "wingUM": 2808, + "pox": 2809, + "fester": 2810, + "blister": 3291, + "pus": 2812, + "SlimeUM": 2813, + "drool": 2814, + "froth": 2815, + "sludge": 2816, + "venom": 2817, + "poison": 2818, + "break": 2819, + "shard": 2820, + "flame": 2821, + "maul": 2822, + "thirstUM": 2823, + "lust": 2824, + "the Hammer": 2825, + "the Axe": 2826, + "the Sharp": 2827, + "the Jagged": 2828, + "the Flayer": 2829, + "the Slasher": 2830, + "the Impaler": 2831, + "the Hunter": 2832, + "the Slayer": 2833, + "the Mauler": 2834, + "the Destroyer": 2835, + "theQuick": 2836, + "the Witch": 2837, + "the Mad": 2838, + "the Wraith": 2839, + "the Shade": 2840, + "the Dead": 2841, + "the Unholy": 2842, + "the Howler": 2843, + "the Grim": 2844, + "the Dark": 2845, + "the Tainted": 2846, + "the Unclean": 2847, + "the Hungry": 2848, + "the Cold": 2849, + "The Cow King": 2850, + "Grand Vizier of Chaos": 2851, + "Lord De Seis": 2852, + "Infector of Souls": 2853, + "Riftwraith the Cannibal": 2854, + "Taintbreeder": 2855, + "The Tormentor": 2856, + "Winged Death": 2857, + "Maffer Dragonhand": 2858, + "Wyand Voidfinger": 2859, + "Toorc Icefist": 2860, + "Bremm Sparkfist": 2861, + "Geleb Flamefinger": 2862, + "Ismail Vilehand": 2863, + "Icehawk Riftwing": 2864, + "Sarina the Battlemaid": 2865, + "Stormtree": 2866, + "Witch Doctor Endugu": 2867, + "Web Mage the Burning": 2868, + "Bishibosh": 2869, + "Bonebreak": 2870, + "Coldcrow": 2871, + "Rakanishu": 2872, + "Treehead WoodFist": 2873, + "Griswold": 2874, + "The Countess": 2875, + "Pitspawn Fouldog": 2876, + "Flamespike the Crawler": 2877, + "Boneash": 2878, + "Radament": 2879, + "Bloodwitch the Wild": 2880, + "Fangskin": 2881, + "Beetleburst": 2882, + "Leatherarm": 2883, + "Coldworm the Burrower": 2884, + "Fire Eye": 2885, + "Dark Elder": 2886, + "The Summoner": 2887, + "Ancient Kaa the Soulless": 2888, + "The Smith": 2889, + "DeckardCain": 2890, + "Gheed": 2891, + "Akara": 2892, + "Kashya": 2893, + "Charsi": 2894, + "Wariv": 2895, + "Warriv": 2896, + "Rogue": 2897, + "StygianDoll": 2898, + "SoulKiller": 2899, + "Flayer": 2900, + "Fetish": 2901, + "RatMan": 2902, + "Undead StygianDoll": 2903, + "Undead SoulKiller": 2904, + "Undead Flayer": 2905, + "Undead Fetish": 2906, + "Undead RatMan": 2907, + "DarkFamiliar": 2908, + "BloodDiver": 2909, + "Gloombat": 2910, + "DesertWing": 2911, + "Banished": 2912, + "BloodLord": 2913, + "DarkLord": 2914, + "NightLord": 2915, + "GhoulLord": 2916, + "Spikefist": 2917, + "Thrasher": 2918, + "BrambleHulk": 2919, + "ThornedHulk": 2920, + "SpiderMagus": 2921, + "FlameSpider": 2922, + "PoisonSpinner": 2923, + "SandFisher": 2924, + "Arach": 2925, + "BloodWing": 2926, + "BloodHook": 2927, + "Feeder": 2928, + "Sucker": 2929, + "WingedNightmare": 2930, + "HellBuzzard": 2931, + "UndeadScavenger": 2932, + "CarrionBird": 2933, + "Unraveler": 2934, + "Guardian": 2935, + "HollowOne": 2936, + "Horadrim Ancient": 2937, + "AlbinoRoach": 2938, + "SteelWeevil": 2939, + "Scarab": 2940, + "SandWarrior": 2941, + "DungSoldier": 2942, + "HellSwarm": 2943, + "PlagueBugs": 2944, + "BlackLocusts": 2945, + "Itchies": 2946, + "HellCat": 2947, + "NightTiger": 2948, + "SaberCat": 2949, + "Huntress": 2950, + "RazorPitDemon": 2951, + "TreeLurker": 2952, + "CaveLeaper": 2953, + "TombCreeper": 2954, + "SandLeaper": 2955, + "TombViper": 2956, + "PitViper": 2957, + "Salamander": 2958, + "ClawViper": 2959, + "SerpentMagus": 2960, + "WorldKiller": 2961, + "GiantLamprey": 2962, + "Devourer": 2963, + "RockWorm": 2964, + "SandMaggot": 2965, + "JungleUrchin": 2966, + "RazorSpine": 2967, + "ThornBeast": 2968, + "SpikeFiend": 2969, + "QuillRat": 2970, + "HellClan": 2971, + "MoonClan": 2972, + "NightClan": 2973, + "DeathClan": 2974, + "BloodClan": 2975, + "TempleGuard": 2976, + "DoomApe": 2977, + "JungleHunter": 2978, + "RockDweller": 2979, + "DuneBeast": 2980, + "FleshHunter": 2981, + "BlackRogue": 2982, + "DarkStalker": 2983, + "VileHunter": 2984, + "DarkHunter": 2985, + "DarkShape": 2986, + "Apparition": 2987, + "Specter": 2988, + "Ghost": 2990, + "Assailant": 2991, + "Infidel": 2992, + "Invader": 2993, + "Marauder": 2994, + "SandRaider": 2995, + "GargantuanBeast": 2996, + "WailingBeast": 2997, + "Yeti": 2998, + "Crusher": 2999, + "Brute": 3000, + "CloudStalker": 3001, + "BlackVulture": 3002, + "BlackRaptor": 3003, + "BloodHawk": 3004, + "FoulCrow": 3005, + "PlagueBearer": 3006, + "Ghoul": 3007, + "DrownedCarcass": 3008, + "HungryDead": 3009, + "Zombie": 3010, + "Skeleton": 3011, + "Horror": 3012, + "Returned": 3013, + "BurningDead": 3014, + "BoneWarrior": 3015, + "Damned": 3016, + "Disfigured": 3017, + "Misshapen": 3018, + "Tainted": 3019, + "Afflicted": 3020, + "Andariel": 3021, + "Natalya": 3022, + "Drognan": 3023, + "Atma": 3024, + "Fara": 3025, + "Lysander": 3026, + "Jerhyn": 3027, + "jerhyn": 3028, + "Geglash": 3029, + "Elzix": 3030, + "Greiz": 3031, + "Meshif": 3032, + "Camel": 3033, + "Cadaver": 3034, + "PreservedDead": 3035, + "Embalmed": 3036, + "DriedCorpse": 3037, + "Decayed": 3038, + "Urdar": 3039, + "Mauler": 3040, + "Gorbelly": 3041, + "Blunderbore": 3042, + "WorldKillerYoung": 3043, + "GiantLampreyYoung": 3044, + "DevourerYoung": 3045, + "RockWormYoung": 3046, + "SandMaggotYoung": 3047, + "WorldKillerEgg": 3048, + "GiantLampreyEgg": 3049, + "DevourerEgg": 3050, + "RockWormEgg": 3051, + "SandMaggotEgg": 3052, + "Maggot": 3053, + "Duriel": 3054, + "BloodHawkNest": 3055, + "FlyingScimitar": 3056, + "CloudStalkerNest": 3057, + "BlackVultureNest": 3058, + "FoulCrowNest": 3059, + "Diablo": 3060, + "Baal": 3061, + "Mephisto": 3062, + "Cantor": 3063, + "Heirophant": 3064, + "Sexton": 3065, + "Zealot": 3066, + "Faithful": 3067, + "Zakarumite": 3068, + "BlackSoul": 3069, + "BurningSoul": 3070, + "SwampGhost": 3071, + "Gloam": 3072, + "WarpedShaman": 3073, + "DarkShaman": 3074, + "DevilkinShaman": 3075, + "CarverShaman": 3076, + "FallenShaman": 3077, + "WarpedFallen": 3078, + "DarkOne": 3079, + "Devilkin": 3080, + "Carver": 3081, + "Fallen": 3082, + "ReturnedArcher": 3083, + "HorrorArcher": 3084, + "BurningDeadArcher": 3085, + "BoneArcher": 3086, + "CorpseArcher": 3087, + "SkeletonArcher": 3088, + "FleshLancer": 3089, + "BlackLancer": 3090, + "DarkLancer": 3091, + "VileLancer": 3092, + "DarkSpearwoman": 3093, + "FleshArcher": 3094, + "BlackArcher": 3095, + "DarkRanger": 3096, + "VileArcher": 3097, + "DarkArcher": 3098, + "Summoner": 3099, + "StygianDollShaman": 3100, + "SoulKillerShaman": 3101, + "FlayerShaman": 3102, + "FetishShaman": 3103, + "RatManShaman": 3104, + "HorrorMage": 3105, + "BurningDeadMage": 3106, + "BoneMage": 3107, + "CorpseMage": 3108, + "ReturnedMage": 3109, + "GargoyleTrap": 3110, + "Bloodraven": 3111, + "navi": 3112, + "Kaelan": 3113, + "meshif": 3114, + "StygianWatcherHead": 3115, + "RiverStalkerHead": 3116, + "WaterWatcherHead": 3117, + "StygianWatcherLimb": 3118, + "RiverStalkerLimb": 3119, + "WaterWatcherLimb": 3120, + "NightMarauder": 3121, + "FireGolem": 3122, + "IronGolem": 3123, + "BloodGolem": 3124, + "ClayGolem": 3125, + "WorldKillerQueen": 3126, + "GiantLampreyQueen": 3127, + "DevourerQueen": 3128, + "RockWormQueen": 3129, + "SandMaggotQueen": 3130, + "Slime Prince": 3131, + "Bog Creature": 3132, + "Swamp Dweller": 3133, + "GiantUrchin": 3134, + "RazorBeast": 3135, + "ThornBrute": 3136, + "SpikeGiant": 3137, + "QuillBear": 3138, + "Council Member": 3139, + "youngdiablo": 3140, + "darkwanderer": 3141, + "HellSlinger": 3142, + "NightSlinger": 3143, + "SpearCat": 3144, + "Slinger": 3145, + "FireTower": 3146, + "LightningSpire": 3147, + "PitLord": 3148, + "Balrog": 3149, + "VenomLord": 3150, + "Iron Wolf": 3151, + "InvisoSpawner": 3152, + "OblivionKnight": 3153, + "Mage": 3154, + "AbyssKnight": 3155, + "Fighter Mage": 3156, + "DoomKnight": 3157, + "Fighter": 3158, + "MawFiend": 3159, + "CorpseSpitter": 3160, + "Corpulent": 3161, + "StormCaster": 3162, + "Strangler": 3163, + "Groper": 3164, + "GrotesqueWyrm": 3165, + "StygianDog": 3166, + "FleshBeast": 3167, + "Grotesque": 3168, + "StygianHag": 3169, + "FleshSpawner": 3170, + "RogueScout": 3171, + "BloodWingNest": 3172, + "BloodHookNest": 3173, + "FeederNest": 3174, + "SuckerNest": 3175, + "NecroMage": 3176, + "NecroSkeleton": 3177, + "TrappedSoul": 3178, + "Valkyrie": 3179, + "Dopplezon": 3180, + "Raises Fetishes": 3181, + "Raises Undead": 3182, + "Lays Eggs": 3183, + "Raises Fallen": 3184, + "heals Zealots and Cantors": 3185, + "drains mana and stamina": 3186, + "drains mana": 3187, + "drains stamina": 3188, + "stun attack": 3189, + "eats and spits corspes": 3190, + "homing missiles": 3191, + "raises Stygian Dolls": 3192, + "raises Soul Killers": 3193, + "raises Flayers": 3194, + "raises Fetishes": 3195, + "raises Ratmen": 3196, + "steals life": 3197, + "raises undead": 3198, + "raises Dark Ones": 3199, + "raises Devilkin": 3200, + "raises Carvers": 3201, + "raises Fallen": 3202, + "raises Warped Fallen": 3203, + "shocking hit": 3204, + "uniquextrastrong": 3205, + "uniqueextrafast": 3206, + "uniquecursed": 3207, + "uniquemagicresistance": 3208, + "uniquefireenchanted": 3209, + "monsteruniqueprop1": 3210, + "monsteruniqueprop2": 3211, + "monsteruniqueprop3": 3212, + "monsteruniqueprop4": 3213, + "monsteruniqueprop5": 3214, + "monsteruniqueprop6": 3215, + "monsteruniqueprop7": 3216, + "monsteruniqueprop8": 3217, + "monsteruniqueprop9": 3218, + "This Cow Bites": 3219, + "Champion": 3220, + "minion": 3221, + "Barrel": 3222, + "Lever1": 3223, + "BarrelEx": 3224, + "Door": 3225, + "Portal": 3226, + "ODoor": 3227, + "BlockedDoor": 3228, + "LockedDoor": 3229, + "StoneAlpha": 3230, + "StoneBeta": 3231, + "StoneDelta": 3232, + "StoneGamma": 3233, + "StoneLambda": 3234, + "StoneTheta": 3235, + "Crate": 3236, + "Casket": 3237, + "Cabinet": 3238, + "Vase": 3239, + "Inifuss": 3240, + "corpse": 3241, + "RogueCorpse": 3242, + "CorpseOnStick": 3243, + "TowerTome": 3244, + "Gibbet": 3245, + "MummyGenerator": 3246, + "ArmorStand": 3247, + "WeaponRack": 3248, + "Sarcophagus": 3249, + "Trap Door": 3250, + "LargeUrn": 3251, + "CanopicJar": 3252, + "Obelisk": 3253, + "HoleAnim": 3254, + "Shrine": 3255, + "Urn": 3256, + "Waypoint": 22526, + "Well": 3258, + "bag": 3259, + "Chest": 3260, + "chest": 3261, + "lockedchest": 3262, + "HorazonsJournal": 3263, + "templeshrine": 3264, + "stair": 3265, + "coffin": 3266, + "bookshelf": 3267, + "loose boulder": 3268, + "loose rock": 3269, + "hollow log": 3270, + "hiding spot": 3271, + "fire": 3328, + "Chest3": 3273, + "hidden stash": 3274, + "GuardCorpse": 3275, + "bowl": 3276, + "jug": 3277, + "AmbientSound": 3278, + "ratnest": 3279, + "burning body": 3280, + "well": 22525, + "door": 3282, + "skeleton": 3283, + "skullpile": 3284, + "cocoon": 3285, + "gidbinn altar": 3286, + "cowa": 3287, + "manashrine": 3288, + "bed": 3289, + "ratchest": 3290, + "bank": 3292, + "goo pile": 3293, + "holyshrine": 3294, + "teleportation pad": 3295, + "ratchest-r": 3296, + "skull pile": 3297, + "body": 3298, + "hell bridge": 3299, + "compellingorb": 3300, + "basket": 3301, + "Basket": 3302, + "RockPIle": 3303, + "Tome": 3304, + "dead body": 3305, + "eunuch": 3306, + "dead guard": 3307, + "portal": 3308, + "sarcophagus": 3309, + "dead villager": 3310, + "sewer lever": 3311, + "sewer stairs": 3312, + "magic shrine": 3313, + "wirt's body": 3314, + "stash": 3315, + "guyq": 3316, + "taintedsunaltar": 3317, + "Hellforge": 3318, + "Corpsefire": 3319, + "fissure": 3320, + "BoneChest": 3321, + "casket": 3322, + "HungSkeleton": 3323, + "pillar": 3324, + "Hydra": 3325, + "Turret": 3326, + "a trap": 3327, + "cost": 3329, + "Repair": 3330, + "Sell": 3331, + "Identify": 3332, + "priceless": 3333, + "NPCMenuTradeRepair": 3334, + "NPCPurchaseItems": 3335, + "NPCSellItems": 3336, + "NPCHeal": 3337, + "NPCRepairItems": 3338, + "NPCNextPage": 3339, + "NPCPreviousPage": 3340, + "strUiMenu2": 4131, + "TransactionMenu1a": 3342, + "TransactionMenu1f": 3343, + "VerifyTransaction1": 3344, + "VerifyTransaction2": 3345, + "VerifyTransaction3": 3346, + "VerifyTransaction4": 3347, + "VerifyTransaction5": 3348, + "VerifyTransaction6": 3349, + "VerifyTransaction7": 3350, + "VerifyTransaction8": 3351, + "VerifyTransaction9": 3352, + "TransactionResults1": 3353, + "TransactionResults2": 3354, + "TransactionResults3": 3355, + "TransactionResults4": 3356, + "TransactionResults5": 3357, + "TransactionResults6": 3358, + "TransactionResults7": 3359, + "TransactionResults8": 3360, + "TransactionResults9": 3361, + "TransactionResults10": 3362, + "TransactionResults11": 3363, + "ItemDesc1s": 3364, + "ItemDesc1t": 3365, + "HP": 3366, + "AC": 3367, + "Level": 3368, + "Cost": 3369, + "Damage": 3370, + "strhirespecial1": 3371, + "strhirespecial2": 3372, + "strhirespecial3": 3373, + "strhirespecial4": 3374, + "strhirespecial5": 3375, + "strhirespecial6": 3376, + "strhirespecial7": 3377, + "strhirespecial8": 3378, + "strhirespecial9": 3379, + "strhirespecial10": 3380, + "TalkMenu": 3381, + "WarrivMenu1b": 3382, + "WarrivMenu1c": 3383, + "MeshifMenuEast": 3384, + "MeshifMenuWest": 3385, + "NPCMenuNews0": 3386, + "NPCMenuNews1": 3387, + "NPCMenuNews2": 3388, + "NPCMenuNews3": 3389, + "NPCMenuNews4": 3390, + "NPCMenuTalkMore": 3391, + "NPCTownMore0": 3392, + "NPCTownMore1": 3393, + "NPCMenuLeave": 3394, + "NPCGossipMenu": 3395, + "NPCMenuTrade": 3396, + "NPCMenuHire": 3397, + "gamble": 3398, + "Intro": 3399, + "Back": 3400, + "ok": 3401, + "cancel": 3402, + "Continue": 3403, + "strMenuMain15": 3404, + "strOptMusic": 3405, + "strOptSound": 3406, + "strOptGamma": 3407, + "strOptRender": 3408, + "strOptPrevious": 3409, + "cfgCtrl": 3410, + "merc01": 3411, + "merc02": 3412, + "merc03": 3413, + "merc04": 3414, + "merc05": 3415, + "merc06": 3416, + "merc07": 3417, + "merc08": 3418, + "merc09": 3419, + "merc10": 3420, + "merc11": 3421, + "merc12": 3422, + "merc13": 3423, + "merc14": 3424, + "merc15": 3425, + "merc16": 3426, + "merc17": 3427, + "merc18": 3428, + "merc19": 3429, + "merc20": 3430, + "merc21": 3431, + "merc22": 3432, + "merc23": 3433, + "merc24": 3434, + "merc25": 3435, + "merc26": 3436, + "merc27": 3437, + "merc28": 3438, + "merc29": 3439, + "merc30": 3440, + "merc31": 3441, + "merc32": 3442, + "merc33": 3443, + "merc34": 3444, + "merc35": 3445, + "merc36": 3446, + "merc37": 3447, + "merc38": 3448, + "merc39": 3449, + "merc40": 3450, + "merc41": 3451, + "merclevelup": 3452, + "Socketable": 3453, + "ItemStats1a": 3454, + "ItemStats1b": 3455, + "ItemStats1c": 3456, + "ItemStats1d": 3457, + "ItemStats1e": 3458, + "ItemStats1f": 3459, + "ItemStats1g": 3460, + "ItemStats1h": 3461, + "ItemStats1i": 3462, + "ItemStats1j": 3463, + "ItemStast1k": 3464, + "ItemStats1l": 3465, + "ItemStats1m": 3466, + "ItemStats1n": 3467, + "ItemStats1o": 3468, + "ItemStats1p": 3469, + "ItemStats1q": 3470, + "ItemStatsrejuv1": 3471, + "ItemStatsrejuv2": 3472, + "ModStr1a": 3473, + "ModStr1b": 3474, + "ModStr1c": 3475, + "ModStr1d": 3476, + "ModStr1e": 3477, + "ModStr1f": 3478, + "ModStr1g": 3479, + "ModStr1h": 3480, + "ModStr1i": 3481, + "ModStr1j": 3482, + "ModStr1k": 3483, + "ModStr1l": 3484, + "ModStr1m": 3485, + "ModStr1n": 3486, + "ModStr1o": 3487, + "ModStr1p": 3488, + "ModStr1q": 3489, + "ModStr1r": 3490, + "ModStr1s": 3491, + "ModStr1t": 3492, + "ModStr1u": 3493, + "ModStr1v": 3494, + "ModStr1w": 3495, + "ModStr1x": 3496, + "ModStr1y": 3497, + "ModStr1z": 3498, + "ModStr2a": 3499, + "ModStr2b": 3500, + "ModStr2c": 3501, + "ModStr2d": 3502, + "ModStr2e": 3503, + "ModStr2f": 3504, + "ModStr2g": 3505, + "ModStr2h": 3506, + "ModStr2i": 3507, + "ModStr2j": 3508, + "ModStr2k": 3509, + "ModStr2l": 3510, + "ModStr2m": 3511, + "ModStr2n": 3512, + "ModStr2o": 3513, + "ModStr2p": 3514, + "ModStr2q": 3515, + "ModStr2r": 3516, + "ModStr2s": 3517, + "ModStr2t": 3518, + "ModStr2u": 3519, + "Modstr2v": 3520, + "ModStr2w": 3521, + "ModStr2x": 3522, + "ModStr2y": 3523, + "ModStr2z": 3524, + "ModStr3a": 3525, + "ModStr3b": 3526, + "ModStr3c": 3527, + "ModStr3d": 3528, + "ModStr3e": 3529, + "ModStr3f": 3530, + "ModStr3g": 3531, + "ModStr3h": 3532, + "ModStr3i": 3533, + "ModStr3j": 3534, + "ModStr3k": 3535, + "ModStr3l": 3536, + "ModStr3m": 3537, + "ModStr3n": 3538, + "ModStr3o": 3539, + "ModStr3p": 3540, + "ModStr3q": 3541, + "ModStr3r": 3542, + "ModStr3u": 3543, + "ModStr3v": 3544, + "ModStr3w": 3545, + "ModStr3x": 3546, + "ModStr3y": 3547, + "ModStr3z": 3548, + "ModStr4a": 3549, + "ModStr4b": 3550, + "ModStr4c": 3551, + "ModStr4d": 3552, + "ModStr4e": 3553, + "ModStr4f": 3554, + "ModStr4g": 3555, + "ModStr4h": 3556, + "ModStr4i": 3557, + "ModStr4j": 3558, + "ModStr4k": 3559, + "ModStr4l": 3560, + "ModStr4m": 3561, + "ModStr4n": 3562, + "ModStr4o": 3563, + "ModStr4p": 3564, + "ModStr4q": 3565, + "ModStr4r": 3566, + "ModStr4s": 3567, + "ModStr4t": 3568, + "ModStr4u": 3569, + "ModStr4v": 3570, + "ModStr4w": 3571, + "ModStr4x": 3572, + "ModStr4y": 3573, + "ModStr4z": 3574, + "ModStr5a": 3575, + "ModStr5b": 3576, + "ModStr5c": 3577, + "ModStr5d": 3578, + "ModStr5e": 3579, + "ModStr5f": 3580, + "ModStr5g": 3581, + "ModStr5h": 3582, + "ModStr5i": 3583, + "ModStr5j": 3584, + "ModStr5k": 3585, + "ModStr5l": 3586, + "ModStr5m": 3587, + "ModStr5n": 3588, + "ModStr5o": 3589, + "ModStr5p": 3590, + "ModStr5q": 3591, + "ModStr5r": 3592, + "ModStr5s": 3593, + "ModStr5t": 3594, + "ModStr5u": 3595, + "ModStr5v": 3596, + "ModStr5w": 3597, + "ModStr5x": 3598, + "ModStr5y": 3599, + "ModStr5z": 3600, + "ModStr6a": 3601, + "ModStr6b": 3602, + "ModStr6c": 3603, + "ModStr6d": 3604, + "ModStr6e": 3605, + "ModStr6f": 3606, + "ModStr6g": 3607, + "ModStr6h": 3608, + "ModStr6i": 3609, + "strModAllResistances": 3610, + "strModAllSkillLevels": 3611, + "strModFireDamage": 3612, + "strModFireDamageRange": 3613, + "strModColdDamage": 3614, + "strModColdDamageRange": 3615, + "strModLightningDamage": 3616, + "strModLightningDamageRange": 3617, + "strModMagicDamage": 3618, + "strModMagicDamageRange": 3619, + "strModPoisonDamage": 3620, + "strModPoisonDamageRange": 3621, + "strModMinDamage": 3622, + "strModMinDamageRange": 3623, + "strModEnhancedDamage": 3624, + "improved damage": 3625, + "improved to hit": 3626, + "improved armor class": 3627, + "improved durability": 3628, + "Quick Strike": 3629, + "strGemPlace1": 3630, + "strGemPlace2": 3631, + "gemeffect1": 3632, + "gemeffect2": 3633, + "gemeffect3": 3634, + "gemeffect4": 3635, + "gemeffect5": 3636, + "gemeffect6": 3637, + "gemeffect7": 3638, + "sysmsg1": 3639, + "sysmsg2": 3640, + "sysmsg3": 3641, + "sysmsg4": 3642, + "sysmsg3a": 3643, + "sysmsg4a": 3644, + "sysmsg5": 3645, + "sysmsg6": 3646, + "sysmsg7": 3647, + "sysmsg8": 3648, + "sysmsg9": 3649, + "sysmsg10": 3650, + "sysmsg11": 3651, + "sysmsg12": 3652, + "sysmsgPlayer": 3653, + "chatmsg1": 3654, + "chatmsg2": 3655, + "chatmsg3": 3657, + "strwhisperworked": 3658, + "syswork": 3659, + "ShrId0": 3660, + "ShrId1": 3661, + "ShrId2": 3662, + "ShrId3": 3663, + "ShrId4": 3664, + "ShrId5": 3665, + "ShrId6": 3666, + "ShrId7": 3667, + "ShrId8": 3668, + "ShrId9": 3669, + "ShrId10": 3670, + "ShrId11": 3671, + "ShrId12": 3672, + "ShrId13": 3673, + "ShrId14": 3674, + "ShrId15": 3675, + "ShrId16": 3676, + "ShrId17": 3677, + "ShrId18": 3678, + "ShrId19": 3679, + "ShrId20": 3680, + "ShrId21": 3681, + "ShrId22": 3682, + "ShrMsg0": 3683, + "ShrMsg1": 3684, + "ShrMsg2": 3685, + "ShrMsg3": 3686, + "ShrMsg4": 3687, + "ShrMsg5": 3688, + "ShrMsg6": 3689, + "ShrMsg7": 3690, + "ShrMsg8": 3691, + "ShrMsg9": 3692, + "ShrMsg10": 3693, + "ShrMsg11": 3694, + "ShrMsg12": 3695, + "ShrMsg13": 3696, + "ShrMsg14": 3697, + "ShrMsg15": 3698, + "ShrMsg16": 3699, + "ShrMsg17": 3700, + "ShrMsg18": 3701, + "ShrMsg19": 3702, + "ShrMsg20": 3703, + "ShrMsg21": 3704, + "ShrMsg22": 3705, + "strqi1": 3706, + "strqi2": 3707, + "stsa1q3alert": 3708, + "stsa1q4alert": 3709, + "stsa3q1alert": 3710, + "qstsa1qt": 3711, + "qstsa1qt0": 3712, + "qstsa1q0": 3713, + "qstsa1q1": 3714, + "qstsa1q2": 3715, + "qstsa1q3": 3716, + "qstsa1q4": 3717, + "qstsa1q5": 3718, + "qstsa1q6": 3719, + "strplaylast": 3720, + "newquestlog": 3721, + "qsts": 3722, + "noactivequest": 3723, + "qstsxxx": 3724, + "qstsnull": 3725, + "qstsComplete": 3726, + "qstsother": 3727, + "qstsprevious": 3728, + "qstsThankYouComeAgain": 3729, + "qstsThankYouComeAgainMulti": 3730, + "qstsThankYouComeAgainSingle": 3731, + "Qstsyouarenot8": 3732, + "qstsa1q3x": 3733, + "qstsa1q4x": 3734, + "qstsa1q11": 3735, + "qstsa1q12": 3736, + "qstsa1q13": 3737, + "qstsa1q14": 3738, + "qstsa1q140": 3739, + "qstsa1q15": 3740, + "qstsa1q21": 3741, + "qstsa1q22": 3742, + "qstsa1q23": 3743, + "qstsa1q41": 3744, + "qstsa1q42": 3745, + "qstsa1q43": 3746, + "qstsa1q44": 3747, + "qstsa1q45": 3748, + "qstsa1q46": 3749, + "qstsa1q46b": 3750, + "qstsa1q51": 3751, + "qstsa1q51a": 3752, + "qstsa1q51b": 3753, + "qstsa1q52": 3754, + "qstsa1q31": 3755, + "qstsa1q32": 3756, + "qstsa1q32b": 3757, + "qstsa1q61": 3758, + "qstsa1q62": 3759, + "qstsa1q62b": 3760, + "qstsa1q63": 3761, + "KeyNone": 3762, + "KeyLButton": 3763, + "KeyRButton": 3764, + "KeyCancel": 3765, + "KeyMButton": 3766, + "Key4Button": 3767, + "Key5Button": 3768, + "KeyWheelUp": 3769, + "KeyWheelDown": 3770, + "KeyKana": 3771, + "KeyJunja": 3772, + "KeyFinal": 3773, + "KeyKanji": 3774, + "KeyEscape": 3775, + "KeyConvert": 3776, + "KeyNonConvert": 3777, + "KeyAccept": 3778, + "KeyModeChange": 3779, + "KeyLeft": 3780, + "KeyUp": 3781, + "KeyRight": 3782, + "KeyDown": 3783, + "KeySelect": 3784, + "KeyExecute": 3785, + "KeyLWin": 3786, + "KeyRWin": 3787, + "KeyApps": 3788, + "KeyNumLock": 3789, + "KeyBack": 3790, + "KeyTab": 3791, + "KeyClear": 3792, + "KeyReturn": 3793, + "KeyShift": 3794, + "KeyControl": 3795, + "KeyMenu": 3796, + "KeyPause": 3797, + "KeyCapital": 3798, + "KeySpace": 3799, + "KeyPrior": 3800, + "KeyNext": 3801, + "KeyEnd": 3802, + "KeyHome": 3803, + "KeyPrint": 3804, + "KeySnapshot": 3805, + "KeyInsert": 3806, + "KeyDelete": 3807, + "KeyHelp": 3808, + "KeyNumPad0": 3809, + "KeyNumPad1": 3810, + "KeyNumPad2": 3811, + "KeyNumPad3": 3812, + "KeyNumPad4": 3813, + "KeyNumPad5": 3814, + "KeyNumPad6": 3815, + "KeyNumPad7": 3816, + "KeyNumPad8": 3817, + "KeyNumPad9": 3818, + "KeyMultiply": 3819, + "KeyAdd": 3820, + "KeySeparator": 3821, + "KeySubtract": 3822, + "KeyDecimal": 3823, + "KeyDivide": 3824, + "KeyF1": 3825, + "KeyF2": 3826, + "KeyF3": 3827, + "KeyF4": 3828, + "KeyF5": 3829, + "KeyF6": 3830, + "KeyF7": 3831, + "KeyF8": 3832, + "KeyF9": 3833, + "KeyF10": 3834, + "KeyF11": 3835, + "KeyF12": 3836, + "KeyF13": 3837, + "KeyF14": 3838, + "KeyF15": 3839, + "KeyF16": 3840, + "KeyF17": 3841, + "KeyF18": 3842, + "KeyF19": 3843, + "KeyF20": 3844, + "KeyF21": 3845, + "KeyF22": 3846, + "KeyF23": 3847, + "KeyF24": 3848, + "KeyScroll": 3849, + "KeySemicolon": 3850, + "KeyEqual": 3851, + "KeyComma": 3852, + "KeyMinus": 3853, + "KeyPeriod": 3854, + "KeySlash": 3855, + "KeyTilde": 3856, + "KeyLBracket": 3857, + "KeyBackslash": 3858, + "KeyRBracket": 3859, + "KeyApostrophe": 3860, + "ShorthandKeyMButton": 3861, + "ShorthandKey4Button": 3862, + "ShorthandKey5Button": 3863, + "ShorthandKeyWheelUp": 3864, + "ShorthandKeyWheelDown": 3865, + "ShorthandKeyKana": 3866, + "ShorthandKeyJunja": 3867, + "ShorthandKeyFinal": 3868, + "ShorthandKeyKanji": 3869, + "ShorthandKeyEscape": 3870, + "ShorthandKeyConvert": 3871, + "ShorthandKeyNonConvert": 3872, + "ShorthandKeyAccept": 3873, + "ShorthandKeyModeChange": 3874, + "ShorthandKeyLeft": 3875, + "ShorthandKeyRight": 3876, + "ShorthandKeyDown": 3877, + "ShorthandKeySelect": 3878, + "ShorthandKeyExecute": 3879, + "ShorthandKeyLeftWindows": 3880, + "ShorthandKeyRightWindows": 3881, + "ShorthandKeyApps": 3882, + "ShorthandKeyNumLock": 3883, + "ShorthandKeyBackspace": 3884, + "ShorthandKeyClear": 3885, + "ShorthandKeyEnter": 3886, + "ShorthandKeyShift": 3887, + "ShorthandKeyControl": 3888, + "ShorthandKeyPause": 3889, + "ShorthandKeyCapsLock": 3890, + "ShorthandKeySpace": 3891, + "ShorthandKeyPageUp": 3892, + "ShorthandKeyPageDown": 3893, + "ShorthandKeyHome": 3894, + "ShorthandKeyPrintScreen": 3895, + "ShorthandKeyInsert": 3896, + "ShorthandKeyDelete": 3897, + "ShorthandKeyHelp": 3898, + "ShorthandKeyNumPad0": 3899, + "ShorthandKeyNumPad1": 3900, + "ShorthandKeyNumPad2": 3901, + "ShorthandKeyNumPad3": 3902, + "ShorthandKeyNumPad4": 3903, + "ShorthandKeyNumPad5": 3904, + "ShorthandKeyNumPad6": 3905, + "ShorthandKeyNumPad7": 3906, + "ShorthandKeyNumPad8": 3907, + "ShorthandKeyNumPad9": 3908, + "ShorthandKeyNumPad*\tnp*": 3909, + "ShorthandKeyNumPad+\tnp+": 3910, + "ShorthandKeyNumPad-\tnp-": 3911, + "ShorthandKeyNumPad.\tnp.": 3912, + "ShorthandKeyNumPad/\tnp/": 3913, + "ShorthandKeyScroll": 3914, + "KeyMacOption": 3915, + "KeyMacCommand": 3916, + "KeyMacNumPad=\tNum Pad =": 3917, + "ShorthandKeyMacOption": 3918, + "ShorthandKeyMacCommand": 3919, + "ShorthandKeyMacNumPad=\tNP=": 3920, + "CfgFunction": 3921, + "CfgPrimaryKey": 3922, + "CfgSecondaryKey": 3923, + "CfgCharacter": 3924, + "CfgInventory": 3925, + "CfgParty": 3926, + "CfgMessageLog": 3927, + "CfgQuestLog": 3928, + "CfgChat": 3929, + "CfgAutoMap": 3930, + "CfgAutoMapCenter": 3931, + "CfgMiniMap": 3932, + "CfgHelp": 3933, + "CfgSkillTree": 3934, + "CfgSkillPick": 3935, + "CfgSkill1": 3936, + "CfgSkill2": 3937, + "CfgSkill3": 3938, + "CfgSkill4": 3939, + "CfgSkill5": 3940, + "CfgSkill6": 3941, + "CfgSkill7": 3942, + "CfgSkill8": 3943, + "Cfgskillup": 3944, + "Cfgskilldown": 3945, + "CfgBeltShow": 3946, + "CfgBelt1": 3947, + "CfgBelt2": 3948, + "CfgBelt3": 3949, + "CfgBelt4": 3950, + "CfgBelt5": 3951, + "CfgBelt6": 3952, + "CfgBelt7": 3953, + "CfgBelt8": 3954, + "CfgBelt9": 3955, + "CfgBelt10": 3956, + "CfgBelt11": 3957, + "CfgBelt12": 3958, + "CfgSay0": 3959, + "CfgSay1": 3960, + "CfgSay2": 3961, + "CfgSay3": 3962, + "CfgSay4": 3963, + "CfgSay5": 3964, + "CfgSay6": 3965, + "CfgRun": 3966, + "CfgRunLock": 3967, + "CfgStandStill": 3968, + "CfgShowItems": 3969, + "CfgClearScreen": 3970, + "CfgSnapshot": 3971, + "CfgDefault": 3972, + "CfgAccept": 3973, + "CfgCancel": 3974, + "strNoKeysAssigned": 3975, + "KeysAssigned": 3976, + "CantAssignMB": 3977, + "CantAssignMW": 3978, + "CantAssignKey": 3979, + "CfgClearKey": 3980, + "Cfgcleartextmsg": 3981, + "CfgTogglePortraits": 3982, + "CfgAutoMapFade": 3983, + "CfgAutoMapNames": 3984, + "CfgAutoMapParty": 3985, + "strlvlup": 3986, + "strnewskl": 3987, + "warpsheader": 3988, + "nowarps": 3989, + "waypointsheader": 3990, + "nowaypoints": 3991, + "max": 3992, + "MAX": 3993, + "colorcode": 3994, + "space": 3995, + "dash": 3996, + "colon": 3997, + "newline": 3998, + "pipe": 3999, + "slash": 4000, + "percent": 4001, + "plus": 4002, + "to": 4003, + "srostertitle": 4004, + "dwell": 4005, + "larva": 4006, + "Barbarian": 4007, + "Paladin": 4008, + "Necromancer": 4009, + "Sorceress": 4010, + "Amazon": 4011, + "druidstr \tDruid": 4012, + "assassinstr": 4013, + "Nest": 4014, + "NoParty": 4015, + "ItsMyParty": 4016, + "Upgrade": 4017, + "upgraderestrict": 4018, + "Use": 4019, + "NPCIdentify1": 4020, + "NPCIdentify2": 4021, + "strCannotDoThisToUnknown": 4022, + "Body Looted": 4023, + "Party1": 4024, + "Party2": 4025, + "Party3": 4026, + "Party4": 4027, + "Party5": 4028, + "Party6": 4029, + "Party7": 4030, + "Party8": 4031, + "Party9": 4032, + "strDropGoldHowMuch": 4033, + "strDropGoldInfo": 4034, + "strMsgLog": 4035, + "strBSArmor": 4036, + "strBSWeapons": 4037, + "strBSMagic": 4038, + "strBSMisc": 4039, + "strTrade": 4040, + "strTradeAccept": 4041, + "strTradeAgreeTo": 4042, + "strWaitingForOtherPlayer": 4043, + "strTradeBusy": 4044, + "strTradeTooFull": 4045, + "strTradeGoldHowMuch": 4046, + "strTradeTimeout": 4047, + "SysmsgPlayer1": 4048, + "strBankGoldDeposit": 4049, + "strBankGoldWithdraw": 4050, + "GoldMax": 4051, + "StrUI0": 4052, + "StrUI1": 4053, + "StrUI2": 4054, + "StrUI3": 4055, + "StrUI4": 4056, + "strchrlvl": 4057, + "strchrexp": 4058, + "strchrnxtlvl": 4059, + "strchrstr": 4060, + "strchrskm": 4061, + "strchrdex": 4062, + "strchratr": 4063, + "strchrdef": 4064, + "strchrrat": 4065, + "strchrvit": 4066, + "strchrstm": 4067, + "strchrlif": 4068, + "strchreng": 4069, + "strchrman": 4070, + "strchrfir": 4071, + "strchrcol": 4072, + "strchrlit": 4073, + "strchrpos": 4074, + "strchrstat": 4075, + "strchrrema": 4076, + "WeaponDescMace": 4077, + "WeaponDescAxe": 4078, + "WeaponDescSword": 4079, + "WeaponDescDagger": 4080, + "WeaponDescThrownPotion": 4081, + "WeaponDescJavelin": 4082, + "WeaponDescSpear": 4083, + "WeaponDescBow": 4084, + "WeaponDescStaff": 4085, + "WeaponDescPoleArm": 4086, + "WeaponDescCrossBow": 4087, + "WeaponAttackFastest": 4088, + "WeaponAttackVeryFast": 4089, + "WeaponAttackFast": 4090, + "WeaponAttackNormal": 4091, + "WeaponAttackSlow": 4092, + "WeaponAttackVerySlow": 4093, + "WeaponAttackSlowest": 4094, + "strNecromanerOnly": 4095, + "strPaladinOnly": 4096, + "strSorceressOnly": 4097, + "strMaceSpecialDamage": 4098, + "strGoldLabel": 4099, + "strParty1": 4100, + "strParty2": 4101, + "strParty3": 4102, + "strParty4": 4103, + "strParty5": 4104, + "strParty6": 4105, + "strParty7": 4106, + "strParty8": 4107, + "strParty9": 4108, + "strParty10": 4109, + "strParty11": 4110, + "strParty12": 4111, + "strParty13": 4112, + "strParty14": 4113, + "strParty15": 4114, + "strParty16": 4115, + "strParty17": 4116, + "strParty18": 4117, + "strParty19": 4118, + "strParty22": 4119, + "strParty24": 4120, + "strParty25": 4121, + "StrParty26": 4122, + "StrParty27": 4123, + "strGoldWithdraw": 4124, + "strGoldDrop": 4125, + "strGoldDeposit": 4126, + "strGoldTrade": 4127, + "strGoldInStash": 4128, + "strGoldTradepup": 4129, + "strUiMenu1": 4130, + "strUiBank": 4132, + "strUnknownTomb": 4133, + "strTradeOtherBox": 4134, + "strTradeBox": 4135, + "strFree": 4136, + "act1": 4137, + "act2": 4138, + "act3": 4139, + "act4": 4140, + "level": 4141, + "lowercasecancel": 4142, + "close": 4143, + "strClose": 4144, + "Lightning Spell": 4145, + "Fire Spell": 4146, + "Cold Spell": 4147, + "Yourparty": 4148, + "Inparty": 4149, + "Invite": 4150, + "Accept": 4151, + "Leave": 4152, + "Partyclose": 4153, + "partycharama": 4154, + "partycharsor": 4155, + "partycharbar": 4156, + "partycharnec": 4157, + "partycharpal": 4158, + "charavghit": 4159, + "charmonster": 4160, + "charmontohit1": 4161, + "charmontohit2": 4162, + "panelexp": 4163, + "panelstamina": 4164, + "panelhealth": 4165, + "panelmana": 4166, + "panelmini": 4167, + "panelcmini": 4168, + "minipanelchar": 4169, + "minipanelinv": 4170, + "minipaneltree": 4171, + "minipanelparty": 4172, + "minipanelautomap": 4173, + "minipanelmessage": 4174, + "minipanelquest": 4175, + "minipanelmenubtn": 4176, + "minipanelHelp": 4177, + "minipanelspecial": 4178, + "RunOn": 4179, + "RunOff": 4180, + "automapgame": 4181, + "automappw": 4182, + "automapdif": 4183, + "scrollbooktext": 4184, + "skilldesc1": 4185, + "skilldesc2": 4186, + "skilldesc3": 4187, + "skilldesc4": 4188, + "strpanel1": 4189, + "strpanel2": 4190, + "strpanel3": 4191, + "strpanel4": 4192, + "strpanel5": 4193, + "strpanel6": 4194, + "strpanel7": 4195, + "strpanel8": 4196, + "stashfull": 4197, + "Strhelp1": 4198, + "StrHelp2": 4199, + "StrHelp3": 4200, + "StrHelp4": 4201, + "StrHelp5": 4202, + "StrHelp6": 4203, + "StrHelp7": 4204, + "StrHelp8": 4205, + "StrHelp8a": 4206, + "StrHelp9": 4207, + "StrHelp10": 4208, + "StrHelp11": 4209, + "StrHelp12": 4210, + "StrHelp13": 4211, + "StrHelp14": 4212, + "StrHelp14a": 4213, + "StrHelp15": 4214, + "StrHelp16": 4215, + "StrHelp16a": 4216, + "StrHelp17": 4217, + "StrHelp18": 4218, + "StrHelp19": 4219, + "StrHelp20": 4220, + "StrHelp21": 4221, + "StrHelp22": 4222, + "strSklTree": 4223, + "StrSklTreea": 4224, + "StrSklTreeb": 4225, + "StrSklTreec": 4226, + "StrSklTree1": 4227, + "StrSklTree2": 4228, + "StrSklTree3": 4229, + "StrSklTree4": 4230, + "StrSklTree5": 4231, + "StrSklTree6": 4232, + "StrSklTree7": 4233, + "StrSklTree8": 4234, + "StrSklTree9": 4235, + "StrSklTree10": 4236, + "StrSklTree11": 4237, + "StrSklTree12": 4238, + "StrSklTree13": 4239, + "StrSklTree14": 4240, + "StrSklTree15": 4241, + "StrSklTree16": 4242, + "StrSklTree17": 4243, + "StrSklTree18": 4244, + "StrSklTree19": 4245, + "StrSklTree20": 4246, + "StrSklTree21": 4247, + "StrSklTree22": 4248, + "StrSklTree23": 4249, + "StrSklTree24": 4250, + "StrSklTree25": 4251, + "StrSkill0": 4252, + "StrSkill1": 4253, + "StrSkill2": 4254, + "StrSkill3": 4255, + "StrSkill4": 4256, + "StrSkill5": 4257, + "StrSkill6": 4258, + "StrSkill7": 4259, + "StrSkill8": 4260, + "StrSkill9": 4261, + "StrSkill10": 4262, + "StrSkill11": 4263, + "StrSkill12": 4264, + "StrSkill13": 4265, + "StrSkill14": 4266, + "StrSkill15": 4267, + "StrSkill16": 4268, + "StrSkill17": 4269, + "StrSkill18": 4270, + "StrSkill19": 4271, + "StrSkill20": 4272, + "StrSkill21": 4273, + "StrSkill22": 4274, + "StrSkill23": 4275, + "StrSkill24": 4276, + "StrSkill25": 4277, + "StrSkill26": 4278, + "StrSkill27": 4279, + "StrSkill28": 4280, + "StrSkill29": 4281, + "StrSkill30": 4282, + "StrSkill31": 4283, + "StrSkill32": 4284, + "StrSkill33": 4285, + "StrSkill34": 4286, + "StrSkill35": 4287, + "StrSkill36": 4288, + "StrSkill37": 4289, + "StrSkill38": 4290, + "StrSkill39": 4291, + "StrSkill40": 4292, + "StrSkill41": 4293, + "StrSkill42": 4294, + "StrSkill43": 4297, + "StrSkill44": 4298, + "StrSkill45": 4299, + "StrSkill46": 4300, + "StrSkill47": 4301, + "StrSkill48": 4302, + "StrSkill49": 4303, + "StrSkill50": 4304, + "StrSkill51": 4305, + "StrSkill52": 4306, + "StrSkill53": 4307, + "StrSkill54": 4308, + "StrSkill55": 4309, + "StrSkill56": 4310, + "StrSkill57": 4311, + "StrSkill58": 4312, + "StrSkill59": 4313, + "StrSkill60": 4314, + "StrSkill61": 4315, + "StrSkill62": 4316, + "StrSkill63": 4317, + "StrSkill64": 4318, + "StrSkill65": 4319, + "StrSkill66": 4320, + "StrSkill67": 4321, + "StrSkill68": 4322, + "StrSkill69": 4323, + "StrSkill70": 4324, + "StrSkill71": 4325, + "StrSkill72": 4326, + "StrSkill73": 4327, + "StrSkill74": 4328, + "StrSkill75": 4329, + "StrSkill76": 4330, + "StrSkill77": 4331, + "StrSkill78": 4332, + "StrSkill79": 4333, + "StrSkill80": 4334, + "StrSkill81": 4335, + "StrSkill82": 4336, + "StrSkill83": 4337, + "StrSkill84": 4338, + "StrSkill85": 4339, + "StrSkill86": 4340, + "StrSkill87": 4341, + "StrSkill88": 4342, + "StrSkill89": 4343, + "StrSkill90": 4344, + "StrSkill91": 4345, + "StrSkill92": 4346, + "StrSkill94": 4347, + "StrSkill95": 4348, + "StrSkill96": 4349, + "StrSkill97": 4350, + "StrSkill98": 4351, + "StrSkill99": 4352, + "StrSkill100": 4353, + "StrSkill101": 4354, + "StrSkill102": 4355, + "StrSkill103": 4356, + "StrSkill104": 4357, + "StrSkill105": 4358, + "StrSkill106": 4359, + "StrSkill107": 4360, + "StrSkill108": 4361, + "StrSkill109": 4362, + "StrSkill110": 4363, + "StrSkill111": 4364, + "StrSkill112": 4365, + "StrSkill113": 4366, + "StrSkill114": 4367, + "StrSkill115": 4368, + "StrSkill116": 4369, + "StrSkill117": 4370, + "StrSkill118": 4371, + "StrSkill119": 4372, + "skillname0": 4373, + "skillsd0": 4374, + "skillld0": 4375, + "skillan0": 4376, + "skillname1": 4377, + "skillsd1": 4378, + "skillld1": 4379, + "skillan1": 4380, + "skillname2": 4381, + "skillsd2": 4382, + "skillld2": 4383, + "skillan2": 4384, + "skillname3": 4385, + "skillsd3": 4386, + "skillld3": 4387, + "skillan3": 4388, + "skillname4": 4389, + "skillsd4": 4390, + "skillld4": 4391, + "skillan4": 4392, + "skillname5": 4393, + "skillsd5": 4394, + "skillld5": 4395, + "skillan5": 4396, + "skillname6": 4397, + "skillsd6": 4398, + "skillld6": 4399, + "skillan6": 4400, + "skillname7": 4401, + "skillsd7": 4402, + "skillld7": 4403, + "skillan7": 4404, + "skillname8": 4405, + "skillsd8": 4406, + "skillld8": 4407, + "skillan8": 4408, + "skillname9": 4409, + "skillsd9": 4410, + "skillld9": 4411, + "skillan9": 4412, + "skillname10": 4413, + "skillsd10": 4414, + "skillld10": 4415, + "skillan10": 4416, + "skillname11": 4417, + "skillsd11": 4418, + "skillld11": 4419, + "skillan11": 4420, + "skillname12": 4421, + "skillsd12": 4422, + "skillld12": 4423, + "skillan12": 4424, + "skillname13": 4425, + "skillsd13": 4426, + "skillld13": 4427, + "skillan13": 4428, + "skillname14": 4429, + "skillsd14": 4430, + "skillld14": 4431, + "skillan14": 4432, + "skillname15": 4433, + "skillsd15": 4434, + "skillld15": 4435, + "skillan15": 4436, + "skillname16": 4437, + "skillsd16": 4438, + "skillld16": 4439, + "skillan16": 4440, + "skillname17": 4441, + "skillsd17": 4442, + "skillld17": 4443, + "skillan17": 4444, + "skillname18": 4445, + "skillsd18": 4446, + "skillld18": 4447, + "skillan18": 4448, + "skillname19": 4449, + "skillsd19": 4450, + "skillld19": 4451, + "skillan19": 4452, + "skillname20": 4453, + "skillsd20": 4454, + "skillld20": 4455, + "skillan20": 4456, + "skillname21": 4457, + "skillsd21": 4458, + "skillld21": 4459, + "skillan21": 4460, + "skillname22": 4461, + "skillsd22": 4462, + "skillld22": 4463, + "skillan22": 4464, + "skillname23": 4465, + "skillsd23": 4466, + "skillld23": 4467, + "skillan23": 4468, + "skillname24": 4469, + "skillsd24": 4470, + "skillld24": 4471, + "skillan24": 4472, + "skillname25": 4473, + "skillsd25": 4474, + "skillld25": 4475, + "skillan25": 4476, + "skillname26": 4477, + "skillsd26": 4478, + "skillld26": 4479, + "skillan26": 4480, + "skillname27": 4481, + "skillsd27": 4482, + "skillld27": 4483, + "skillan27": 4484, + "skillname28": 4485, + "skillsd28": 4486, + "skillld28": 4487, + "skillan28": 4488, + "skillname29": 4489, + "skillsd29": 4490, + "skillld29": 4491, + "skillan29": 4492, + "skillname30": 4493, + "skillsd30": 4494, + "skillld30": 4495, + "skillan30": 4496, + "skillname31": 4497, + "skillsd31": 4498, + "skillld31": 4499, + "skillan31": 4500, + "skillname32": 4501, + "skillsd32": 4502, + "skillld32": 4503, + "skillan32": 4504, + "skillname33": 4505, + "skillsd33": 4506, + "skillld33": 4507, + "skillan33": 4508, + "skillname34": 4509, + "skillsd34": 4510, + "skillld34": 4511, + "skillan34": 4512, + "skillname35": 4513, + "skillsd35": 4514, + "skillld35": 4515, + "skillan35": 4516, + "skillname36": 4517, + "skillsd36": 4518, + "skillld36": 4519, + "skillan36": 4520, + "skillname37": 4521, + "skillsd37": 4522, + "skillld37": 4523, + "skillan37": 4524, + "skillname38": 4525, + "skillsd38": 4526, + "skillld38": 4527, + "skillan38": 4528, + "skillname39": 4529, + "skillsd39": 4530, + "skillld39": 4531, + "skillan39": 4532, + "skillname40": 4533, + "skillsd40": 4534, + "skillld40": 4535, + "skillan40": 4536, + "skillname41": 4537, + "skillsd41": 4538, + "skillld41": 4539, + "skillan41": 4540, + "skillname42": 4541, + "skillsd42": 4542, + "skillld42": 4543, + "skillan42": 4544, + "skillname43": 4545, + "skillsd43": 4546, + "skillld43": 4547, + "skillan43": 4548, + "skillname44": 4549, + "skillsd44": 4550, + "skillld44": 4551, + "skillan44": 4552, + "skillname45": 4553, + "skillsd45": 4554, + "skillld45": 4555, + "skillan45": 4556, + "skillname46": 4557, + "skillsd46": 4558, + "skillld46": 4559, + "skillan46": 4560, + "skillname47": 4561, + "skillsd47": 4562, + "skillld47": 4563, + "skillan47": 4564, + "skillname48": 4565, + "skillsd48": 4566, + "skillld48": 4567, + "skillan48": 4568, + "skillname49": 4569, + "skillsd49": 4570, + "skillld49": 4571, + "skillan49": 4572, + "skillname50": 4573, + "skillsd50": 4574, + "skillld50": 4575, + "skillan50": 4576, + "skillname51": 4577, + "skillsd51": 4578, + "skillld51": 4579, + "skillan51": 4580, + "skillname52": 4581, + "skillsd52": 4582, + "skillld52": 4583, + "skillan52": 4584, + "skillname53": 4585, + "skillsd53": 4586, + "skillld53": 4587, + "skillan53": 4588, + "skillname54": 4589, + "skillsd54": 4590, + "skillld54": 4591, + "skillan54": 4592, + "skillname55": 4593, + "skillsd55": 4594, + "skillld55": 4595, + "skillan55": 4596, + "skillname56": 4597, + "skillsd56": 4598, + "skillld56": 4599, + "skillan56": 4600, + "skillname57": 4601, + "skillsd57": 4602, + "skillld57": 4603, + "skillan57": 4604, + "skillname58": 4605, + "skillsd58": 4606, + "skillld58": 4607, + "skillan58": 4608, + "skillname59": 4609, + "skillsd59": 4610, + "skillld59": 4611, + "skillan59": 4612, + "skillname60": 4613, + "skillsd60": 4614, + "skillld60": 4615, + "skillan60": 4616, + "skillsname61": 4617, + "skillsd61": 4618, + "skillld61": 4619, + "skillan61": 4620, + "skillname62": 4621, + "skillsd62": 4622, + "skillld62": 4623, + "skillan62": 4624, + "skillname63": 4625, + "skillsd63": 4626, + "skillld63": 4627, + "skillan63": 4628, + "skillname64": 4629, + "skillsd64": 4630, + "skillld64": 4631, + "skillan64": 4632, + "skillname65": 4633, + "skillsd65": 4634, + "skillld65": 4635, + "skillan65": 4636, + "skillname66": 4637, + "skillsd66": 4638, + "skillld66": 4639, + "skillan66": 4640, + "skillname67": 4641, + "skillsd67": 4642, + "skillld67": 4643, + "skillan67": 4644, + "skillname68": 4645, + "skillsd68": 4646, + "skillld68": 4647, + "skillan68": 4648, + "skillname69": 4649, + "skillsd69": 4650, + "skillld69": 4651, + "skillan69": 4652, + "skillname70": 4653, + "skillsd70": 4654, + "skillld70": 4655, + "skillan70": 4656, + "skillname71": 4657, + "skillsd71": 4658, + "skillld71": 4659, + "skillan71": 4660, + "skillname72": 4661, + "skillsd72": 4662, + "skillld72": 4663, + "skillan72": 4664, + "skillname73": 4665, + "skillsd73": 4666, + "skillld73": 4667, + "skillan73": 4668, + "skillname74": 4669, + "skillsd74": 4670, + "skillld74": 4671, + "skillan74": 4672, + "skillname75": 4673, + "skillsd75": 4674, + "skillld75": 4675, + "skillan75": 4676, + "skillname76": 4677, + "skillsd76": 4678, + "skillld76": 4679, + "skillan76": 4680, + "skillname77": 4681, + "skillsd77": 4682, + "skillld77": 4683, + "skillan77": 4684, + "skillname78": 4685, + "skillsd78": 4686, + "skillld78": 4687, + "skillan78": 4688, + "skillname79": 4689, + "skillsd79": 4690, + "skillld79": 4691, + "skillan79": 4692, + "skillname80": 4693, + "skillsd80": 4694, + "skillld80": 4695, + "skillan80": 4696, + "skillname81": 4697, + "skillsd81": 4698, + "skillld81": 4699, + "skillan81": 4700, + "skillname82": 4701, + "skillsd82": 4702, + "skillld82": 4703, + "skillan82": 4704, + "skillname83": 4705, + "skillsd83": 4706, + "skillld83": 4707, + "skillan83": 4708, + "skillname84": 4709, + "skillsd84": 4710, + "skillld84": 4711, + "skillan84": 4712, + "skillname85": 4713, + "skillsd85": 4714, + "skillld85": 4715, + "skillan85": 4716, + "skillname86": 4717, + "skillsd86": 4718, + "skillld86": 4719, + "skillan86": 4720, + "skillname87": 4721, + "skillsd87": 4722, + "skillld87": 4723, + "skillan87": 4724, + "skillname88": 4725, + "skillsd88": 4726, + "skillld88": 4727, + "skillan88": 4728, + "skillname89": 4729, + "skillsd89": 4730, + "skillld89": 4731, + "skillan89": 4732, + "skillname90": 4733, + "skillsd90": 4734, + "skillld90": 4735, + "skillan90": 4736, + "skillname91": 4737, + "skillsd91": 4738, + "skillld91": 4739, + "skillan91": 4740, + "skillname92": 4741, + "skillsd92": 4742, + "skillld92": 4743, + "skillan92": 4744, + "skillname93": 4745, + "skillsd93": 4746, + "skillld93": 4747, + "skillan93": 4748, + "skillname94": 4749, + "skillsd94": 4750, + "skillld94": 4751, + "skillan94": 4752, + "skillname95": 4753, + "skillsd95": 4754, + "skillld95": 4755, + "skillan95": 4756, + "skillname96": 4757, + "skillsd96": 4758, + "skillld96": 4759, + "skillan96": 4760, + "skillname97": 4761, + "skillsd97": 4762, + "skillld97": 4763, + "skillan97": 4764, + "skillname98": 4765, + "skillsd98": 4766, + "skillld98": 4767, + "skillan98": 4768, + "skillname99": 4769, + "skillsd99": 4770, + "skillld99": 4771, + "skillan99": 4772, + "skillname100": 4773, + "skillsd100": 4774, + "skillld100": 4775, + "skillan100": 4776, + "skillname101": 4777, + "skillsd101": 4778, + "skillld101": 4779, + "skillan101": 4780, + "skillname102": 4781, + "skillsd102": 4782, + "skillld102": 4783, + "skillan102": 4784, + "skillname103": 4785, + "skillsd103": 4786, + "skillld103": 4787, + "skillan103": 4788, + "skillname104": 4789, + "skillsd104": 4790, + "skillld104": 4791, + "skillan104": 4792, + "skillname105": 4793, + "skillsd105": 4794, + "skillld105": 4795, + "skillan105": 4796, + "skillname106": 4797, + "skillsd106": 4798, + "skillld106": 4799, + "skillan106": 4800, + "skillname107": 4801, + "skillsd107": 4802, + "skillld107": 4803, + "skillan107": 4804, + "skillname108": 4805, + "skillsd108": 4806, + "skillld108": 4807, + "skillan108": 4808, + "skillname109": 4809, + "skillsd109": 4810, + "skillld109": 4811, + "skillan109": 4812, + "skillname110": 4813, + "skillsd110": 4814, + "skillld110": 4815, + "skillan110": 4816, + "skillname111": 4817, + "skillsd111": 4818, + "skillld111": 4819, + "skillan111": 4820, + "skillname112": 4821, + "skillsd112": 4822, + "skillld112": 4823, + "skillan112": 4824, + "skillname113": 4825, + "skillsd113": 4826, + "skillld113": 4827, + "skillan113": 4828, + "skillname114": 4829, + "skillsd114": 4830, + "skillld114": 4831, + "skillan114": 4832, + "skillname115": 4833, + "skillsd115": 4834, + "skillld115": 4835, + "skillan115": 4836, + "skillname116": 4837, + "skillsd116": 4838, + "skillld116": 4839, + "skillan116": 4840, + "skillname117": 4841, + "skillsd117": 4842, + "skillld117": 4843, + "skillan117": 4844, + "skillname118": 4845, + "skillsd118": 4846, + "skillld118": 4847, + "skillan118": 4848, + "skillname119": 4849, + "skillsd119": 4850, + "skillld119": 4851, + "skillan119": 4852, + "skillname120": 4853, + "skillsd120": 4854, + "skillld120": 4855, + "skillan120": 4856, + "skillname121": 4857, + "skillsd121": 4858, + "skillld121": 4859, + "skillan121": 4860, + "skillname122": 4861, + "skillsd122": 4862, + "skillld122": 4863, + "skillan122": 4864, + "skillname123": 4865, + "skillsd123": 4866, + "skillld123": 4867, + "skillan123": 4868, + "skillname124": 4869, + "skillsd124": 4870, + "skillld124": 4871, + "skillan124": 4872, + "skillname125": 4873, + "skillsd125": 4874, + "skillld125": 4875, + "skillan125": 4876, + "skillname126": 4877, + "skillsd126": 4878, + "skillld126": 4879, + "skillan126": 4880, + "skillname127": 4881, + "skillsd127": 4882, + "skillld127": 4883, + "skillan127": 4884, + "skillname128": 4885, + "skillsd128": 4886, + "skillld128": 4887, + "skillan128": 4888, + "skillname129": 4889, + "skillsd129": 4890, + "skillld129": 4891, + "skillan129": 4892, + "skillname130": 4893, + "skillsd130": 4894, + "skillld130": 4895, + "skillan130": 4896, + "skillname131": 4897, + "skillsd131": 4898, + "skillld131": 4899, + "skillan131": 4900, + "skillname132": 4901, + "skillsd132": 4902, + "skillld132": 4903, + "skillan132": 4904, + "skillname133": 4905, + "skillsd133": 4906, + "skillld133": 4907, + "skillan133": 4908, + "skillname134": 4909, + "skillsd134": 4910, + "skillld134": 4911, + "skillan134": 4912, + "skillname135": 4913, + "skillsd135": 4914, + "skillld135": 4915, + "skillan135": 4916, + "skillname136": 4917, + "skillsd136": 4918, + "skillld136": 4919, + "skillan136": 4920, + "skillname137": 4921, + "skillsd137": 4922, + "skillld137": 4923, + "skillan137": 4924, + "skillname138": 4925, + "skillsd138": 4926, + "skillld138": 4927, + "skillan138": 4928, + "skillname139": 4929, + "skillsd139": 4930, + "skillld139": 4931, + "skillan139": 4932, + "skillname140": 4933, + "skillsd140": 4934, + "skillld140": 4935, + "skillan140": 4936, + "skillname141": 4937, + "skillsd141": 4938, + "skillld141": 4939, + "skillan141": 4940, + "skillname142": 4941, + "skillsd142": 4942, + "skillld142": 4943, + "skillan142": 4944, + "skillname143": 4945, + "skillsd143": 4946, + "skillld143": 4947, + "skillan143": 4948, + "skillname144": 4949, + "skillsd144": 4950, + "skillld144": 4951, + "skillan144": 4952, + "skillname145": 4953, + "skillsd145": 4954, + "skillld145": 4955, + "skillan145": 4956, + "skillname146": 4957, + "skillsd146": 4958, + "skillld146": 4959, + "skillan146": 4960, + "skillname147": 4961, + "skillsd147": 4962, + "skillld147": 4963, + "skillan147": 4964, + "skillname148": 4965, + "skillsd148": 4966, + "skillld148": 4967, + "skillan148": 4968, + "skillname149": 4969, + "skillsd149": 4970, + "skillld149": 4971, + "skillan149": 4972, + "skillname150": 4973, + "skillsd150": 4974, + "skillld150": 4975, + "skillan150": 4976, + "skillname151": 4977, + "skillsd151": 4978, + "skillld151": 4979, + "skillan151": 4980, + "skillname152": 4981, + "skillsd152": 4982, + "skillld152": 4983, + "skillan152": 4984, + "skillname153": 4985, + "skillsd153": 4986, + "skillld153": 4987, + "skillan153": 4988, + "skillname154": 4989, + "skillsd154": 4990, + "skillld154": 4991, + "skillan154": 4992, + "skillname155": 4993, + "skillsd155": 4994, + "skillld155": 4995, + "skillan155": 4996, + "skillname217": 4997, + "skillsd217": 4998, + "skillld217": 4999, + "skillan217": 5000, + "skillname218": 5001, + "skillsd218": 5002, + "skillld218": 5003, + "skillan218": 5004, + "skillname219": 5005, + "skillsd219": 5006, + "skillld219": 5007, + "skillan219": 5008, + "skillname220": 5009, + "skillsd220": 5010, + "skillld220": 5011, + "skillan220": 5012, + "strMephistoDoorLocked": 5013, + "strTitleFeminine": 5014, + "strTitleMasculine": 5015, + "strChatHardcore": 5016, + "strChatLevel": 5017, + "Tristram": 5018, + "Catacombs Level 4": 5019, + "Catacombs Level 3": 5020, + "Catacombs Level 2": 5021, + "Catacombs Level 1": 5022, + "Cathedral": 5023, + "Inner Cloister": 5024, + "Jail Level 3": 5025, + "Jail Level 2": 5026, + "Jail Level 1": 5027, + "Barracks": 5028, + "Outer Cloister": 5029, + "Monastery Gate": 5030, + "Tower Cellar Level 5": 5031, + "Tower Cellar Level 4": 5032, + "Tower Cellar Level 3": 5033, + "Tower Cellar Level 2": 5034, + "Tower Cellar Level 1": 5035, + "Forgotten Tower": 5036, + "Mausoleum": 5037, + "Crypt": 5038, + "Burial Grounds": 5039, + "Pit Level 2": 5040, + "Hole Level 2": 5041, + "Underground Passage Level 2": 5042, + "Cave Level 2": 5043, + "Pit Level 1": 5044, + "Hole Level 1": 5045, + "Underground Passage Level 1": 5046, + "Cave Level 1": 5047, + "Den of Evil": 5048, + "Tamoe Highland": 5049, + "Black Marsh": 5050, + "Dark Wood": 5051, + "Stony Field": 5052, + "Cold Plains": 5053, + "Blood Moor": 5054, + "Rogue Encampment": 5055, + "To Tristram": 5056, + "To The Catacombs Level 4": 5057, + "To The Catacombs Level 3": 5058, + "To The Catacombs Level 2": 5059, + "To The Catacombs Level 1": 5060, + "To The Cathedral": 5061, + "To The Inner Cloister": 5062, + "To The Jail Level 3": 5063, + "To The Jail Level 2": 5064, + "To The Jail Level 1": 5065, + "To The Barracks": 5066, + "To The Outer Cloister": 5067, + "To The Monastery Gate": 5068, + "To The Tower Cellar Level 5": 5069, + "To The Tower Cellar Level 4": 5070, + "To The Tower Cellar Level 3": 5071, + "To The Tower Cellar Level 2": 5072, + "To The Tower Cellar Level 1": 5073, + "To The Forgotten Tower": 5074, + "To The Mausoleum": 5075, + "To The Crypt": 5076, + "To The Burial Grounds": 5077, + "To The Pit Level 2": 5078, + "To The Hole Level 2": 5079, + "To Underground Passage Level 2": 5080, + "To The Cave Level 2": 5081, + "To The Pit Level 1": 5082, + "To The Hole Level 1": 5083, + "To Underground Passage Level 1": 5084, + "To The Cave Level 1": 5085, + "To The Den of Evil": 5086, + "To The Tamoe Highland": 5087, + "To The Black Marsh": 5088, + "To The Dark Wood": 5089, + "To The Stony Field": 5090, + "To The Cold Plains": 5091, + "To The Blood Moor": 5092, + "To The Rogue Encampment": 5093, + "Deathmessage": 5094, + "Deathmessnight": 5095, + "Harddeathmessage": 5096, + "LordofTerrordied": 5097, + "Killdiablo1": 5098, + "KillDiablo2": 5099, + "KillDiablo3": 5100, + "x": 22741, + "X": 22746, + "Gem Activated": 5334, + "Gem Deactivated": 5335, + "Perfect Gem Activated": 5336, + "dummy": 5382, + "Dummy": 5383, + "not used": 5384, + "unused": 5385, + "Not used": 5386, + "convertsto": 5387, + "strNotInBeta": 5388, + "strLevelLoadFailed": 5389, + "Endthispuppy": 5390, + "A4Q2ExpansionSuccessTyrael": 20000, + "A4Q2ExpansionSuccessCain": 20001, + "AncientsAct5IntroGossip1": 20002, + "CainAct5IntroGossip1": 20003, + "CainAct5Gossip1": 20004, + "CainAct5Gossip2": 20005, + "CainAct5Gossip3": 20006, + "CainAct5Gossip4": 20007, + "CainAct5Gossip5": 20008, + "CainAct5Gossip6": 20009, + "CainAct5Gossip7": 20010, + "CainAct5Gossip8": 20011, + "CainAct5Gossip9": 20012, + "CainAct5Gossip10": 20013, + "AnyaAct5IntroGossip1": 20014, + "AnyaGossip1": 20015, + "AnyaGossip2": 20016, + "AnyaGossip3": 20017, + "AnyaGossip4": 20018, + "AnyaGossip5": 20019, + "AnyaGossip6": 20020, + "AnyaGossip7": 20021, + "AnyaGossip8": 20022, + "AnyaGossip9": 20023, + "AnyaGossip10": 20024, + "LarzukAct5IntroGossip1": 20025, + "LarzukAct5IntroAmaGossip1": 20026, + "LarzukGossip1": 20027, + "LarzukGossip2": 20028, + "LarzukGossip3": 20029, + "LarzukGossip4": 20030, + "LarzukGossip5": 20031, + "LarzukGossip6": 20032, + "LarzukGossip7": 20033, + "LarzukGossip8": 20034, + "LarzukGossip9": 20035, + "LarzukGossip10": 20036, + "MalahAct5IntroGossip1": 20037, + "MalahAct5IntroSorGossip1": 20038, + "MalahAct5IntroBarGossip1": 20039, + "MalahGossip1": 20040, + "MalahGossip2": 20041, + "MalahGossip3": 20042, + "MalahGossip4": 20043, + "MalahGossip5": 20044, + "MalahGossip6": 20045, + "MalahGossip7": 20046, + "MalahGossip8": 20047, + "MalahGossip9": 20048, + "MalahGossip10": 20049, + "MalahGossip11": 20050, + "MalahGossip12": 20051, + "MalahGossip13": 20052, + "NihlathakAct5IntroGossip1": 20053, + "NihlathakAct5IntroAssGossip1": 20054, + "NihlathakAct5IntroNecGossip1": 20055, + "NihlathakGossip1": 20056, + "NihlathakGossip2": 20057, + "NihlathakGossip3": 20058, + "NihlathakGossip4": 20059, + "NihlathakGossip5": 20060, + "NihlathakGossip6": 20061, + "NihlathakGossip7": 20062, + "NihlathakGossip8": 20063, + "NihlathakGossip9": 20064, + "QualKehkAct5IntroGossip1": 20065, + "QualKehkAct5IntroPalGossip1": 20066, + "QualKehkAct5IntroDruGossip1": 20067, + "QualKehkGossip1": 20068, + "QualKehkGossip2": 20069, + "QualKehkGossip3": 20070, + "QualKehkGossip4": 20071, + "QualKehkGossip5": 20072, + "QualKehkGossip6": 20073, + "QualKehkGossip7": 20074, + "QualKehkGossip8": 20075, + "QualKehkGossip9": 20076, + "A5Q1InitLarzuk": 20077, + "A5Q1AfterInitLarzuk": 20078, + "A5Q1AfterInitCain": 20079, + "A5Q1AfterInitAnya": 20080, + "A5Q1AfterInitMalah": 20081, + "A5Q1AfterInitNihlathak": 20082, + "A5Q1AfterInitQualKehk": 20083, + "A5Q1EarlyReturnLarzuk": 20084, + "A5Q1EarlyReturnCain": 20085, + "A5Q1EarlyReturnAnya": 20086, + "A5Q1EarlyReturnMalah": 20087, + "A5Q1EarlyReturnNihlathak": 20088, + "A5Q1EarlyReturnQualKehk": 20089, + "A5Q1SuccessfulLarzuk": 20090, + "A5Q1SuccessfulCain": 20091, + "A5Q1SuccessfulAnya": 20092, + "A5Q1SuccessfulMalah": 20093, + "A5Q1SuccessfulNihlathak": 20094, + "A5Q1SuccessfulQualKehk": 20095, + "A5Q2InitQualKehk": 20096, + "A5Q2AfterInitQualKehk": 20097, + "A5Q2AfterInitCain": 20098, + "A5Q2AfterInitAnya": 20099, + "A5Q2AfterInitLarzuk": 20100, + "A5Q2AfterInitMalah": 20101, + "A5Q2AfterInitNihlathak": 20102, + "A5Q2EarlyReturnQualKehk": 20103, + "A5Q2EarlyReturnQualKehkMan": 20104, + "A5Q2EarlyReturnCain": 20105, + "A5Q2EarlyReturnAnya": 20106, + "A5Q2EarlyReturnLarzuk": 20107, + "A5Q2EarlyReturnMalah": 20108, + "A5Q2EarlyReturnNihlathak": 20109, + "A5Q2SuccessfulQualKehk": 20110, + "A5Q2SuccessfulCain": 20111, + "A5Q2SuccessfulAnya": 20112, + "A5Q2SuccessfulLarzuk": 20113, + "A5Q2SuccessfulMalah": 20114, + "A5Q2SuccessfulNihlathak": 20115, + "A5Q3InitMalah": 20116, + "A5Q3AfterInitMalah": 20117, + "A5Q3AfterInitCain": 20118, + "A5Q3AfterInitLarzuk": 20119, + "A5Q3AfterInitNihlathak": 20120, + "A5Q3AfterInitQualKehk": 20121, + "A5Q3EarlyReturnMalah": 20122, + "A5Q3EarlyReturnCain": 20123, + "A5Q3EarlyReturnLarzuk": 20124, + "A5Q3EarlyReturnNihlathak": 20125, + "A5Q3EarlyReturnQualKehk": 20126, + "A5Q3FoundAnyaMalah": 20127, + "A5Q3FoundAnyaCain": 20128, + "A5Q3FoundAnyaLarzuk": 20129, + "A5Q3FoundAnyaQualKehk": 20130, + "A5Q3FoundAnyaAnya": 20131, + "A5Q3SuccessfulMalah": 20132, + "A5Q3SuccessfulCain": 20133, + "A5Q3SuccessfulLarzuk": 20134, + "A5Q3SuccessfulQualKehk": 20135, + "A5Q3SuccessfulAnya": 20136, + "A5Q4InitAnya": 20137, + "A5Q4AfterInitAnya": 20138, + "A5Q4AfterInitCain": 20139, + "A5Q4AfterInitMalah": 20140, + "A5Q4AfterInitLarzuk": 20141, + "A5Q4AfterInitQualKehk": 20142, + "A5Q4EarlyReturnAnya": 20143, + "A5Q4EarlyReturnCain": 20144, + "A5Q4EarlyReturnLarzuk": 20145, + "A5Q4EarlyReturnMalah": 20146, + "A5Q4EarlyReturnQualKehk": 20147, + "A5Q4SuccessfulAnya": 20148, + "A5Q4SuccessfulCain": 20149, + "A5Q4SuccessfulLarzuk": 20150, + "A5Q4SuccessfulMalah": 20151, + "A5Q4SuccessfulQualKehk": 20152, + "A5Q5InitQualKehk": 20153, + "A5Q5AfterInitQualKehk": 20154, + "A5Q5AfterInitCain": 20155, + "A5Q5AfterInitAnya": 20156, + "A5Q5AfterInitLarzuk": 20157, + "A5Q5AfterInitMalah": 20158, + "A5Q5EarlyReturnQualKehk": 20159, + "A5Q5EarlyReturnCain": 20160, + "A5Q5EarlyReturnAnya": 20161, + "A5Q5EarlyReturnLarzuk": 20162, + "A5Q5EarlyReturnMalah": 20163, + "A5Q5SuccessfulQualKehk": 20164, + "A5Q5SuccessfulCain": 20165, + "A5Q5SuccessfulAnya": 20166, + "A5Q5SuccessfulLarzuk": 20167, + "A5Q5SuccessfulMalah": 20168, + "A5Q6InitAncients": 20169, + "A5Q6EarlyReturnCain": 20170, + "A5Q6EarlyReturnLarzuk": 20171, + "A5Q6EarlyReturnMalah": 20172, + "A5Q6EarlyReturnAnya": 20173, + "A5Q6EarlyReturnQualKehk": 20174, + "A5Q6SuccessfulTyrael": 20175, + "A5Q6SuccessfulAnya": 20176, + "A5Q6SuccessfulCain": 20177, + "A5Q6SuccessfulLarzuk": 20178, + "A5Q6SuccessfulMalah": 20179, + "A5Q6SuccessfulQualKehk": 20180, + "ktr": 20181, + "wrb": 20182, + "ces": 20183, + "clw": 20184, + "btl": 20185, + "skr": 20186, + "9ar": 20187, + "9wb": 20188, + "9xf": 20189, + "9cs": 20190, + "9lw": 20191, + "9tw": 20192, + "9qr": 20193, + "7ar": 20194, + "7wb": 20195, + "7xf": 20196, + "7cs": 20197, + "7lw": 20198, + "7tw": 20199, + "7qr": 20200, + "7ha": 20201, + "7ax": 20202, + "72a": 20203, + "7mp": 20204, + "7wa": 20205, + "7la": 20206, + "7ba": 20207, + "7bt": 20208, + "7ga": 20209, + "7gi": 20210, + "7wn": 20211, + "7yw": 20212, + "7bw": 20213, + "7gw": 20214, + "7cl": 20215, + "7sc": 20216, + "7qs": 20217, + "7ws": 20218, + "7sp": 20219, + "7ma": 20220, + "7mt": 20221, + "7fl": 20222, + "7wh": 20223, + "7m7": 20224, + "7gm": 20225, + "7ss": 20226, + "7sm": 20227, + "7sb": 20228, + "7fc": 20229, + "7cr": 20230, + "7bs": 20231, + "7ls": 20232, + "7wd": 20233, + "72h": 20234, + "7cm": 20235, + "7gs": 20236, + "7b7": 20237, + "7fb": 20238, + "7gd": 20239, + "7dg": 20240, + "7di": 20241, + "7kr": 20242, + "7bl": 20243, + "7tk": 20244, + "7ta": 20245, + "7bk": 20246, + "7b8": 20247, + "7ja": 20248, + "7pi": 20249, + "7s7": 20250, + "7gl": 20251, + "7ts": 20252, + "7sr": 20253, + "7tr": 20254, + "7br": 20255, + "7st": 20256, + "7p7": 20257, + "7o7": 20258, + "7vo": 20259, + "7s8": 20260, + "7pa": 20261, + "7h7": 20262, + "7wc": 20263, + "6ss": 20264, + "6ls": 20265, + "6cs": 20266, + "6bs": 20267, + "6ws": 20268, + "6sb": 20269, + "6hb": 20270, + "6lb": 20271, + "6cb": 20272, + "6s7": 20273, + "6l7": 20274, + "6sw": 20275, + "6lw": 20276, + "6lx": 20277, + "6mx": 20278, + "6hx": 20279, + "6rx": 20280, + "am1": 20292, + "am2": 20293, + "am3": 20294, + "am4": 20295, + "am5": 20296, + "ob6": 20297, + "ob7": 20298, + "ob8": 20299, + "ob9": 20300, + "oba": 20301, + "am6": 20302, + "am7": 20303, + "am8": 20304, + "am9": 20305, + "ama": 20306, + "obb": 20307, + "obc": 20308, + "obd": 20309, + "obe": 20310, + "obf": 20311, + "amb": 20312, + "amc": 20313, + "amd": 20314, + "ame": 20315, + "amf": 20316, + "ba1": 20322, + "ba2": 20323, + "ba3": 20324, + "ba4": 20325, + "ba5": 20326, + "pa1": 20327, + "pa2": 20328, + "pa3": 20329, + "pa4": 20330, + "pa5": 20331, + "ci0": 20337, + "ci1": 20338, + "ci2": 20339, + "ci3": 20340, + "uap": 20341, + "ukp": 20342, + "ulm": 20343, + "uhl": 20344, + "uhm": 20345, + "urn": 20346, + "usk": 20347, + "uui": 20348, + "uea": 20349, + "ula": 20350, + "utu": 20351, + "ung": 20352, + "ucl": 20353, + "uhn": 20354, + "urs": 20355, + "upl": 20356, + "ult": 20357, + "uld": 20358, + "uth": 20359, + "uul": 20360, + "uar": 20361, + "utp": 20362, + "uuc": 20363, + "uml": 20364, + "urg": 20365, + "uit": 20366, + "uow": 20367, + "uts": 20368, + "ulg": 20369, + "uvg": 20370, + "umg": 20371, + "utg": 20372, + "uhg": 20373, + "ulb": 20374, + "uvb": 20375, + "umb": 20376, + "utb": 20377, + "uhb": 20378, + "ulc": 20379, + "uvc": 20380, + "umc": 20381, + "utc": 20382, + "uhc": 20383, + "uh9": 20384, + "ush": 20385, + "upk": 20386, + "dr9": 20387, + "dr7": 20388, + "dr8": 20389, + "dr6": 20390, + "dra": 20391, + "ba6": 20392, + "ba7": 20393, + "ba8": 20394, + "ba9": 20395, + "baa": 20396, + "pa6": 20397, + "pa7": 20398, + "pa8": 20399, + "pa9": 20400, + "paa": 20401, + "ne6": 20402, + "ne7": 20403, + "ne8": 20404, + "ne9": 20405, + "nea": 20406, + "dre": 20407, + "drc": 20408, + "drd": 20409, + "drb": 20410, + "drf": 20411, + "bab": 20412, + "bac": 20413, + "bad": 20414, + "bae": 20415, + "baf": 20416, + "pab": 20417, + "pac": 20418, + "pae": 20419, + "paf": 20420, + "neb": 20421, + "nec": 20422, + "ned": 20423, + "nee": 20424, + "nef": 20425, + "jew": 20433, + "cm1": 20435, + "cm2": 20436, + "cm3": 20437, + "Charmdes": 20438, + "ice": 20439, + "r33": 20440, + "r32": 20441, + "r31": 20442, + "r30": 20443, + "r29": 20444, + "r28": 20445, + "r27": 20446, + "r26": 20447, + "r25": 20448, + "r24": 20449, + "r23": 20450, + "r22": 20451, + "r21": 20452, + "r20": 20453, + "r19": 20454, + "r18": 20455, + "r17": 20456, + "r16": 20457, + "r15": 20458, + "r14": 20459, + "r13": 20460, + "r12": 20461, + "r11": 20462, + "r10": 20463, + "r09": 20464, + "r08": 20465, + "r07": 20466, + "r06": 20467, + "r05": 20468, + "r04": 20469, + "r03": 20470, + "r02": 20471, + "r01": 20472, + "r33L": 20473, + "r32L": 20474, + "r31L": 20475, + "r30L": 20476, + "r29L": 20477, + "r28L": 20478, + "r27L": 20479, + "r26L": 20480, + "r25L": 20481, + "r24L": 20482, + "r23L": 20483, + "r22L": 20484, + "r21L": 20485, + "r20L": 20486, + "r19L": 20487, + "r18L": 20488, + "r17L": 20489, + "r16L": 20490, + "r15L": 20491, + "r14L": 20492, + "r13L": 20493, + "r12L": 20494, + "r11L": 20495, + "r10L": 20496, + "r09L": 20497, + "r08L": 20498, + "r07L": 20499, + "r06L": 20500, + "r05L": 20501, + "r04L": 20502, + "r03L": 20503, + "r02L": 20504, + "r01L": 20505, + "RuneQuote": 20506, + "Runeword1": 20507, + "Runeword2": 20508, + "Runeword3": 20509, + "Runeword4": 20510, + "Runeword5": 20511, + "Runeword6": 20512, + "Runeword7": 20513, + "Runeword8": 20514, + "Runeword9": 20515, + "Runeword10": 20516, + "Runeword11": 20517, + "Runeword12": 20518, + "Runeword13": 20519, + "Runeword14": 20520, + "Runeword15": 20521, + "Runeword16": 20522, + "Runeword17": 20523, + "Runeword18": 20524, + "Runeword19": 20525, + "Runeword20": 20526, + "Runeword21": 20527, + "Runeword22": 20528, + "Runeword23": 20529, + "Runeword24": 20530, + "Runeword25": 20531, + "Runeword26": 20532, + "Runeword27": 20533, + "Runeword28": 20534, + "Runeword29": 20535, + "Runeword30": 20536, + "Runeword31": 20537, + "Runeword32": 20538, + "Runeword33": 20539, + "Runeword34": 20540, + "Runeword35": 20541, + "Runeword36": 20542, + "Runeword37": 20543, + "Runeword38": 20544, + "Runeword39": 20545, + "Runeword40": 20546, + "Runeword41": 20547, + "Runeword42": 20548, + "Runeword43": 20549, + "Runeword44": 20550, + "Runeword45": 20551, + "Runeword46": 20552, + "Runeword47": 20553, + "Runeword48": 20554, + "Runeword49": 20555, + "Runeword50": 20556, + "Runeword51": 20557, + "Runeword52": 20558, + "Runeword53": 20559, + "Runeword54": 20560, + "Runeword55": 20561, + "Runeword56": 20562, + "Runeword57": 20563, + "Runeword58": 20564, + "Runeword59": 20565, + "Runeword60": 20566, + "Runeword61": 20567, + "Runeword62": 20568, + "Runeword63": 20569, + "Runeword64": 20570, + "Runeword65": 20571, + "Runeword66": 20572, + "Runeword67": 20573, + "Runeword68": 20574, + "Runeword69": 20575, + "Runeword70": 20576, + "Runeword71": 20577, + "Runeword72": 20578, + "Runeword73": 20579, + "Runeword74": 20580, + "Runeword75": 20581, + "Runeword76": 20582, + "Runeword77": 20583, + "Runeword78": 20584, + "Runeword79": 20585, + "Runeword81": 20586, + "Runeword82": 20587, + "Runeword83": 20588, + "Runeword84": 20589, + "Runeword85": 20590, + "Runeword86": 20591, + "Runeword87": 20592, + "Runeword88": 20593, + "Runeword89": 20594, + "Runeword90": 20595, + "Runeword91": 20596, + "Runeword92": 20597, + "Runeword93": 20598, + "Runeword94": 20599, + "Runeword95": 20600, + "Runeword96": 20601, + "Runeword97": 20602, + "Runeword98": 20603, + "Runeword99": 20604, + "Runeword100": 20605, + "Runeword101": 20606, + "Runeword102": 20607, + "Runeword103": 20608, + "Runeword104": 20609, + "Runeword105": 20610, + "Runeword106": 20611, + "Runeword107": 20612, + "Runeword108": 20613, + "Runeword109": 20614, + "Runeword110": 20615, + "Runeword111": 20616, + "Runeword112": 20617, + "Runeword113": 20618, + "Runeword114": 20619, + "Runeword115": 20620, + "Runeword116": 20621, + "Runeword117": 20622, + "Runeword118": 20623, + "Runeword119": 20624, + "Runeword120": 20625, + "Runeword121": 20626, + "Runeword122": 20627, + "Runeword123": 20628, + "Runeword124": 20629, + "Runeword125": 20630, + "Runeword126": 20631, + "Runeword127": 20632, + "Runeword128": 20633, + "Runeword129": 20634, + "Runeword130": 20635, + "Runeword131": 20636, + "Runeword132": 20637, + "Runeword133": 20638, + "Runeword134": 20639, + "Runeword135": 20640, + "Runeword136": 20641, + "Runeword137": 20642, + "Runeword138": 20643, + "Runeword139": 20644, + "Runeword140": 20645, + "Runeword141": 20646, + "Runeword142": 20647, + "Runeword143": 20648, + "Runeword144": 20649, + "Runeword145": 20650, + "Runeword146": 20651, + "Runeword147": 20652, + "Runeword148": 20653, + "Runeword149": 20654, + "Runeword150": 20655, + "Runeword151": 20656, + "Runeword152": 20657, + "Runeword153": 20658, + "Runeword154": 20659, + "Runeword155": 20660, + "Runeword156": 20661, + "Runeword157": 20662, + "Runeword158": 20663, + "Runeword159": 20664, + "Runeword160": 20665, + "Runeword161": 20666, + "Runeword162": 20667, + "Runeword163": 20668, + "Runeword164": 20669, + "Runeword165": 20670, + "Runeword166": 20671, + "Runeword167": 20672, + "Runeword168": 20673, + "Runeword169": 20674, + "Runeword170": 20675, + "spe": 20676, + "scz": 20677, + "sol": 20678, + "qll": 20679, + "fng": 20680, + "flg": 20681, + "tal": 20682, + "hrn": 20683, + "eyz": 20684, + "jaw": 20685, + "brz": 20686, + "hrt": 20687, + "Stout": 20688, + "Antimagic": 20689, + "Null": 20690, + "Godly": 20691, + "Ivory": 20692, + "Eburin": 20693, + "Blanched": 20694, + "Stalwart": 20695, + "Burly": 20696, + "Dense": 20697, + "Thin": 20698, + "Compact": 20699, + "Witch-hunter's": 20700, + "Magekiller's": 20701, + "Hierophant's": 20702, + "Shaman's": 20703, + "Pestilent": 20704, + "Toxic": 20705, + "Corosive": 20706, + "Envenomed": 20707, + "Septic": 20708, + "Shocking": 20709, + "Arcing": 20710, + "Buzzing": 20711, + "Static": 20712, + "Scorching": 20713, + "Flaming": 20714, + "Smoking": 20715, + "Smoldering": 20716, + "Ember": 20717, + "Hibernal": 20718, + "Boreal": 20719, + "Shivering": 20720, + "Snowflake": 20721, + "Mnemonic": 20722, + "Visionary": 20723, + "Eagleeye": 20724, + "Hawkeye": 20725, + "Falconeye": 20726, + "Sparroweye": 20727, + "Robineye": 20728, + "Paradox": 20729, + "Shouting": 20730, + "Yelling": 20731, + "Calling": 20732, + "Loud": 20733, + "Trump": 20734, + "Joker's": 20735, + "Jester's": 20736, + "Jack's": 20737, + "Knave's": 20738, + "Paleocene": 20739, + "Eocene": 20740, + "Oligocene": 20741, + "Miocene": 20742, + "Kenshi's": 20743, + "Sensei's": 20744, + "Shogukusha's": 20745, + "Psychic": 20746, + "Mentalist's": 20747, + "Cunning": 20748, + "Trickster's": 20749, + "Entrapping": 20750, + "Gaea's": 20751, + "Terra's": 20752, + "Nature's": 20753, + "Communal": 20754, + "Feral": 20755, + "Spiritual": 20756, + "Keeper's": 20757, + "Caretaker's": 20758, + "Trainer's": 20759, + "Veteran's": 20760, + "Expert's": 20761, + "Furious": 20762, + "Raging": 20763, + "Echoing": 20764, + "Resonant": 20765, + "Sounding": 20766, + "Guardian's": 20767, + "Warder's": 20768, + "Preserver's": 20769, + "Marshal's": 20770, + "Commander's": 20771, + "Captain's": 20772, + "Rose Branded": 20773, + "Hawk Branded": 20774, + "Lion Branded": 20775, + "Golemlord's": 20776, + "Vodoun": 20777, + "Graverobber's": 20778, + "Venomous": 20779, + "Noxious": 20780, + "Fungal": 20781, + "Accursed": 20782, + "Blighting": 20783, + "Hexing": 20784, + "Glacial": 20785, + "Freezing": 20786, + "Chilling": 20787, + "Powered": 20788, + "Charged": 20789, + "Sparking": 20790, + "Volcanic": 20791, + "Blazing": 20792, + "Burning": 20793, + "Lancer's": 20794, + "Spearmaiden's": 20795, + "Harpoonist's": 20796, + "Athlete's": 20797, + "Gymnast's": 20798, + "Acrobat's": 20799, + "Bowyer's": 20800, + "Diamond": 20801, + "Celestial": 20802, + "Elysian": 20803, + "Astral": 20804, + "Unearthly": 20805, + "Arcadian": 20806, + "Jeweler's": 20807, + "Artificer's": 20808, + "Mechanist's": 20809, + "Aureolin": 20810, + "Victorious": 20811, + "Ambergris": 20812, + "Camphor": 20813, + "Lapis Lazuli": 20814, + "Chromatic": 20815, + "Scintillating": 20816, + "Turquoise": 20817, + "Jacinth": 20818, + "Zircon": 20819, + "Bahamut's": 20820, + "Great Wyrm's": 20821, + "Felicitous": 20822, + "Lucky": 20823, + "Wailing": 20824, + "Screaming": 20825, + "Grandmaster's": 20826, + "Master's": 20827, + "Argent": 20828, + "Tin": 20829, + "Nickel": 20830, + "Maroon": 20831, + "Chestnut": 20832, + "Vigorous": 20833, + "Brown": 20834, + "Dun": 20835, + "Realgar": 20836, + "Rusty": 20837, + "Cinnabar": 20838, + "Vermillion": 20839, + "Carmine": 20840, + "Carbuncle": 20841, + "Serrated": 20842, + "Scarlet": 20843, + "Bloody": 20844, + "Sanguinary": 20845, + "Pearl": 20846, + "Divine": 20847, + "Hallowed": 20848, + "Sacred": 20849, + "Pure": 20850, + "Consecrated": 20851, + "Assamic": 20852, + "Frantic": 20853, + "Hellatial": 20854, + "Quixotic": 20855, + "Smiting": 20856, + "Steller": 20857, + "Stinging": 20858, + "Singing": 20859, + "Timeless": 20860, + "Original": 20861, + "Corporal": 20862, + "Lawful": 20863, + "Chaotic": 20864, + "Fierce": 20865, + "Ferocious": 20866, + "Perpetual": 20867, + "Continuous": 20868, + "Laden": 20869, + "Pernicious": 20870, + "Harmful": 20871, + "Evil": 20872, + "Insidious": 20873, + "Malicious": 20874, + "Spiteful": 20875, + "Precocious": 20876, + "Majestic": 20877, + "Sanguine": 20878, + "Monumental": 20879, + "Irresistible": 20880, + "Festering": 20881, + "Musty": 20882, + "Dusty": 20883, + "Decaying": 20884, + "Rotting": 20885, + "Infectious": 20886, + "Foggy": 20887, + "Cloudy": 20888, + "Hazy": 20889, + "Punishing": 20890, + "Obsidian": 20891, + "Royal": 20892, + "Frigid": 20893, + "Moldy": 20894, + "Gaudy": 20895, + "Impecable": 20896, + "Soulless": 20897, + "Heated": 20898, + "Lasting": 20899, + "Scorched": 20900, + "Marred": 20901, + "Lilac": 20902, + "Rose": 20903, + "Shimmering": 20904, + "Wicked": 20906, + "Strange": 20907, + "Repulsive": 20908, + "Reclusive": 20909, + "Rude": 20911, + "Hermetic": 20912, + "Rainbow": 20913, + "Colorful": 20914, + "Stinky": 20915, + "Gritty": 20916, + "of Warming": 20917, + "of Stoicism": 20918, + "of the Dynamo": 20919, + "of Grounding": 20920, + "of Insulation": 20921, + "of Resistance": 20922, + "of Faith": 20923, + "of Fire Quenching": 20924, + "of Amianthus": 20925, + "of Incombustibility": 20926, + "of Coolness": 20927, + "of Anima": 20928, + "of Life Everlasting": 20929, + "of Sunlight": 20930, + "of Frozen Orb": 20931, + "of Hydra Shield": 20932, + "of Chilling Armor": 20933, + "of Blizzard": 20934, + "of Energy Shield": 20935, + "of Thunder Storm": 20936, + "of Meteor": 20937, + "of Glacial Spike": 20938, + "of Teleport Shield": 20939, + "of Chain Lightning": 20940, + "of Enchant": 20941, + "of Fire Wall": 20942, + "of Shiver Armor": 20943, + "of Nova Shield": 20944, + "of Nova": 20945, + "of Fire Ball": 20946, + "of Blaze": 20947, + "of Ice Blast": 20948, + "of Frost Shield": 20949, + "of Telekinesis": 20950, + "of Static Field": 20951, + "of Frozen Armor": 20952, + "of Icebolt": 20953, + "of Charged Shield": 20954, + "of Firebolts": 20955, + "of the Elements": 20956, + "of the Cobra": 20957, + "of the Efreeti": 20958, + "of the Phoenix": 20959, + "of the Yeti": 20960, + "of Grace and Power": 20961, + "of Grace": 20962, + "of Power": 20963, + "of the Elephant": 20964, + "of Memory": 20965, + "of the Kraken1": 20966, + "of Propogation": 20967, + "of Replenishing": 20968, + "of Ages": 20969, + "of Fast Repair": 20970, + "of Self-Repair": 20971, + "of Acceleration": 20972, + "of Traveling": 20973, + "of Virility": 20974, + "of Atlus": 20975, + "of Freedom": 20976, + "of the Lamprey": 20977, + "of Hope": 20978, + "of Spirit": 20979, + "of Vita": 20980, + "of Substinence": 20981, + "of the Whale": 20982, + "of the Squid": 20983, + "of the Colossus1": 20984, + "of Knowledge": 20985, + "of Enlightenment": 20986, + "of Prosperity": 20987, + "of Good Luck": 20988, + "of Luck": 20989, + "of Avarice": 20990, + "of Honor": 20991, + "of Revivification": 20992, + "of Truth": 20993, + "of Daring": 20994, + "of Nirvana": 20995, + "of Envy": 20996, + "of Anthrax": 20997, + "of Bliss": 20998, + "of Joy": 20999, + "of Transcendence": 21000, + "of Wrath": 21001, + "of Ire": 21002, + "of Evisceration": 21003, + "of Butchery": 21004, + "of Ennui": 21005, + "of Storms": 21006, + "of Passion": 21007, + "of Incineration": 21008, + "of Frigidity": 21009, + "of Winter": 21010, + "of the Icicle": 21011, + "of Fervor": 21012, + "of Malice": 21013, + "of Swords": 21014, + "of Razors": 21015, + "of Desire": 21016, + "of the Sirocco": 21017, + "of the Dunes": 21018, + "of Thawing": 21019, + "Of the Choir": 21020, + "Of the Sniper": 21021, + "Of the Stiletto": 21022, + "Of Bile": 21023, + "Of Blitzen": 21024, + "Of Cremation": 21025, + "Of Darkness": 21026, + "Of Disease": 21027, + "Of Remorse": 21028, + "Of Terror": 21029, + "Of the Sky": 21030, + "Of Valhalla": 21031, + "Of Waste": 21032, + "Of Nobility": 21033, + "Of Karma": 21034, + "Of Grounding": 21035, + "Of the River": 21036, + "Of the Lake": 21037, + "Of the Ocean": 21038, + "Of the Bayou": 21039, + "Of the Stream": 21040, + "Of the Lady": 21041, + "Of the Maiden": 21042, + "Of the Virgin": 21043, + "Of the Hag": 21044, + "Of the Witch": 21045, + "Of Judgement": 21046, + "Of Illusion": 21047, + "Of Elusion": 21048, + "Of Combat": 21049, + "Of Attrition": 21050, + "Of Abrasion": 21051, + "Of Erosion": 21052, + "Of Searing": 21053, + "Of Stone": 21054, + "Of Stature": 21055, + "Of Fortication": 21056, + "Of Quickening": 21057, + "Of Dispatch": 21058, + "Of Daring": 21059, + "Of Dread": 21060, + "Of Suffering": 21061, + "Of Doom": 21062, + "Of Vengence": 21063, + "Of Redemption": 21064, + "Of Luck": 21065, + "Of the Avenger": 21066, + "Of the Specter": 21067, + "Of the Ghost": 21068, + "Of the Infantry": 21069, + "Of the Mosquito": 21070, + "Of the Gnat": 21071, + "Of the Fly": 21072, + "Of the Plague": 21073, + "Of Twilight": 21074, + "Of Dusk": 21075, + "Of Dawn": 21076, + "Of the Imbecile": 21077, + "Of the Idiot": 21078, + "Of the Retard": 21079, + "Of the Jujube": 21080, + "Of the Obscenity": 21081, + "Of Quota": 21082, + "Of the Maggot": 21083, + "Of Horror": 21084, + "Of Baddass": 21085, + "Of the Beast": 21086, + "Of Cruelty": 21087, + "Of Badness": 21088, + "Of the Horde": 21089, + "Of the Forest": 21090, + "Of the Lilly": 21091, + "Of the Grassy Gnoll": 21092, + "Of the Stars": 21093, + "Of the Moon": 21094, + "Of Love": 21095, + "Of the Unicorn": 21096, + "Of the Walrus": 21097, + "Of the Earth": 21098, + "Of Vines": 21099, + "Of Honor": 21100, + "Of Tribute": 21101, + "Of Credit": 21102, + "Of Admiration": 21103, + "Of Sweetness": 21104, + "Of Beauty": 21105, + "Of Pilfering": 21106, + "of Damage Amplification": 21107, + "of Hurricane": 21108, + "of Armageddon": 21109, + "of Tornado": 21110, + "of Volcano": 21111, + "of Twister": 21112, + "of Cyclone Armor": 21113, + "of Eruption": 21114, + "of Molten Boulders": 21115, + "of Firestorms": 21116, + "of Battle Command": 21117, + "of War Cry": 21118, + "of Grim Ward": 21119, + "of Battle Orders": 21120, + "of Battle Cry": 21121, + "of Concentration": 21122, + "of Item Finding": 21123, + "of Stunning": 21124, + "of Shouting": 21125, + "of Taunting": 21126, + "of Potion Finding": 21127, + "of Howling": 21128, + "of Fist of the Heavens": 21129, + "of Holy Shield": 21130, + "of Conversion": 21131, + "of Blessed Hammers": 21132, + "of Vengeance": 21133, + "of Charging": 21134, + "of Zeal": 21135, + "of Holy Bolts": 21136, + "of Sacrifice": 21137, + "of Fire Golem Summoning": 21138, + "of Bone Spirits": 21139, + "of Poison Novas": 21140, + "of Lower Resistance": 21141, + "of Iron Golem Creation": 21142, + "of Bone Imprisonment": 21143, + "of Decrepification": 21144, + "of Attraction": 21145, + "of Blood Golem Summoning": 21146, + "of Bone Spears": 21147, + "of Poison Explosion": 21148, + "of Life Tap": 21149, + "of Confusion": 21150, + "of Raise Skeletal Mages": 21151, + "of Bone Walls": 21152, + "of Terror": 21153, + "of Iron Maiden": 21154, + "of Clay Golem Summoning": 21155, + "of Corpse Explosions": 21156, + "of Poison Dagger": 21157, + "of Weaken": 21158, + "of Dim Vision": 21159, + "of Raise Skeletons": 21160, + "of Bone Armor": 21161, + "of Teeth": 21162, + "of Amplify Damage": 21163, + "of Frozen Orbs": 21164, + "of Hydras": 21165, + "of Blizzards": 21166, + "of Meteors": 21167, + "of Glacial Spikes": 21168, + "of Teleportation": 21169, + "of Enchantment": 21170, + "of Fire Walls": 21171, + "of Novas": 21172, + "of Fire Balls": 21173, + "of Blazing": 21174, + "of Ice Blasts": 21175, + "of Frost Novas": 21176, + "of Ice Bolts": 21177, + "of Charged Bolts": 21178, + "of Fire Bolts": 21179, + "of Lightning Fury": 21180, + "of Lightning Spear": 21181, + "of Freezing Arrows": 21182, + "of Fending": 21183, + "of Immolating Arrows": 21184, + "of Plague Javelin": 21185, + "of Charged Spear": 21186, + "of Guided Arrows": 21187, + "of Ice Arrows": 21188, + "of Lightning Javelin": 21189, + "of Impaling Spear": 21190, + "of Slow Missiles": 21191, + "of Exploding Arrows": 21192, + "of Poison Javelin": 21193, + "of Power Spear": 21194, + "of Multiple Shot": 21195, + "of Cold Arrows": 21196, + "of Jabbing": 21197, + "of Inner Sight": 21198, + "of Fire Arrows": 21199, + "of Magic Arrows": 21200, + "Of self-repair": 21201, + "of Dawn": 21202, + "of Inertia": 21203, + "of Joyfulness": 21204, + "ModStre8a": 21205, + "ModStre8b": 21206, + "ModStre8c": 21207, + "ModStre8d": 21208, + "ModStre8e": 21209, + "ModStre8f": 21210, + "ModStre8g": 21211, + "ModStre8h": 21212, + "ModStre8i": 21213, + "ModStre8j": 21214, + "ModStre8k": 21215, + "ModStre8l": 21216, + "ModStre8m": 21217, + "ModStre8n": 21218, + "ModStre8o": 21219, + "ModStre8p": 21220, + "ModStre8q": 21221, + "ModStre8r": 21222, + "ModStre8s": 21223, + "ModStre8t": 21224, + "ModStre8u": 21225, + "ModStre8v": 21226, + "ModStre8w": 21227, + "ModStre8x": 21228, + "ModStre8y": 21229, + "ModStre8z": 21230, + "ModStre9a": 21231, + "ModStre9b": 21232, + "ModStre9c": 21233, + "ModStre9d": 21234, + "ModStre9e": 21235, + "ModStre9f": 21236, + "ModStre9g": 21237, + "ModStre9h": 21238, + "ModStre9i": 21239, + "ModStre9s": 21240, + "ModStre9t": 21241, + "ModStre9u": 21242, + "ModStre9v": 21243, + "ModStre9w": 21244, + "ModStre9x": 21245, + "ModStre9y": 21246, + "ModStre9z": 21247, + "ModStre10a": 21248, + "ModStre10b": 21249, + "ModStre10c": 21250, + "ModStre10d": 21251, + "ModStre10e": 21252, + "ModStre10f": 21253, + "ModStre10g": 21254, + "ModStre10h": 21255, + "ModStre10i": 21256, + "ModStre10j": 21257, + "WeaponDescOrb": 21259, + "ItemexpED": 21260, + "StrGemX1": 21261, + "StrGemX2": 21262, + "StrGemX3": 21263, + "StrGemX4": 21264, + "GemeffectX11": 21265, + "GemeffectX12": 21266, + "GemeffectX13": 21267, + "GemeffectX21": 21268, + "GemeffectX22": 21269, + "GemeffectX23": 21270, + "GemeffectX31": 21271, + "GemeffectX32": 21272, + "GemeffectX33": 21273, + "GemeffectX41": 21274, + "GemeffectX42": 21275, + "GemeffectX43": 21276, + "GemeffectX51": 21277, + "GemeffectX52": 21278, + "GemeffectX53": 21279, + "GemeffectX61": 21280, + "GemeffectX62": 21281, + "GemeffectX63": 21282, + "GemeffectX71": 21283, + "GemeffectX72": 21284, + "GemeffectX73": 21285, + "Coldkill": 21286, + "Butchers Cleaver": 21287, + "Butcher's Pupil": 21288, + "Islestrike": 21289, + "Pompe's Wrath": 21290, + "Guardian Naga": 21291, + "Warlord's Trust": 21292, + "Spellsteel": 21293, + "Stormrider": 21294, + "Boneslayer Blade": 21295, + "The Minotaur": 21296, + "Suicide Branch": 21297, + "Cairn Shard": 21298, + "Arm of King Leoric": 21299, + "Blackhand Key": 21300, + "Dark Clan Crusher": 21301, + "Drulan's Tongue": 21302, + "Zakrum's Hand": 21303, + "The Fetid Sprinkler": 21304, + "Hand of Blessed Light": 21305, + "Fleshrender": 21306, + "Sureshrill Frost": 21307, + "Moonfall": 21308, + "Baezils Vortex": 21309, + "Earthshaker": 21310, + "Bloodtree Stump": 21311, + "The Gavel of Pain": 21312, + "Bloodletter": 21313, + "Coldsteal Eye": 21314, + "Hexfire": 21315, + "Blade of Ali Baba": 21316, + "Riftslash": 21317, + "Headstriker": 21318, + "Plague Bearer": 21319, + "The Atlantien": 21320, + "Crainte Vomir": 21321, + "Bing Sz Wang": 21322, + "The Vile Husk": 21323, + "Cloudcrack": 21324, + "Todesfaelle Flamme": 21325, + "Swordguard": 21326, + "Spineripper": 21327, + "Heart Carver": 21328, + "Blackbog's Sharp": 21329, + "Stormspike": 21330, + "The Impaler": 21331, + "Kelpie Snare": 21332, + "Soulfeast Tine": 21333, + "Hone Sundan": 21334, + "Spire of Honor": 21335, + "The Meat Scraper": 21336, + "Blackleach Blade": 21337, + "Athena's Wrath": 21338, + "Pierre Tombale Couant": 21339, + "Husoldal Evo": 21340, + "Grim's Burning Dead": 21341, + "Ribcracker": 21342, + "Chromatic Ire": 21343, + "Warpspear": 21344, + "Skullcollector": 21345, + "Skystrike": 21346, + "Kuko Shakaku": 21347, + "Endlessshail": 21348, + "Whichwild String": 21349, + "Godstrike Arch": 21350, + "Langer Briser": 21351, + "Pus Spiter": 21352, + "Buriza-Do Kyanon": 21353, + "Vampiregaze": 21354, + "String of Ears": 21355, + "Gorerider": 21356, + "Lavagout": 21357, + "Venom Grip": 21358, + "Visceratuant": 21359, + "Guardian Angle": 21360, + "Shaftstop": 21361, + "Skin of the Vipermagi": 21362, + "Blackhorn": 21363, + "Valkiry Wing": 21364, + "Peasent Crown": 21365, + "Demon Machine": 21366, + "Magewrath": 21367, + "Cliffkiller": 21368, + "Riphook": 21369, + "Razorswitch": 21370, + "Meatscrape": 21371, + "Coldsteel Eye": 21372, + "Pitblood Thirst": 21373, + "Gaya Wand": 21374, + "Ondal's Wisdom": 21375, + "Geronimo's Fury": 21376, + "Charsi's Favor": 21377, + "Doppleganger's Shadow": 21378, + "Deathbit": 21379, + "Warshrike": 21380, + "Gutsiphon": 21381, + "Razoredge": 21382, + "Stonerattle": 21383, + "Marrowgrinder": 21384, + "Gore Ripper": 21385, + "Bush Wacker": 21386, + "Demonlimb": 21387, + "Steelshade": 21388, + "Tomb Reaver": 21389, + "Death's Web": 21390, + "Gaia's Wrath": 21391, + "Khalim's Vengance": 21392, + "Angel's Song": 21393, + "The Reedeemer": 21394, + "Fleshbone": 21395, + "Odium": 21396, + "Blood Comet": 21397, + "Bonehew": 21398, + "Steelrend": 21399, + "Stone Crusher": 21400, + "Bul-Kathos' Might": 21401, + "Arioc's Needle": 21402, + "Shadowdancer": 21403, + "Indiego's Fancy": 21404, + "Aladdin's Eviserator": 21405, + "Tyrael's Mercy": 21406, + "Souldrain": 21407, + "Runemaster": 21408, + "Deathcleaver": 21409, + "Executioner's Justice": 21410, + "Wallace's Tear": 21411, + "Leviathan": 21412, + "The Wanderer's Blade": 21413, + "Qual'Kek's Enforcer": 21414, + "Dawnbringer": 21415, + "Dragontooth": 21416, + "Wisp": 21417, + "Gargoyle's Bite": 21418, + "Lacerator": 21419, + "Mang Song's Lesson": 21420, + "Viperfork": 21421, + "Blood Chalice": 21422, + "El Espiritu": 21423, + "The Long Rod": 21424, + "Demonhorn's Edge": 21425, + "The Ensanguinator": 21426, + "The Reaper's Toll": 21427, + "Spiritkeeper": 21428, + "Hellrack": 21429, + "Alma Negra": 21430, + "Darkforge Spawn": 21431, + "Rockhew": 21432, + "Sankenkur's Resurrection": 21433, + "Erion's Bonehandle": 21434, + "The Archon Magus": 21435, + "Widow maker": 21436, + "Catgut": 21437, + "Ghostflame": 21438, + "Shadowkiller": 21439, + "Bling Bling": 21440, + "Nebucaneezer's Storm": 21441, + "Griffon's Eye": 21442, + "Eaglewind": 21443, + "Windhammer": 21444, + "Thunderstroke": 21445, + "Giantmaimer": 21446, + "Demon's Arch": 21447, + "The Scalper": 21448, + "Bloodmoon": 21449, + "Djinnslayer": 21450, + "Cranebeak": 21451, + "Iansang's Frenzy": 21452, + "Warhound": 21453, + "Gulletwound": 21454, + "Headhunter's Glory": 21455, + "Mordoc's marauder": 21456, + "Talberd's Law": 21457, + "Amodeus's Manipulator": 21458, + "Darksoul": 21459, + "The Black Adder": 21460, + "Earthshifter": 21461, + "Nature's Peace": 21462, + "Horazon's Chalice": 21463, + "Seraph's Hymn": 21464, + "Zakarum's Salvation": 21465, + "Fleshripper": 21466, + "Stonerage": 21467, + "Blood Rain": 21468, + "Horizon's Tornado": 21469, + "Nord's Tenderizer": 21470, + "Wrath of Cain": 21471, + "Siren's call": 21472, + "Jadetalon": 21473, + "Wraithfang": 21474, + "Blademaster": 21475, + "Cerebus": 21476, + "Archangel's Deliverance": 21477, + "Sinblade": 21478, + "Runeslayer": 21479, + "Excalibur": 21480, + "Fuego Del Sol": 21481, + "Stoneraven": 21482, + "El Infierno": 21483, + "Moonrend": 21484, + "Larzuk's Champion": 21485, + "Nightsummon": 21486, + "Bonescapel": 21487, + "Rabbit Slayer": 21488, + "Pagan's Athame": 21489, + "The Swashbuckler": 21490, + "Kang's Virtue": 21491, + "Snaketongue": 21492, + "Lifechoke": 21493, + "Ethereal edge": 21494, + "Palo Grande": 21495, + "Carnageleaver": 21496, + "Ghostleach": 21497, + "Soulreaper": 21498, + "Samual's Caretaker": 21499, + "Hell's Whisper": 21500, + "The Harvester": 21501, + "Raiden's Crutch": 21502, + "The TreeEnt": 21503, + "Stormwillow": 21504, + "Moonshadow": 21505, + "Strongoak": 21506, + "Demonweb": 21507, + "Bloodraven's Charge": 21508, + "Shadefalcon": 21509, + "Robin's Yolk": 21510, + "Glimmershred": 21511, + "Wraithflight": 21512, + "Lestron's Mark": 21513, + "Banshee's Wail": 21514, + "Windstrike": 21515, + "Medusa's Gaze": 21516, + "Titanfist": 21517, + "Hadeshorn": 21518, + "Rockstopper": 21519, + "Stealskull": 21520, + "Darksight Helm": 21521, + "Crown of Thieves": 21522, + "Blackhorn's Face": 21523, + "The Spirit Shroud": 21524, + "Skin of the Flayed One": 21525, + "Ironpelt": 21526, + "Spiritforge": 21527, + "Crow Caw": 21528, + "Duriel's Shell": 21529, + "Skullder's Ire": 21530, + "Toothrow": 21531, + "Atma's Wail": 21532, + "Black Hades": 21533, + "Corpsemourn": 21534, + "Que-hegan's Wisdom": 21535, + "Moser's Blessed Circle": 21536, + "Stormchaser": 21537, + "Tiamat's Rebuke": 21538, + "Gerke's Sanctuary": 21539, + "Radimant's Sphere": 21540, + "Gravepalm": 21541, + "Ghoulhide": 21542, + "Hellmouth": 21543, + "Infernostride": 21544, + "Waterwalk": 21545, + "Silkweave": 21546, + "Wartraveler": 21547, + "Razortail": 21548, + "Gloomstrap": 21549, + "Snowclash": 21550, + "Thudergod's Vigor": 21551, + "Lidless Wall": 21552, + "Lanceguard": 21553, + "Squire's Cover": 21554, + "Boneflame": 21555, + "Steelpillar": 21556, + "Nightwing's Veil": 21557, + "Hightower's Watch": 21558, + "Crown of Ages": 21559, + "Andariel's Visage": 21560, + "Darkfear": 21561, + "Dragonscale": 21562, + "Steel Carapice": 21563, + "Ashrera's Wired Frame": 21564, + "Rainbow Facet": 21565, + "Ravenlore": 21566, + "Boneshade": 21567, + "Nethercrow": 21568, + "Hellwarden's Husk": 21569, + "Flamebellow": 21570, + "Fathom": 21571, + "Wolfhowl": 21572, + "Spirit Ward": 21573, + "Kira's Guardian": 21574, + "Orumus' Robes": 21575, + "Gheed's Fortune": 21576, + "The Vicar": 21577, + "Stormlash": 21578, + "Halaberd's Reign": 21579, + "Parkersor's Calm": 21580, + "Warriv's Warder": 21581, + "Spike Thorn": 21582, + "Dracul's Grasp": 21583, + "Frostwind": 21584, + "Templar's Might": 21585, + "Eschuta's temper": 21620, + "Firelizard's Talons": 21587, + "Sandstorm Trek": 21588, + "Marrowwalk": 21589, + "Heaven's Light": 21590, + "Merman's Speed": 21591, + "Arachnid Mesh": 21592, + "Nosferatu's Coil": 21593, + "Metalgird": 21594, + "Verdugo's Hearty Cord": 21595, + "Sigurd's Staunch": 21596, + "Carrion Wind": 21597, + "Giantskull": 21598, + "Ironward": 21599, + "Gillian's Brazier": 21600, + "Drakeflame": 21601, + "Dust Storm": 21602, + "Skulltred": 21603, + "Alma's Reflection": 21604, + "Drulan's Tounge": 21605, + "Sacred Charge": 21606, + "Bul-Kathos": 21607, + "Saracen's Chance": 21608, + "Highlord's Wrath": 21609, + "Raven Frost": 21610, + "Dwarf Star": 21611, + "Atma's Scarab": 21612, + "Mara's Kaleidoscope": 21613, + "Crescent Moon": 21614, + "The Rising Sun": 21615, + "The Cat's Eye": 21616, + "Bul Katho's Wedding Band": 21617, + "Rings": 21618, + "Metalgrid": 21619, + "Stormshield": 21621, + "Blackoak Shield": 21622, + "Ormus' Robes": 21623, + "Arkaine's Valor": 21624, + "The Gladiator's Bane": 21625, + "Veil of Steel": 21626, + "Harlequin Crest": 21627, + "Lance Guard": 21628, + "Kerke's Sanctuary": 21629, + "Mosers Blessed Circle": 21630, + "Que-Hegan's Wisdon": 21631, + "Guardian Angel": 21632, + "Skin of the Flayerd One": 21633, + "Armor": 21634, + "Windforce": 21635, + "Eaglehorn": 21636, + "Gimmershred": 21637, + "Widowmaker": 21638, + "Stormspire": 21639, + "Naj's Puzzler": 21640, + "Ethereal Edge": 21641, + "Wizardspike": 21642, + "The Grandfather": 21643, + "Doombringer": 21644, + "Tyrael's Might": 21645, + "Lightsabre": 21646, + "The Cranium Basher": 21647, + "Schaefer's Hammer": 21648, + "Baranar's Star": 21649, + "Deaths's Web": 21650, + "Messerschmidt's Reaver": 21651, + "Hellslayer": 21652, + "Endlesshail": 21653, + "The Atlantian": 21654, + "Riftlash": 21655, + "Baezil's Vortex": 21656, + "Zakarum's Hand": 21657, + "Carin Shard": 21658, + "The Minataur": 21659, + "Trang-Oul's Avatar": 21660, + "Trang-Oul's Guise": 21661, + "Trang-Oul's Wing": 21662, + "Trang-Oul's Mask": 21663, + "Trang-Oul's Scales": 21664, + "Trang-Oul's Claws": 21665, + "Trang-Oul's Girth": 21666, + "Natalya's Odium": 21667, + "Natalya's Totem": 21668, + "Natalya's Mark": 21669, + "Natalya's Shadow": 21670, + "Natalya's Soul": 21671, + "Griswold's Legacy": 21672, + "Griswolds's Redemption": 21673, + "Griswold's Honor": 21674, + "Griswold's Heart": 21675, + "Griswold's Valor": 21676, + "Tang's Imperial Robes": 21677, + "Tang's Fore-Fathers": 21678, + "Tang's Rule": 21679, + "Tang's Throne": 21680, + "Tang's Battle Standard": 21681, + "Ogun's Fierce Visage": 21682, + "Ogun's Shadow": 21683, + "Ogun's Lash": 21684, + "Ogun's Vengeance": 21685, + "Bul-Kathos' Warden": 21686, + "Bul-Kathos' Children": 21687, + "Bul-Kathos' Sacred Charge": 21688, + "Bul-Kathos' Tribal Guardian": 21689, + "Bul-Kathos' Custodian": 21690, + "Flowkrad's Howl": 21691, + "Flowkrad's Grin": 21692, + "Flowkrad's Fur": 21693, + "Flowkrad's Paws": 21694, + "Flowkrad's Sinew": 21695, + "Aldur's Watchtower": 21696, + "Aldur's Stony Gaze": 21697, + "Aldur's Deception": 21698, + "Aldur's Guantlet": 21699, + "Aldur's Advance": 21700, + "M'avina's Battle Hymn": 21701, + "M'avina's True Sight": 21702, + "M'avina's Embrace": 21703, + "M'avina's Icy Clutch": 21704, + "M'avina's Tenet": 21705, + "M'avina's Caster": 21706, + "Sazabi's Grand Tribute": 21707, + "Sazabi's Cobalt Redeemer": 21708, + "Sazabi's Ghost Liberator": 21709, + "Sazabi's Mental Sheath": 21710, + "Hwanin's Majesty": 21711, + "Hwanin's Justice": 21712, + "Hwanin's Splendor": 21713, + "Hwanin's Refuge": 21714, + "Hwanin's Cordon": 21715, + "The Disciple": 21716, + "Telling of Beads": 21717, + "Laying of Hands": 21718, + "Rite of Passage": 21719, + "Spiritual Custodian": 21720, + "Credendum": 21721, + "Cow King's Leathers": 21722, + "Cow King's Horns": 21723, + "Cow King's Hide": 21724, + "Cow King's Hoofs": 21725, + "Aragon's Masterpiece": 21726, + "Aragon's Sunfire": 21727, + "Aragon's Icy Stare": 21728, + "Aragon's Storm Cloud": 21729, + "Orphan's Call": 21730, + "Guillaume's Face": 21731, + "Willhelm's Pride": 21732, + "Magnus' Skin": 21733, + "Wihtstan's Guard": 21734, + "Titan's Revenge": 21735, + "Shakabra's Crux": 21736, + "Lycander's Aim": 21737, + "Shadow's Touch": 21738, + "The Prowler": 21739, + "Mortal Crescent": 21740, + "Cutthroat": 21741, + "Sarmichian Justice": 21742, + "Annihilus": 21743, + "Arreat's Face": 21744, + "The Harbinger": 21745, + "Doomseer": 21746, + "Howling Visage": 21747, + "Terra": 21748, + "Syrian": 21749, + "Jalal's Mane": 21750, + "Malignant": 21751, + "Apothecary's Tote": 21752, + "Apocrypha": 21753, + "Foci of Visjerei": 21754, + "Homunculus": 21755, + "Aurora's Guard": 21756, + "Crest of Morn": 21757, + "Herald of Zakarum": 21758, + "Akarat's Protector": 21759, + "Ancient Eye": 21760, + "Globe of Visjerei": 21761, + "The Oculus": 21762, + "Phoenix Egg": 21763, + "Xenos": 21764, + "Nagas": 21765, + "Wyvern's Head": 21766, + "Sightless Veil": 21767, + "ChampionFormatX": 21768, + "EskillKickSing": 21769, + "EskillKickPlur": 21770, + "EskillPetLife": 21771, + "EskillWolfDef": 21772, + "EskillPassiveFeral": 21773, + "Eskillperhit12": 21774, + "Eskillincasehit": 21775, + "Eskillincasemastery": 21776, + "Eskillincaseraven": 21777, + "pad": 21779, + "axf": 21780, + "Eskillkickdamage": 21781, + "ModStre10k": 21782, + "ModStre10L": 21783, + "Class Specific": 21784, + "fana": 21785, + "qsta5q14": 21786, + "qstsa5q42a": 21787, + "qstsa5q31a": 21788, + "qstsa5q21a": 21789, + "qstsa5q43a": 21790, + "qstsa5q62a": 21791, + "qstsa5q61a": 21792, + "act1X": 21797, + "act2X": 21798, + "act3X": 21799, + "act4X": 21800, + "strepilogueX": 21801, + "act5X": 21802, + "strlastcinematic": 21803, + "CfgSay7": 21804, + "0sc": 21805, + "tr2": 21806, + "of Lightning Strike": 21807, + "of Plague Jab": 21808, + "of Charged Strike": 21809, + "of Impaling Strike": 21810, + "of Poison Jab": 21811, + "of Power Strike": 21812, + "of the Colossus": 21813, + "of the Kraken": 21814, + "Tal Rasha's Wrappings": 21815, + "Tal Rasha's Fire-Spun Cloth": 21816, + "Tal Rasha's Adjudication": 21817, + "Tal Rasha's Howling Wind": 21818, + "Tal Rasha's Lidless Eye": 21819, + "Tal Rasha's Horadric Crest": 21820, + "Hwanin's Seal": 21821, + "Heaven's Brethren": 21822, + "Dangoon's Teaching": 21823, + "Ondal's Almighty": 21824, + "Heaven's Taebaek": 21825, + "Haemosu's Adament": 21826, + "Lycander's Flank": 21827, + "Constricting Ring": 21828, + "Ginther's Rift": 21829, + "Naj's Ancient Set": 21830, + "Naj's Light Plate": 21831, + "Naj's Circlet": 21832, + "Sander's Superstition": 21833, + "Sander's Taboo": 21834, + "Sander's Basis": 21835, + "Sander's Derby": 21836, + "Sander's Court Jester": 21837, + "Ghost Liberator": 21838, + "Wilhelm's Pride": 21839, + "Immortal King's Stone Crusher": 21840, + "Immortal King's Pillar": 21841, + "Immortal King's Forge": 21842, + "Immortal King's Detail": 21843, + "Immortal King's Soul Cage \tImmortal King's Soul Cage": 21844, + "Immortal King's Will": 21845, + "Immortal King": 21846, + "Aldur's Gauntlet": 21847, + "Ancient Statue 3": 21848, + "Ancient Statue 2": 21849, + "Ancient Statue 1": 21850, + "Baal Subject 1": 21851, + "Baal Subject 2": 21852, + "Baal Subject 3": 21853, + "Baal Subject 4": 21854, + "Baal Subject 5": 21855, + "Baal Subject 6": 21856, + "Baal Subject 6a": 21857, + "Baal Subject 6b": 21858, + "Baal Crab Clone": 21859, + "Baal Crab to Stairs": 21860, + "BaalColdMage": 21861, + "Baal Subject Mummy": 21862, + "Baal Tentacle": 21863, + "Baals Minion": 21864, + "Hell1": 21865, + "Hell2": 21866, + "Hell3": 21867, + "To Hell1": 21868, + "To Hell2": 21869, + "To Hell3": 21870, + "Lord of Destruction": 21871, + "EskillPerBlade": 21873, + "ExInsertSockets": 21874, + "McAuley's Superstition": 21875, + "McAuley's Taboo": 21876, + "McAuley's Riprap": 21877, + "McAuley's Paragon": 21878, + "McAuley's Folly": 21879, + "qstsa5q62b": 21881, + "of the Plague": 21883, + "Go South": 21884, + "ItemExpansiveChancX": 21885, + "ItemExpansiveChanc1": 21886, + "ItemExpansiveChanc2": 21887, + "ItemExpcharmdesc": 21888, + "StrMercEx12": 21889, + "StrMercEx14": 21890, + "StrMercEx15": 21891, + "Eskillelementaldmg": 21892, + "Playersubtitles29": 21893, + "Playersubtitles30": 21894, + "LeaveCampDru": 21895, + "LeaveCampAss": 21896, + "EnterDOEAss": 21897, + "EnterDOEDru": 21898, + "EnterBurialAss": 21899, + "EnterBurialDru": 21900, + "EnterMonasteryAss": 21901, + "EnterMonasteryDru": 21902, + "EnterForgottenTAss": 21903, + "EnterForgottenTDru": 21904, + "EnterJailAss": 21905, + "EnterJailDru": 21906, + "EnterCatacombsAss": 21907, + "EnterCatacombsDru": 21908, + "CompletingDOEAss": 21909, + "CompletingDOEDru": 21910, + "CompletingBurialAss": 21911, + "CompletingBurialDru": 21912, + "FindingInifusAss": 21913, + "FindingInifusDru": 21914, + "FindingCairnAss": 21915, + "FindingCairnDru": 21916, + "FindingTristramAss": 21917, + "FindingTristramDru": 21918, + "RescueCainAss": 21919, + "RescueCainDru": 21920, + "HoradricMalusAss": 21921, + "HoradricMalusDru": 21922, + "CompletingAndarielAss": 21925, + "CompletingAndarielDru": 21926, + "EnteringRadamentAss": 21927, + "EnteringRadamentDru": 21928, + "CompletingRadamentAss": 21929, + "CompletingRadamentDru": 21930, + "BeginTaintedSunAss": 21931, + "BeginTaintedSunDru": 21932, + "EnteringClawViperAss": 21933, + "EnteringClawViperDru": 21934, + "CompletingTaintedSunAss": 21935, + "CompletingTaintedSunDru": 21936, + "EnteringArcaneAss": 21937, + "EnteringArcaneDru": 21938, + "FindingSummonerAss": 21939, + "FindingSummonerDru": 21940, + "CompletingSummonerAss": 21941, + "CompletingSummonerDru": 21942, + "FindingdecoyTombAss": 21943, + "FindingdecoyTombDru": 21944, + "FindingTrueTombAss": 21945, + "FindingTrueTombDru": 21946, + "CompletingTombAss": 21947, + "CompletingTombDru": 21948, + "FindingLamEsenAss": 21949, + "FindingLamEsenDru": 21950, + "CompletingLamEsenAss": 21952, + "CompletingLamEsenDru": 21953, + "FindingBeneathCityAss": 21954, + "FindingBeneathCityDru": 21955, + "FindingDrainLeverAss": 21956, + "FindingDrainLeverDru": 21957, + "CompletingBeneathCityAss": 21958, + "CompletingBeneathCityDru": 21959, + "CompletingBladeAss": 21960, + "CompletingBladeDru": 21961, + "FindingJadeFigAss": 21962, + "FindingJadeFigDru": 21963, + "FindingTempleAss": 21964, + "FindingTempleDru": 21965, + "CompletingTempleAss": 21966, + "CompletingTempleDru": 21967, + "FindingGuardianTowerAss": 21968, + "FindingGuardianTowerDru": 21969, + "CompletingGuardianTowerAss": 21971, + "FreezingIzualAss": 21973, + "FreezingIzualDru": 21974, + "KillingdDiabloSor": 21975, + "KillingdDiabloBar": 21976, + "KillingdDiabloNec": 21977, + "KillingdDiabloPal": 21978, + "KillingdDiabloAms": 21979, + "KillingdDiabloAss": 21980, + "KillingdDiabloDru": 21981, + "LeavingTownAct5Sor": 21982, + "LeavingTownAct5Bar": 21983, + "LeavingTownAct5Nec": 21984, + "LeavingTownAct5Pal": 21985, + "LeavingTownAct5Ams": 21986, + "LeavingTownAct5Ass": 21987, + "LeavingTownAct5Dru": 21988, + "CompletingStopSiegeSor": 21989, + "CompletingStopSiegeBar": 21990, + "CompletingStopSiegeNec": 21991, + "CompletingStopSiegePal": 21992, + "CompletingStopSiegeAms": 21993, + "CompletingStopSiegeAss": 21994, + "CompletingStopSiegeDru": 21995, + "RescueQual-KehkAct5Sor": 21996, + "RescueQual-KehkAct5Bar": 21997, + "RescueQual-KehkAct5Nec": 21998, + "RescueQual-KehkAct5Pal": 21999, + "RescueQual-KehkAct5Ams": 22000, + "RescueQual-KehkAct5Ass": 22001, + "RescueQual-KehkAct5Dru": 22002, + "EnteringNihlathakAct5Sor": 22003, + "EnteringNihlathakAct5Bar": 22004, + "EnteringNihlathakAct5Nec": 22005, + "EnteringNihlathakAct5Pal": 22006, + "EnteringNihlathakAct5Ams": 22007, + "EnteringNihlathakAct5Ass": 22008, + "EnteringNihlathakAct5Dru": 22009, + "CompletingNihlathakAct5Sor": 22010, + "CompletingNihlathakAct5Bar": 22011, + "CompletingNihlathakAct5Nec": 22012, + "CompletingNihlathakAct5Pal": 22013, + "CompletingNihlathakAct5Ams": 22014, + "CompletingNihlathakAct5Ass": 22015, + "CompletingNihlathakAct5Dru": 22016, + "EnteringTopMountAct5Sor": 22017, + "EnteringTopMountAct5Bar": 22018, + "EnteringTopMountAct5Nec": 22019, + "EnteringTopMountAct5Pal": 22020, + "EnteringTopMountAct5Ams": 22021, + "EnteringTopMountAct5Ass": 22022, + "EnteringTopMountAct5Dru": 22023, + "EnteringWorldstoneAct5Sor": 22024, + "EnteringWorldstoneAct5Bar": 22025, + "EnteringWorldstoneAct5Nec": 22026, + "EnteringWorldstoneAct5Pal": 22027, + "EnteringWorldstoneAct5Ams": 22028, + "EnteringWorldstoneAct5Ass": 22029, + "EnteringWorldstoneAct5Dru": 22030, + "CompletingDefeatBaalAct5Sor": 22031, + "CompletingDefeatBaalAct5Bar": 22032, + "CompletingDefeatBaalAct5Nec": 22033, + "CompletingDefeatBaalAct5Pal": 22034, + "CompletingDefeatBaalAct5Ams": 22035, + "CompletingDefeatBaalAct5Ass": 22036, + "CompletingDefeatBaalAct5Dru": 22037, + "Skillname222": 22038, + "Skillsd222": 22039, + "Skillld222": 22040, + "Skillan222": 22041, + "Skillname223": 22046, + "Skillsd223": 22047, + "Skillld223": 22048, + "Skillan223": 22049, + "Skillname225": 22050, + "Skillsd225": 22051, + "Skillld225": 22052, + "Skillan225": 22053, + "Skillname226": 22054, + "Skillsd226": 22055, + "Skillld226": 22056, + "Skillan226": 22057, + "Skillname227": 22058, + "Skillsd227": 22059, + "Skillld227": 22060, + "Skillan227": 22061, + "Skillname228": 22062, + "Skillsd228": 22063, + "Skillld228": 22064, + "Skillan228": 22065, + "Skillname229": 22066, + "Skillsd229": 22067, + "Skillld229": 22068, + "Skillan229": 22069, + "Skillname230": 22070, + "Skillsd230": 22071, + "Skillld230": 22072, + "Skillan230": 22073, + "Skillname231": 22074, + "Skillsd231": 22075, + "Skillld231": 22076, + "Skillan231": 22077, + "Skillname232": 22078, + "Skillsd232": 22079, + "Skillld232": 22080, + "Skillan232": 22081, + "Skillname233": 22082, + "Skillsd233": 22083, + "Skillld233": 22084, + "Skillan233": 22085, + "Skillname234": 22086, + "Skillsd234": 22087, + "Skillld234": 22088, + "Skillan234": 22089, + "Skillname235": 22090, + "Skillsd235": 22091, + "Skillld235": 22092, + "Skillan235": 22093, + "Skillname236": 22094, + "Skillsd236": 22095, + "Skillld236": 22096, + "Skillan236": 22097, + "Skillname237": 22098, + "Skillsd237": 22099, + "Skillld237": 22100, + "Skillan237": 22101, + "Skillname238": 22102, + "Skillsd238": 22103, + "Skillld238": 22104, + "Skillan238": 22105, + "Skillname239": 22106, + "Skillsd239": 22107, + "Skillld239": 22108, + "Skillan239": 22109, + "Skillname240": 22110, + "Skillsd240": 22111, + "Skillld240": 22112, + "Skillan240": 22113, + "Skillname241": 22114, + "Skillsd241": 22115, + "Skillld241": 22116, + "Skillan241": 22117, + "Skillname242": 22118, + "Skillsd242": 22119, + "Skillld242": 22120, + "Skillan242": 22121, + "Skillname243": 22122, + "Skillsd243": 22123, + "Skillld243": 22124, + "Skillan243": 22125, + "Skillname244": 22126, + "Skillsd244": 22127, + "Skillld244": 22128, + "Skillan244": 22129, + "Skillname245": 22130, + "Skillsd245": 22131, + "Skillld245": 22132, + "Skillan245": 22133, + "Skillname246": 22134, + "Skillsd246": 22135, + "Skillld246": 22136, + "Skillan246": 22137, + "Skillname247": 22138, + "Skillsd247": 22139, + "Skillld247": 22140, + "Skillan247": 22141, + "Skillname248": 22142, + "Skillsd248": 22143, + "Skillld248": 22144, + "Skillan248": 22145, + "Skillname249": 22146, + "Skillsd249": 22147, + "Skillld249": 22148, + "Skillan249": 22149, + "Skillname250": 22150, + "Skillsd250": 22151, + "Skillld250": 22152, + "Skillan250": 22153, + "Skillname251": 22154, + "Skillsd251": 22155, + "Skillld251": 22156, + "Skillan251": 22157, + "Skillname252": 22158, + "Skillsd252": 22159, + "Skillld252": 22160, + "Skillan252": 22161, + "Skillname253": 22162, + "Skillsd253": 22163, + "Skillld253": 22164, + "Skillan253": 22165, + "Skillname254": 22166, + "Skillsd254": 22167, + "Skillld254": 22168, + "Skillan254": 22169, + "Skillname255": 22170, + "Skillsd255": 22171, + "Skillld255": 22172, + "Skillan255": 22173, + "Skillname256": 22174, + "Skillsd256": 22175, + "Skillld256": 22176, + "Skillan256": 22177, + "Skillname257": 22178, + "Skillsd257": 22179, + "Skillld257": 22180, + "Skillan257": 22181, + "Skillname258": 22182, + "Skillsd258": 22183, + "Skillld258": 22184, + "Skillan258": 22185, + "Skillname259": 22186, + "Skillsd259": 22187, + "Skillld259": 22188, + "Skillan259": 22189, + "Skillname260": 22190, + "Skillsd260": 22191, + "Skillld260": 22192, + "Skillan260": 22193, + "Skillname261": 22194, + "Skillsd261": 22195, + "Skillld261": 22196, + "Skillan261": 22197, + "Skillname262": 22198, + "Skillsd262": 22199, + "Skillld262": 22200, + "Skillan262": 22201, + "Skillname263": 22202, + "Skillsd263": 22203, + "Skillld263": 22204, + "Skillan263": 22205, + "Skillname264": 22206, + "Skillsd264": 22207, + "Skillld264": 22208, + "Skillan264": 22209, + "Skillname265": 22210, + "Skillsd265": 22211, + "Skillld265": 22212, + "Skillan265": 22213, + "Skillname266": 22214, + "Skillsd266": 22215, + "Skillld266": 22216, + "Skillan266": 22217, + "Skillname267": 22218, + "Skillsd267": 22219, + "Skillld267": 22220, + "Skillan267": 22221, + "Skillname268": 22222, + "Skillsd268": 22223, + "Skillld268": 22224, + "Skillan268": 22225, + "Skillname269": 22226, + "Skillsd269": 22227, + "Skillld269": 22228, + "Skillan269": 22229, + "Skillname270": 22230, + "Skillsd270": 22231, + "Skillld270": 22232, + "Skillan270": 22233, + "Skillname271": 22234, + "Skillsd271": 22235, + "Skillld271": 22236, + "Skillan271": 22237, + "Skillname272": 22238, + "Skillsd272": 22239, + "Skillld272": 22240, + "Skillan272": 22241, + "Skillname273": 22242, + "Skillsd273": 22243, + "Skillld273": 22244, + "Skillan273": 22245, + "Skillname274": 22246, + "Skillsd274": 22247, + "Skillld274": 22248, + "Skillan274": 22249, + "Skillname275": 22250, + "Skillsd275": 22251, + "Skillld275": 22252, + "Skillan275": 22253, + "Skillname276": 22254, + "Skillsd276": 22255, + "Skillld276": 22256, + "Skillan276": 22257, + "Skillname277": 22258, + "Skillsd277": 22259, + "Skillld277": 22260, + "Skillan277": 22261, + "Skillname278": 22262, + "Skillsd278": 22263, + "Skillld278": 22264, + "Skillan278": 22265, + "Skillname279": 22266, + "Skillsd279": 22267, + "Skillld279": 22268, + "Skillan279": 22269, + "Skillname280": 22270, + "Skillsd280": 22271, + "Skillld280": 22272, + "Skillan280": 22273, + "Skillname281": 22274, + "Skillsd281": 22275, + "Skillld281": 22276, + "Skillan281": 22277, + "ESkillPerKick": 22286, + "EskillLifeSteal": 22287, + "Eskillchancetostun": 22288, + "Eskillchancetoafflict": 22289, + "Eskillpowerup1": 22290, + "Eskillpowerup2": 22291, + "Eskillpowerup3": 22292, + "Eskillpowerupadd": 22293, + "Eskillsinishup": 22294, + "Eskillpudlife": 22295, + "Eskillpudmana": 22296, + "Eskillpudburning": 22297, + "Eskillpuddgmper": 22298, + "Eskilllowerresis": 22299, + "Eskilltomeleeattacks": 22300, + "EskillManaSteal": 22301, + "Eskillferalpets": 22302, + "Eskillpercentatt": 22303, + "Eskillpercentlif": 22304, + "Eskillpercentdmg": 22305, + "Eskillfinishmove": 22306, + "Eskillmanarecov": 22307, + "Eskillphoenix1": 22308, + "Eskillphoenix2": 22309, + "Eskillphoenix3": 22310, + "Eskillthunder1": 22311, + "Eskillthunder2": 22312, + "Eskillthunder3": 22313, + "Eskillfistsoffire1": 22314, + "Eskillfistsoffire2": 22315, + "Eskillfistsoffire3": 22316, + "Eskillbladesofice1": 22317, + "Eskillbladesofice2": 22318, + "Eskillbladesofice3": 22319, + "strUI5": 22320, + "strUI6": 22321, + "strUI7": 22322, + "strUI8": 22323, + "strUI9": 22324, + "strUI10": 22325, + "strUI11": 22326, + "strUI12": 22327, + "strUI13": 22328, + "strUI14": 22329, + "UIFenirsui": 22330, + "UiRescuedBarUI": 22331, + "UiShadowUI": 22332, + "StrUI18": 22333, + "Spike Generator": 22334, + "Charged Bolt Sentry": 22335, + "Lightning Sentry": 22336, + "Blade Creeper": 22337, + "Invis Pet": 22338, + "Druid Hawk": 22339, + "Druid Wolf": 22340, + "Druid Totem": 22341, + "Druid Fenris": 22342, + "Druid Spirit Wolf": 22343, + "Druid Bear": 22344, + "Druid Plague Poppy": 22345, + "Druid Cycle of Life": 22346, + "Vine Creature": 22347, + "Eagleexp": 22348, + "Wolf": 22349, + "Bear": 22350, + "Siege Door": 22351, + "Siege Beast": 22358, + "Hell Temptress": 22389, + "Blood Temptress": 22390, + "Blood Witch": 22394, + "Hell Witch": 22395, + "CatapultN": 22411, + "CatapultS": 22412, + "CatapultE": 22413, + "CatapultW": 22414, + "Frozen Horror1": 22415, + "Frozen Horror2": 22416, + "Frozen Horror3": 22417, + "Frozen Horror4": 22418, + "Frozen Horror5": 22419, + "Blood Lord1": 22420, + "Blood Lord2": 22421, + "Blood Lord3": 22422, + "Blood Lord4": 22423, + "Blood Lord5": 22424, + "Catapult Spotter N": 22425, + "Catapult Spotter S": 22426, + "Catapult Spotter E": 22427, + "Catapult Spotter W": 22428, + "Catapult Spotter Siege": 22429, + "CatapultSiege": 22430, + "Barricade Wall Right": 22431, + "Barricade Wall Left": 22432, + "Barricade Door": 22433, + "Barricade Tower": 22434, + "Siege Boss": 22435, // shenk the overseer + "Evil hut": 22436, + "Death Mauler1": 22437, + "Death Mauler2": 22438, + "Death Mauler3": 22439, + "Death Mauler4": 22440, + "Death Mauler5": 22441, + "SnowYeti1": 22442, + "SnowYeti2": 22443, + "SnowYeti3": 22444, + "SnowYeti4": 22445, + "Baal Throne": 22446, + "Baal Crab": 22447, + "Baal Taunt": 22448, + "Putrid Defiler1": 22449, + "Putrid Defiler2": 22450, + "Putrid Defiler3": 22451, + "Putrid Defiler4": 22452, + "Putrid Defiler5": 22453, + "Pain Worm1": 22454, + "Pain Worm2": 22455, + "Pain Worm3": 22456, + "Pain Worm4": 22457, + "Pain Worm5": 22458, + "WolfRider5": 22459, + "WolfRider4": 22460, + "WolfRider3": 22461, + "WolfRider2": 22462, + "WolfRider1": 22463, + "Oak Sage": 22464, + "Heart of Wolverine": 22465, + "Spirit of Barbs": 22466, + "Shadow Warrior": 22467, + "Death Sentry": 22468, + "Inferno Sentry": 22469, + "Shadow Master": 22470, + "Wake of Destruction": 22471, + "Ghostly": 22472, + "Fanatic": 22473, + "Possessed": 22474, + "Berserk": 22475, + "Larzuk": 22476, + "Drehya": 22477, + "Malah": 22478, + "Nihlathak Town": 22479, + "Qual-Kehk": 22480, + "Act 5 Townguard": 22481, + "Act 5 Combatant": 22482, + "Nihlathak": 22483, + "POW": 22484, + "Moe": 22485, + "Curly": 22486, + "Larry": 22487, + "Ancient Barbarian 3": 22488, + "Ancient Barbarian 2": 22489, + "Ancient Barbarian 1": 22490, + "Blaze Ripper": 22491, + "Magma Torquer": 22492, + "Sharp Tooth Sayer": 22493, + "Vinvear Molech": 22494, + "Anodized Elite": 22495, + "Snapchip Shatter": 22496, + "Pindleskin": 22497, + "Threash Socket": 22498, + "Eyeback Unleashed": 22499, + "Megaflow Rectifier": 22500, // eldritch the rectifier + "Dac Farren": 22501, + "Bonesaw Breaker": 22502, + "Axe Dweller": 22503, + "Frozenstein": 22504, + "strDruidOnly": 22505, + "strAssassinOnly": 22506, + "strAmazonOnly": 22507, + "strBarbarianOnly": 22508, + "StrSklTree26": 22509, + "StrSklTree27": 22510, + "StrSklTree28": 22511, + "StrSklTree29": 22512, + "StrSklTree30": 22513, + "StrSklTree31": 22514, + "StrSklTree32": 22515, + "StrSklTree33": 22516, + "StrSklTree34": 22517, + "chestr": 22520, + "barrel wilderness": 22521, + "woodchestL": 22522, + "burialchestL": 22523, + "burialchestR": 22524, + "ChestL": 22527, + "ChestSL": 22528, + "ChestSR": 22529, + "woodchestR": 22530, + "chestR": 22531, + "burningbodies": 22532, + "burningpit": 22533, + "tribal flag": 22534, + "flag widlerness": 22535, + "eflg": 22536, + "chan": 22537, + "jar": 22538, + "jar2": 22539, + "jar3": 22540, + "swingingheads": 22541, + "pole": 22542, + "animatedskullsandrocks": 22543, + "hellgate": 22544, + "gate": 22545, + "banner1": 22546, + "banner2": 22547, + "mrpole": 22548, + "pene": 22549, + "debris": 22550, + "woodchest2R": 22551, + "woodchest2L": 22552, + "object1": 22553, + "magic shrine2": 22554, + "torch2": 22555, + "torch1": 22556, + "tomb3": 22557, + "tomb2": 22558, + "tomb1": 22559, + "ttor": 22560, + "icecave_torch2": 22561, + "icecave_torch1": 22562, + "clientsmoke": 22563, + "deadbarbarian": 22564, + "deadbarbarian18": 22565, + "uncle f#%* comedy central(c)\tMoe": 22566, + "cagedwussie1": 22567, + "icecaveshrine2": 22568, + "icecavejar4": 22569, + "icecavejar3": 22570, + "icecavejar2": 22571, + "icecavejar1": 22572, + "evilurn": 22573, + "secret object": 22574, + "Altar": 22575, + "Ldeathpole": 22576, + "deathpole": 22577, + "explodingchest": 22578, + "banner 2": 22579, + "banner 1": 22580, + "pileofskullsandrocks": 22581, + "animated skulland rockpile": 22582, + "jar1": 22583, + "etorch2": 22584, + "ettr": 22585, + "ecfra": 22586, + "etorch1": 22587, + "healthshrine": 22588, + "explodingbarrel": 22589, + "flag wilderness": 22590, + "object": 22591, + "Shrine2wilderness": 22592, + "Shrine3wilderness": 22593, + "pyox": 22594, + "ptox": 22595, + "Siege Control": 22596, + "mrjar": 22597, + "object2": 22598, + "mrbox": 22599, + "tomb3L": 22600, + "tomb2L": 22601, + "tomb1L": 22602, + "red light": 22603, + "groundtombL": 22604, + "groundtomb": 22605, + "deadperson": 22606, + "candles": 22607, + "sbub": 22608, + "ubub": 22609, + "deadperson2": 22610, + "Prison Door": 22611, + "ancientsaltar": 22612, + "hiddenstash": 22613, + "eweaponrackL": 22614, + "eweaponrackR": 22615, + "earmorstandL": 22616, + "earmorstandR": 22617, + "qstsa5q1": 22618, + "qsta5q11": 22619, + "qsta5q12": 22620, + "qsta5q13": 22621, + "qstsa5q2": 22622, + "qstsa5q21": 22623, + "qstsa5q22": 22624, + "qstsa5q23": 22625, + "qstsa5q24": 22626, + "qstsa5q3": 22627, + "qstsa5q31": 22628, + "qstsa5q32": 22629, + "qstsa5q33": 22630, + "qstsa5q34": 22631, + "qstsa5q35": 22632, + "qstsa5q4": 22633, + "qstsa5q41": 22634, + "qstsa5q42": 22635, + "qstsa5q43": 22636, + "qstsa5q5": 22637, + "qstsa5q51": 22638, + "qstsa5q52": 22639, + "qstsa5q53": 22640, + "qstsa5q6": 22641, + "qstsa5q61": 22642, + "qstsa5q62": 22643, + "qstsa5q63": 22644, + "qstsa5q64": 22645, + "Harrogath": 22646, + "Bloody Foothills": 22647, + "Rigid Highlands": 22648, + "Arreat Plateau": 22649, + "Crystalized Cavern Level 1": 22650, + "Cellar of Pity": 22651, + "Crystalized Cavern Level 2": 22652, + "Echo Chamber": 22653, + "Tundra Wastelands": 22654, + "Glacial Caves Level 1": 22655, + "Glacial Caves Level 2": 22656, + "Rocky Summit": 22657, + "Nihlathaks Temple": 22658, + "Halls of Anguish": 22659, + "Halls of Death's Calling": 22660, + "Halls of Tormented Insanity": 22661, + "Halls of Vaught": 22662, + "The Worldstone Keep Level 1": 22663, + "The Worldstone Keep Level 2": 22664, + "The Worldstone Keep Level 3": 22665, + "The Worldstone Chamber": 22666, + "Throne of Destruction": 22667, + "To Harrogath": 22668, + "To The Bloody Foothills": 22669, + "To The Rigid Highlands": 22670, + "To The Arreat Plateau": 22671, + "To The Crystalized Cavern Level 1": 22672, + "To The Cellar of Pity": 22673, + "To The Crystalized Cavern Level 2": 22674, + "To The Echo Chamber": 22675, + "To The Tundra Wastelands": 22676, + "To The Glacier Caves Level 1": 22677, + "To The Glacier Caves Level 2": 22678, + "To The Rocky Summit": 22679, + "To Nihlathaks Temple": 22680, + "To The Halls of Anguish": 22681, + "To The Halls of Death's Calling": 22682, + "To The Halls of Tormented Insanity": 22683, + "To The Halls of Vaught": 22684, + "To The Worldstone Keep Level 1": 22685, + "To The Worldstone Keep Level 2": 22686, + "To The Worldstone Keep Level 3": 22687, + "To The Worldstone Chamber": 22688, + "To The Throne of Destruction": 22689, + "hireiconinfo1": 22690, + "hireiconinfo2": 22691, + "hiredismiss": 22692, + "hiredismisshire": 22693, + "hirerehire": 22694, + "hireresurrect": 22695, + "hireresurrect2": 22696, + "hirechat1": 22697, + "hirechat2": 22698, + "hirechat3": 22699, + "hirepraise1": 22700, + "hirepraise2": 22701, + "hiredanger1": 22702, + "hiredanger2": 22703, + "hiredanger3": 22704, + "hiredanger4": 22705, + "hiredanger5": 22706, + "hiredanger6": 22707, + "hirefeelstronger2": 22708, + "hirehelp1": 22709, + "hirehelp2": 22710, + "hirehelp3": 22711, + "hirehelp4": 22712, + "hiregreets1": 22713, + "hiregreets2": 22714, + "hiregreets3": 22715, + "hiregreets4": 22716, + "CfgSkill9": 22717, + "CfgSkill10": 22718, + "CfgSkill11": 22719, + "CfgSkill12": 22720, + "CfgSkill13": 22721, + "CfgSkill14": 22722, + "CfgSkill15": 22723, + "CfgSkill16": 22724, + "CfgToggleminimap": 22725, + "Cfgswapweapons": 22726, + "Cfghireling": 22727, + "MiniPanelHireinv": 22728, + "MiniPanelHire": 22729, + "Go North": 22737, + "Travel To Harrogath": 22738, + "Rename Instruct": 22747, + "Addsocketsui": 22748, + "Personalizeui": 22749, + "Addsocketsui2": 22750, + "MercX101": 22751, + "MercX102": 22752, + "MercX103": 22753, + "MercX104": 22754, + "MercX105": 22755, + "MercX106": 22756, + "MercX107": 22757, + "MercX108": 22758, + "MercX109": 22759, + "MercX110": 22760, + "MercX111": 22761, + "MercX112": 22762, + "MercX113": 22763, + "MercX114": 22764, + "MercX115": 22765, + "MercX116": 22766, + "MercX117": 22767, + "MercX118": 22768, + "MercX119": 22769, + "MercX120": 22770, + "MercX121": 22771, + "MercX122": 22772, + "MercX123": 22773, + "MercX124": 22774, + "MercX125": 22775, + "MercX126": 22776, + "MercX127": 22777, + "MercX128": 22778, + "MercX129": 22779, + "MercX130": 22780, + "MercX131": 22781, + "MercX132": 22782, + "MercX133": 22783, + "MercX134": 22784, + "MercX135": 22785, + "MercX136": 22786, + "MercX137": 22787, + "MercX138": 22788, + "MercX139": 22789, + "MercX140": 22790, + "MercX141": 22791, + "MercX142": 22792, + "MercX143": 22793, + "MercX144": 22794, + "MercX145": 22795, + "MercX146": 22796, + "MercX147": 22797, + "MercX148": 22798, + "MercX149": 22799, + "MercX150": 22800, + "MercX151": 22801, + "MercX152": 22802, + "MercX153": 22803, + "MercX154": 22804, + "MercX155": 22805, + "MercX156": 22806, + "MercX157": 22807, + "MercX158": 22808, + "MercX159": 22809, + "MercX160": 22810, + "MercX161": 22811, + "MercX162": 22812, + "MercX163": 22813, + "MercX164": 22814, + "MercX165": 22815, + "MercX166": 22816, + "MercX167": 22817 + }; + + let LocaleStringName = {}; + + for (let k in LocaleStringID) { + LocaleStringName[LocaleStringID[k]] = k; + } + + module.exports = { + LocaleStringName: LocaleStringName, + LocaleStringID: LocaleStringID + }; +})(module); diff --git a/d2bs/kolbot/libs/core/GameData/MonsterData.js b/d2bs/kolbot/libs/core/GameData/MonsterData.js new file mode 100644 index 000000000..4e80b0cd2 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/MonsterData.js @@ -0,0 +1,120 @@ +/** +* @filename MonsterData.js +* @author Nishimura-Katsuo +* @desc monster data library +* +*/ + +(function (module, require) { + const LocaleStringName = require("./LocaleStringID").LocaleStringName; + const MONSTER_INDEX_COUNT = 770; + /** + * @typedef MonsterDataObj + * @type {object} + * @property {number} Index = Index of this monster + * @property {number} ClassID = classid of this monster + * @property {number} Type = Type of monster + * @property {number} Level = Level of this monster in normal (use GameData.monsterLevel to find monster levels) + * @property {boolean} Ranged = if monster is ranged + * @property {number} Rarity = weight of this monster in level generation + * @property {number} Threat = threat level used by mercs + * @property {number} Align = alignment of unit (determines what it will attack) + * @property {boolean} Melee = if monster is melee + * @property {boolean} NPC = if unit is NPC + * @property {boolean} Demon = if monster is demon + * @property {boolean} Flying = if monster is flying + * @property {boolean} Boss = if monster is a boss + * @property {boolean} ActBoss = if monster is act boss + * @property {boolean} Killable = if monster can be killed + * @property {boolean} Convertable = if monster is affected by convert or mind blast + * @property {boolean} NeverCount = if not counted as a minion + * @property {number} DeathDamage = explodes on death + * @property {number} Regeneration = hp regeneration + * @property {number} LocaleString = locale string index for getLocaleString + * @property {number} ExperienceModifier = percent of base monster exp this unit rewards when killed + * @property {number} Undead = 2 if greater undead, 1 if lesser undead, 0 if neither + * @property {number} Drain = drain effectiveness percent + * @property {number} Block = block percent + * @property {number} Physical = physical resist + * @property {number} Magic = magic resist + * @property {number} Fire = fire resist + * @property {number} Lightning = lightning resist + * @property {number} Poison = poison resist + * @property {number[]} Minions = array of minions that can spawn with this unit + * @property {number} MinionCount.Min = minimum number of minions that can spawn with this unit + * @property {number} MinionCount.Max = maximum number of minions that can spawn with this unit + */ + + /** @type {MonsterDataObj[]} */ + const MonsterData = Array(MONSTER_INDEX_COUNT); + + for (let i = 0; i < MonsterData.length; i++) { + let index = i; + + MonsterData[i] = ({ + Index: index, + ClassID: index, + Type: getBaseStat("monstats", index, "MonType"), + Level: getBaseStat("monstats", index, "Level"), // normal only, nm/hell are determined by area's LevelEx + Ranged: getBaseStat("monstats", index, "RangedType"), + Rarity: getBaseStat("monstats", index, "Rarity"), + Threat: getBaseStat("monstats", index, "threat"), + PetIgnore: getBaseStat("monstats", index, "petignore"), + Align: getBaseStat("monstats", index, "Align"), + Melee: getBaseStat("monstats", index, "isMelee"), + NPC: getBaseStat("monstats", index, "npc"), + Demon: getBaseStat("monstats", index, "demon"), + Flying: getBaseStat("monstats", index, "flying"), + Boss: getBaseStat("monstats", index, "boss"), + ActBoss: getBaseStat("monstats", index, "primeevil"), + Killable: getBaseStat("monstats", index, "killable"), + Convertable: getBaseStat("monstats", index, "switchai"), + NeverCount: getBaseStat("monstats", index, "neverCount"), + DeathDamage: getBaseStat("monstats", index, "deathDmg"), + Regeneration: getBaseStat("monstats", index, "DamageRegen"), + LocaleString: getLocaleString(getBaseStat("monstats", index, "NameStr")), + InternalName: LocaleStringName[getBaseStat("monstats", index, "NameStr")], + ExperienceModifier: getBaseStat("monstats", index, ["Exp", "Exp(N)", "Exp(H)"][me.diff]), + Undead: (getBaseStat("monstats", index, "hUndead") && 2) | (getBaseStat("monstats", index, "lUndead") && 1), + Drain: getBaseStat("monstats", index, ["Drain", "Drain(N)", "Drain(H)"][me.diff]), + Block: getBaseStat("monstats", index, ["ToBlock", "ToBlock(N)", "ToBlock(H)"][me.diff]), + Physical: getBaseStat("monstats", index, ["ResDm", "ResDm(N)", "ResDm(H)"][me.diff]), + Magic: getBaseStat("monstats", index, ["ResMa", "ResMa(N)", "ResMa(H)"][me.diff]), + Fire: getBaseStat("monstats", index, ["ResFi", "ResFi(N)", "ResFi(H)"][me.diff]), + Lightning: getBaseStat("monstats", index, ["ResLi", "ResLi(N)", "ResLi(H)"][me.diff]), + Cold: getBaseStat("monstats", index, ["ResCo", "ResCo(N)", "ResCo(H)"][me.diff]), + Poison: getBaseStat("monstats", index, ["ResPo", "ResPo(N)", "ResPo(H)"][me.diff]), + Minions: ([ + getBaseStat("monstats", index, "minion1"), getBaseStat("monstats", index, "minion2") + ].filter(mon => mon !== 65535)), + GroupCount: ({ + Min: getBaseStat("monstats", index, "MinGrp"), + Max: getBaseStat("monstats", index, "MaxGrp") + }), + MinionCount: ({ + Min: getBaseStat("monstats", index, "PartyMin"), + Max: getBaseStat("monstats", index, "PartyMax") + }), + Velocity: getBaseStat("monstats", index, "Velocity"), + Run: getBaseStat("monstats", index, "Run"), + SizeX: getBaseStat("monstats", index, "SizeX"), + SizeY: getBaseStat("monstats", index, "SizeY"), + Attack1MinDmg: getBaseStat("monstats", index, ["A1MinD", "A1MinD(N)", "A1MinD(H)"][me.diff]), + Attack1MaxDmg: getBaseStat("monstats", index, ["A1MaxD", "A1MaxD(N)", "A1MaxD(H)"][me.diff]), + Attack2MinDmg: getBaseStat("monstats", index, ["A2MinD", "A2MinD(N)", "A2MinD(H)"][me.diff]), + Attack2MaxDmg: getBaseStat("monstats", index, ["A2MaxD", "A2MaxD(N)", "A2MaxD(H)"][me.diff]), + Skill1MinDmg: getBaseStat("monstats", index, ["S1MinD", "S1MinD(N)", "S1MinD(H)"][me.diff]), + Skill1MaxDmg: getBaseStat("monstats", index, ["S1MaxD", "S1MaxD(N)", "S1MaxD(H)"][me.diff]), + }); + } + + MonsterData.findByName = function (whatToFind) { + let matches = MonsterData + .map(mon => [Math.min(whatToFind.diffCount(mon.LocaleString), whatToFind.diffCount(mon.InternalName)), mon]) + .sort((a, b) => a[0] - b[0]); + + return matches[0][1]; + }; + + module.exports = MonsterData; +})(module, require); diff --git a/d2bs/kolbot/libs/core/GameData/NTItemAlias.js b/d2bs/kolbot/libs/core/GameData/NTItemAlias.js new file mode 100644 index 000000000..86353cb38 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/NTItemAlias.js @@ -0,0 +1,1521 @@ +/* eslint-disable dot-notation */ +/** +* @filename NTItemAlias.js +* @author kolton +* @credit d2nt +* @desc Item alias's to work with NTItemParser for kolbots pickit system +* +*/ + +/** @global */ +const NTIPAliasType = {}; +NTIPAliasType["shield"] = 2; +NTIPAliasType["armor"] = 3; +NTIPAliasType["gold"] = 4; +NTIPAliasType["bowquiver"] = 5; +NTIPAliasType["crossbowquiver"] = 6; +NTIPAliasType["playerbodypart"] = 7; +NTIPAliasType["herb"] = 8; +NTIPAliasType["potion"] = 9; +NTIPAliasType["ring"] = 10; +NTIPAliasType["elixir"] = 11; +NTIPAliasType["amulet"] = 12; +NTIPAliasType["charm"] = 13; +NTIPAliasType["notused"] = 14; +NTIPAliasType["boots"] = 15; +NTIPAliasType["gloves"] = 16; +NTIPAliasType["notused"] = 17; +NTIPAliasType["book"] = 18; +NTIPAliasType["belt"] = 19; +NTIPAliasType["gem"] = 20; +NTIPAliasType["torch"] = 21; +NTIPAliasType["scroll"] = 22; +NTIPAliasType["notused"] = 23; +NTIPAliasType["scepter"] = 24; +NTIPAliasType["wand"] = 25; +NTIPAliasType["staff"] = 26; +NTIPAliasType["bow"] = 27; +NTIPAliasType["axe"] = 28; +NTIPAliasType["club"] = 29; +NTIPAliasType["sword"] = 30; +NTIPAliasType["hammer"] = 31; +NTIPAliasType["knife"] = 32; +NTIPAliasType["spear"] = 33; +NTIPAliasType["polearm"] = 34; +NTIPAliasType["crossbow"] = 35; +NTIPAliasType["mace"] = 36; +NTIPAliasType["helm"] = 37; +NTIPAliasType["missilepotion"] = 38; +NTIPAliasType["quest"] = 39; +NTIPAliasType["bodypart"] = 40; +NTIPAliasType["key"] = 41; +NTIPAliasType["throwingknife"] = 42; +NTIPAliasType["throwingaxe"] = 43; +NTIPAliasType["javelin"] = 44; +NTIPAliasType["weapon"] = 45; +NTIPAliasType["meleeweapon"] = 46; +NTIPAliasType["missileweapon"] = 47; +NTIPAliasType["thrownweapon"] = 48; +NTIPAliasType["comboweapon"] = 49; +NTIPAliasType["anyarmor"] = 50; +NTIPAliasType["anyshield"] = 51; +NTIPAliasType["miscellaneous"] = 52; +NTIPAliasType["socketfiller"] = 53; +NTIPAliasType["secondhand"] = 54; +NTIPAliasType["stavesandrods"] = 55; +NTIPAliasType["missile"] = 56; +NTIPAliasType["blunt"] = 57; +NTIPAliasType["jewel"] = 58; +NTIPAliasType["classspecific"] = 59; +NTIPAliasType["amazonitem"] = 60; +NTIPAliasType["barbarianitem"] = 61; +NTIPAliasType["necromanceritem"] = 62; +NTIPAliasType["paladinitem"] = 63; +NTIPAliasType["sorceressitem"] = 64; +NTIPAliasType["assassinitem"] = 65; +NTIPAliasType["druiditem"] = 66; +NTIPAliasType["handtohand"] = 67; +NTIPAliasType["orb"] = 68; +NTIPAliasType["voodooheads"] = 69; +NTIPAliasType["auricshields"] = 70; +NTIPAliasType["primalhelm"] = 71; +NTIPAliasType["pelt"] = 72; +NTIPAliasType["cloak"] = 73; +NTIPAliasType["rune"] = 74; +NTIPAliasType["circlet"] = 75; +NTIPAliasType["healingpotion"] = 76; +NTIPAliasType["manapotion"] = 77; +NTIPAliasType["rejuvpotion"] = 78; +NTIPAliasType["staminapotion"] = 79; +NTIPAliasType["antidotepotion"] = 80; +NTIPAliasType["thawingpotion"] = 81; +NTIPAliasType["smallcharm"] = 82; +NTIPAliasType["mediumcharm"] = 83; +NTIPAliasType["largecharm"] = 84; +NTIPAliasType["amazonbow"] = 85; +NTIPAliasType["amazonspear"] = 86; +NTIPAliasType["amazonjavelin"] = 87; +NTIPAliasType["assassinclaw"] = 88; +NTIPAliasType["magicbowquiv"] = 89; +NTIPAliasType["magicxbowquiv"] = 90; +NTIPAliasType["chippedgem"] = 91; +NTIPAliasType["flawedgem"] = 92; +NTIPAliasType["standardgem"] = 93; +NTIPAliasType["flawlessgem"] = 94; +NTIPAliasType["perfectgem"] = 95; +NTIPAliasType["amethyst"] = 96; +NTIPAliasType["diamond"] = 97; +NTIPAliasType["emerald"] = 98; +NTIPAliasType["ruby"] = 99; +NTIPAliasType["sapphire"] = 100; +NTIPAliasType["topaz"] = 101; +NTIPAliasType["skull"] = 102; + +/** @global */ +const NTIPAliasClassID = {}; +NTIPAliasClassID["hax"] = 0; NTIPAliasClassID["handaxe"] = 0; +NTIPAliasClassID["axe"] = 1; +NTIPAliasClassID["2ax"] = 2; NTIPAliasClassID["doubleaxe"] = 2; +NTIPAliasClassID["mpi"] = 3; NTIPAliasClassID["militarypick"] = 3; +NTIPAliasClassID["wax"] = 4; NTIPAliasClassID["waraxe"] = 4; +NTIPAliasClassID["lax"] = 5; NTIPAliasClassID["largeaxe"] = 5; +NTIPAliasClassID["bax"] = 6; NTIPAliasClassID["broadaxe"] = 6; +NTIPAliasClassID["btx"] = 7; NTIPAliasClassID["battleaxe"] = 7; +NTIPAliasClassID["gax"] = 8; NTIPAliasClassID["greataxe"] = 8; +NTIPAliasClassID["gix"] = 9; NTIPAliasClassID["giantaxe"] = 9; +NTIPAliasClassID["wnd"] = 10; NTIPAliasClassID["wand"] = 10; +NTIPAliasClassID["ywn"] = 11; NTIPAliasClassID["yewwand"] = 11; +NTIPAliasClassID["bwn"] = 12; NTIPAliasClassID["bonewand"] = 12; +NTIPAliasClassID["gwn"] = 13; NTIPAliasClassID["grimwand"] = 13; +NTIPAliasClassID["clb"] = 14; NTIPAliasClassID["club"] = 14; +NTIPAliasClassID["scp"] = 15; NTIPAliasClassID["scepter"] = 15; +NTIPAliasClassID["gsc"] = 16; NTIPAliasClassID["grandscepter"] = 16; +NTIPAliasClassID["wsp"] = 17; NTIPAliasClassID["warscepter"] = 17; +NTIPAliasClassID["spc"] = 18; NTIPAliasClassID["spikedclub"] = 18; +NTIPAliasClassID["mac"] = 19; NTIPAliasClassID["mace"] = 19; +NTIPAliasClassID["mst"] = 20; NTIPAliasClassID["morningstar"] = 20; +NTIPAliasClassID["fla"] = 21; NTIPAliasClassID["flail"] = 21; +NTIPAliasClassID["whm"] = 22; NTIPAliasClassID["warhammer"] = 22; +NTIPAliasClassID["mau"] = 23; NTIPAliasClassID["maul"] = 23; +NTIPAliasClassID["gma"] = 24; NTIPAliasClassID["greatmaul"] = 24; +NTIPAliasClassID["ssd"] = 25; NTIPAliasClassID["shortsword"] = 25; +NTIPAliasClassID["scm"] = 26; NTIPAliasClassID["scimitar"] = 26; +NTIPAliasClassID["sbr"] = 27; NTIPAliasClassID["sabre"] = 27; +NTIPAliasClassID["flc"] = 28; NTIPAliasClassID["falchion"] = 28; +NTIPAliasClassID["crs"] = 29; NTIPAliasClassID["crystalsword"] = 29; +NTIPAliasClassID["bsd"] = 30; NTIPAliasClassID["broadsword"] = 30; +NTIPAliasClassID["lsd"] = 31; NTIPAliasClassID["longsword"] = 31; +NTIPAliasClassID["wsd"] = 32; NTIPAliasClassID["warsword"] = 32; +NTIPAliasClassID["2hs"] = 33; NTIPAliasClassID["twohandedsword"] = 33; +NTIPAliasClassID["clm"] = 34; NTIPAliasClassID["claymore"] = 34; +NTIPAliasClassID["gis"] = 35; NTIPAliasClassID["giantsword"] = 35; +NTIPAliasClassID["bsw"] = 36; NTIPAliasClassID["bastardsword"] = 36; +NTIPAliasClassID["flb"] = 37; NTIPAliasClassID["flamberge"] = 37; +NTIPAliasClassID["gsd"] = 38; NTIPAliasClassID["greatsword"] = 38; +NTIPAliasClassID["dgr"] = 39; NTIPAliasClassID["dagger"] = 39; +NTIPAliasClassID["dir"] = 40; NTIPAliasClassID["dirk"] = 40; +NTIPAliasClassID["kri"] = 41; NTIPAliasClassID["kris"] = 41; +NTIPAliasClassID["bld"] = 42; NTIPAliasClassID["blade"] = 42; +NTIPAliasClassID["tkf"] = 43; NTIPAliasClassID["throwingknife"] = 43; +NTIPAliasClassID["tax"] = 44; NTIPAliasClassID["throwingaxe"] = 44; +NTIPAliasClassID["bkf"] = 45; NTIPAliasClassID["balancedknife"] = 45; +NTIPAliasClassID["bal"] = 46; NTIPAliasClassID["balancedaxe"] = 46; +NTIPAliasClassID["jav"] = 47; NTIPAliasClassID["javelin"] = 47; +NTIPAliasClassID["pil"] = 48; NTIPAliasClassID["pilum"] = 48; +NTIPAliasClassID["ssp"] = 49; NTIPAliasClassID["shortspear"] = 49; +NTIPAliasClassID["glv"] = 50; NTIPAliasClassID["glaive"] = 50; +NTIPAliasClassID["tsp"] = 51; NTIPAliasClassID["throwingspear"] = 51; +NTIPAliasClassID["spr"] = 52; NTIPAliasClassID["spear"] = 52; +NTIPAliasClassID["tri"] = 53; NTIPAliasClassID["trident"] = 53; +NTIPAliasClassID["brn"] = 54; NTIPAliasClassID["brandistock"] = 54; +NTIPAliasClassID["spt"] = 55; NTIPAliasClassID["spetum"] = 55; +NTIPAliasClassID["pik"] = 56; NTIPAliasClassID["pike"] = 56; +NTIPAliasClassID["bar"] = 57; NTIPAliasClassID["bardiche"] = 57; +NTIPAliasClassID["vou"] = 58; NTIPAliasClassID["voulge"] = 58; +NTIPAliasClassID["scy"] = 59; NTIPAliasClassID["scythe"] = 59; +NTIPAliasClassID["pax"] = 60; NTIPAliasClassID["poleaxe"] = 60; +NTIPAliasClassID["hal"] = 61; NTIPAliasClassID["halberd"] = 61; +NTIPAliasClassID["wsc"] = 62; NTIPAliasClassID["warscythe"] = 62; +NTIPAliasClassID["sst"] = 63; NTIPAliasClassID["shortstaff"] = 63; +NTIPAliasClassID["lst"] = 64; NTIPAliasClassID["longstaff"] = 64; +NTIPAliasClassID["cst"] = 65; NTIPAliasClassID["gnarledstaff"] = 65; +NTIPAliasClassID["bst"] = 66; NTIPAliasClassID["battlestaff"] = 66; +NTIPAliasClassID["wst"] = 67; NTIPAliasClassID["warstaff"] = 67; +NTIPAliasClassID["sbw"] = 68; NTIPAliasClassID["shortbow"] = 68; +NTIPAliasClassID["hbw"] = 69; NTIPAliasClassID["hunter'sbow"] = 69; +NTIPAliasClassID["lbw"] = 70; NTIPAliasClassID["longbow"] = 70; +NTIPAliasClassID["cbw"] = 71; NTIPAliasClassID["compositebow"] = 71; +NTIPAliasClassID["sbb"] = 72; NTIPAliasClassID["shortbattlebow"] = 72; +NTIPAliasClassID["lbb"] = 73; NTIPAliasClassID["longbattlebow"] = 73; +NTIPAliasClassID["swb"] = 74; NTIPAliasClassID["shortwarbow"] = 74; +NTIPAliasClassID["lwb"] = 75; NTIPAliasClassID["longwarbow"] = 75; +NTIPAliasClassID["lxb"] = 76; NTIPAliasClassID["lightcrossbow"] = 76; +NTIPAliasClassID["mxb"] = 77; NTIPAliasClassID["crossbow"] = 77; +NTIPAliasClassID["hxb"] = 78; NTIPAliasClassID["heavycrossbow"] = 78; +NTIPAliasClassID["rxb"] = 79; NTIPAliasClassID["repeatingcrossbow"] = 79; +NTIPAliasClassID["gps"] = 80; NTIPAliasClassID["rancidgaspotion"] = 80; +NTIPAliasClassID["ops"] = 81; NTIPAliasClassID["oilpotion"] = 81; +NTIPAliasClassID["gpm"] = 82; NTIPAliasClassID["chokinggaspotion"] = 82; +NTIPAliasClassID["opm"] = 83; NTIPAliasClassID["explodingpotion"] = 83; +NTIPAliasClassID["gpl"] = 84; NTIPAliasClassID["stranglinggaspotion"] = 84; +NTIPAliasClassID["opl"] = 85; NTIPAliasClassID["fulminatingpotion"] = 85; +NTIPAliasClassID["d33"] = 86; NTIPAliasClassID["decoygidbinn"] = 86; +NTIPAliasClassID["g33"] = 87; NTIPAliasClassID["thegidbinn"] = 87; +NTIPAliasClassID["leg"] = 88; NTIPAliasClassID["wirt'sleg"] = 88; +NTIPAliasClassID["hdm"] = 89; NTIPAliasClassID["horadricmalus"] = 89; +NTIPAliasClassID["hfh"] = 90; NTIPAliasClassID["hellforgehammer"] = 90; +NTIPAliasClassID["hst"] = 91; NTIPAliasClassID["horadricstaff"] = 91; +NTIPAliasClassID["msf"] = 92; NTIPAliasClassID["shaftofthehoradricstaff"] = 92; +NTIPAliasClassID["9ha"] = 93; NTIPAliasClassID["hatchet"] = 93; +NTIPAliasClassID["9ax"] = 94; NTIPAliasClassID["cleaver"] = 94; +NTIPAliasClassID["92a"] = 95; NTIPAliasClassID["twinaxe"] = 95; +NTIPAliasClassID["9mp"] = 96; NTIPAliasClassID["crowbill"] = 96; +NTIPAliasClassID["9wa"] = 97; NTIPAliasClassID["naga"] = 97; +NTIPAliasClassID["9la"] = 98; NTIPAliasClassID["militaryaxe"] = 98; +NTIPAliasClassID["9ba"] = 99; NTIPAliasClassID["beardedaxe"] = 99; +NTIPAliasClassID["9bt"] = 100; NTIPAliasClassID["tabar"] = 100; +NTIPAliasClassID["9ga"] = 101; NTIPAliasClassID["gothicaxe"] = 101; +NTIPAliasClassID["9gi"] = 102; NTIPAliasClassID["ancientaxe"] = 102; +NTIPAliasClassID["9wn"] = 103; NTIPAliasClassID["burntwand"] = 103; +NTIPAliasClassID["9yw"] = 104; NTIPAliasClassID["petrifiedwand"] = 104; +NTIPAliasClassID["9bw"] = 105; NTIPAliasClassID["tombwand"] = 105; +NTIPAliasClassID["9gw"] = 106; NTIPAliasClassID["gravewand"] = 106; +NTIPAliasClassID["9cl"] = 107; NTIPAliasClassID["cudgel"] = 107; +NTIPAliasClassID["9sc"] = 108; NTIPAliasClassID["runescepter"] = 108; +NTIPAliasClassID["9qs"] = 109; NTIPAliasClassID["holywatersprinkler"] = 109; +NTIPAliasClassID["9ws"] = 110; NTIPAliasClassID["divinescepter"] = 110; +NTIPAliasClassID["9sp"] = 111; NTIPAliasClassID["barbedclub"] = 111; +NTIPAliasClassID["9ma"] = 112; NTIPAliasClassID["flangedmace"] = 112; +NTIPAliasClassID["9mt"] = 113; NTIPAliasClassID["jaggedstar"] = 113; +NTIPAliasClassID["9fl"] = 114; NTIPAliasClassID["knout"] = 114; +NTIPAliasClassID["9wh"] = 115; NTIPAliasClassID["battlehammer"] = 115; +NTIPAliasClassID["9m9"] = 116; NTIPAliasClassID["warclub"] = 116; +NTIPAliasClassID["9gm"] = 117; NTIPAliasClassID["marteldefer"] = 117; +NTIPAliasClassID["9ss"] = 118; NTIPAliasClassID["gladius"] = 118; +NTIPAliasClassID["9sm"] = 119; NTIPAliasClassID["cutlass"] = 119; +NTIPAliasClassID["9sb"] = 120; NTIPAliasClassID["shamshir"] = 120; +NTIPAliasClassID["9fc"] = 121; NTIPAliasClassID["tulwar"] = 121; +NTIPAliasClassID["9cr"] = 122; NTIPAliasClassID["dimensionalblade"] = 122; +NTIPAliasClassID["9bs"] = 123; NTIPAliasClassID["battlesword"] = 123; +NTIPAliasClassID["9ls"] = 124; NTIPAliasClassID["runesword"] = 124; +NTIPAliasClassID["9wd"] = 125; NTIPAliasClassID["ancientsword"] = 125; +NTIPAliasClassID["92h"] = 126; NTIPAliasClassID["espandon"] = 126; +NTIPAliasClassID["9cm"] = 127; NTIPAliasClassID["dacianfalx"] = 127; +NTIPAliasClassID["9gs"] = 128; NTIPAliasClassID["tusksword"] = 128; +NTIPAliasClassID["9b9"] = 129; NTIPAliasClassID["gothicsword"] = 129; +NTIPAliasClassID["9fb"] = 130; NTIPAliasClassID["zweihander"] = 130; +NTIPAliasClassID["9gd"] = 131; NTIPAliasClassID["executionersword"] = 131; +NTIPAliasClassID["9dg"] = 132; NTIPAliasClassID["poignard"] = 132; +NTIPAliasClassID["9di"] = 133; NTIPAliasClassID["rondel"] = 133; +NTIPAliasClassID["9kr"] = 134; NTIPAliasClassID["cinquedeas"] = 134; +NTIPAliasClassID["9bl"] = 135; NTIPAliasClassID["stiletto"] = 135; +NTIPAliasClassID["9tk"] = 136; NTIPAliasClassID["battledart"] = 136; +NTIPAliasClassID["9ta"] = 137; NTIPAliasClassID["francisca"] = 137; +NTIPAliasClassID["9bk"] = 138; NTIPAliasClassID["wardart"] = 138; +NTIPAliasClassID["9b8"] = 139; NTIPAliasClassID["hurlbat"] = 139; +NTIPAliasClassID["9ja"] = 140; NTIPAliasClassID["warjavelin"] = 140; +NTIPAliasClassID["9pi"] = 141; NTIPAliasClassID["greatpilum"] = 141; +NTIPAliasClassID["9s9"] = 142; NTIPAliasClassID["simbilan"] = 142; +NTIPAliasClassID["9gl"] = 143; NTIPAliasClassID["spiculum"] = 143; +NTIPAliasClassID["9ts"] = 144; NTIPAliasClassID["harpoon"] = 144; +NTIPAliasClassID["9sr"] = 145; NTIPAliasClassID["warspear"] = 145; +NTIPAliasClassID["9tr"] = 146; NTIPAliasClassID["fuscina"] = 146; +NTIPAliasClassID["9br"] = 147; NTIPAliasClassID["warfork"] = 147; +NTIPAliasClassID["9st"] = 148; NTIPAliasClassID["yari"] = 148; +NTIPAliasClassID["9p9"] = 149; NTIPAliasClassID["lance"] = 149; +NTIPAliasClassID["9b7"] = 150; NTIPAliasClassID["lochaberaxe"] = 150; +NTIPAliasClassID["9vo"] = 151; NTIPAliasClassID["bill"] = 151; +NTIPAliasClassID["9s8"] = 152; NTIPAliasClassID["battlescythe"] = 152; +NTIPAliasClassID["9pa"] = 153; NTIPAliasClassID["partizan"] = 153; +NTIPAliasClassID["9h9"] = 154; NTIPAliasClassID["becdecorbin"] = 154; +NTIPAliasClassID["9wc"] = 155; NTIPAliasClassID["grimscythe"] = 155; +NTIPAliasClassID["8ss"] = 156; NTIPAliasClassID["jostaff"] = 156; +NTIPAliasClassID["8ls"] = 157; NTIPAliasClassID["quarterstaff"] = 157; +NTIPAliasClassID["8cs"] = 158; NTIPAliasClassID["cedarstaff"] = 158; +NTIPAliasClassID["8bs"] = 159; NTIPAliasClassID["gothicstaff"] = 159; +NTIPAliasClassID["8ws"] = 160; NTIPAliasClassID["runestaff"] = 160; +NTIPAliasClassID["8sb"] = 161; NTIPAliasClassID["edgebow"] = 161; +NTIPAliasClassID["8hb"] = 162; NTIPAliasClassID["razorbow"] = 162; +NTIPAliasClassID["8lb"] = 163; NTIPAliasClassID["cedarbow"] = 163; +NTIPAliasClassID["8cb"] = 164; NTIPAliasClassID["doublebow"] = 164; +NTIPAliasClassID["8s8"] = 165; NTIPAliasClassID["shortsiegebow"] = 165; +NTIPAliasClassID["8l8"] = 166; NTIPAliasClassID["largesiegebow"] = 166; +NTIPAliasClassID["8sw"] = 167; NTIPAliasClassID["runebow"] = 167; +NTIPAliasClassID["8lw"] = 168; NTIPAliasClassID["gothicbow"] = 168; +NTIPAliasClassID["8lx"] = 169; NTIPAliasClassID["arbalest"] = 169; +NTIPAliasClassID["8mx"] = 170; NTIPAliasClassID["siegecrossbow"] = 170; +NTIPAliasClassID["8hx"] = 171; NTIPAliasClassID["ballista"] = 171; +NTIPAliasClassID["8rx"] = 172; NTIPAliasClassID["chukonu"] = 172; +NTIPAliasClassID["qf1"] = 173; NTIPAliasClassID["khalim'sflail"] = 173; +NTIPAliasClassID["qf2"] = 174; NTIPAliasClassID["khalim'swill"] = 174; +NTIPAliasClassID["ktr"] = 175; NTIPAliasClassID["katar"] = 175; +NTIPAliasClassID["wrb"] = 176; NTIPAliasClassID["wristblade"] = 176; +NTIPAliasClassID["axf"] = 177; NTIPAliasClassID["hatchethands"] = 177; +NTIPAliasClassID["ces"] = 178; NTIPAliasClassID["cestus"] = 178; +NTIPAliasClassID["clw"] = 179; NTIPAliasClassID["claws"] = 179; +NTIPAliasClassID["btl"] = 180; NTIPAliasClassID["bladetalons"] = 180; +NTIPAliasClassID["skr"] = 181; NTIPAliasClassID["scissorskatar"] = 181; +NTIPAliasClassID["9ar"] = 182; NTIPAliasClassID["quhab"] = 182; +NTIPAliasClassID["9wb"] = 183; NTIPAliasClassID["wristspike"] = 183; +NTIPAliasClassID["9xf"] = 184; NTIPAliasClassID["fascia"] = 184; +NTIPAliasClassID["9cs"] = 185; NTIPAliasClassID["handscythe"] = 185; +NTIPAliasClassID["9lw"] = 186; NTIPAliasClassID["greaterclaws"] = 186; +NTIPAliasClassID["9tw"] = 187; NTIPAliasClassID["greatertalons"] = 187; +NTIPAliasClassID["9qr"] = 188; NTIPAliasClassID["scissorsquhab"] = 188; +NTIPAliasClassID["7ar"] = 189; NTIPAliasClassID["suwayyah"] = 189; +NTIPAliasClassID["7wb"] = 190; NTIPAliasClassID["wristsword"] = 190; +NTIPAliasClassID["7xf"] = 191; NTIPAliasClassID["warfist"] = 191; +NTIPAliasClassID["7cs"] = 192; NTIPAliasClassID["battlecestus"] = 192; +NTIPAliasClassID["7lw"] = 193; NTIPAliasClassID["feralclaws"] = 193; +NTIPAliasClassID["7tw"] = 194; NTIPAliasClassID["runictalons"] = 194; +NTIPAliasClassID["7qr"] = 195; NTIPAliasClassID["scissorssuwayyah"] = 195; +NTIPAliasClassID["7ha"] = 196; NTIPAliasClassID["tomahawk"] = 196; +NTIPAliasClassID["7ax"] = 197; NTIPAliasClassID["smallcrescent"] = 197; +NTIPAliasClassID["72a"] = 198; NTIPAliasClassID["ettinaxe"] = 198; +NTIPAliasClassID["7mp"] = 199; NTIPAliasClassID["warspike"] = 199; +NTIPAliasClassID["7wa"] = 200; NTIPAliasClassID["berserkeraxe"] = 200; +NTIPAliasClassID["7la"] = 201; NTIPAliasClassID["feralaxe"] = 201; +NTIPAliasClassID["7ba"] = 202; NTIPAliasClassID["silveredgedaxe"] = 202; +NTIPAliasClassID["7bt"] = 203; NTIPAliasClassID["decapitator"] = 203; +NTIPAliasClassID["7ga"] = 204; NTIPAliasClassID["championaxe"] = 204; +NTIPAliasClassID["7gi"] = 205; NTIPAliasClassID["gloriousaxe"] = 205; +NTIPAliasClassID["7wn"] = 206; NTIPAliasClassID["polishedwand"] = 206; +NTIPAliasClassID["7yw"] = 207; NTIPAliasClassID["ghostwand"] = 207; +NTIPAliasClassID["7bw"] = 208; NTIPAliasClassID["lichwand"] = 208; +NTIPAliasClassID["7gw"] = 209; NTIPAliasClassID["unearthedwand"] = 209; +NTIPAliasClassID["7cl"] = 210; NTIPAliasClassID["truncheon"] = 210; +NTIPAliasClassID["7sc"] = 211; NTIPAliasClassID["mightyscepter"] = 211; +NTIPAliasClassID["7qs"] = 212; NTIPAliasClassID["seraphrod"] = 212; +NTIPAliasClassID["7ws"] = 213; NTIPAliasClassID["caduceus"] = 213; +NTIPAliasClassID["7sp"] = 214; NTIPAliasClassID["tyrantclub"] = 214; +NTIPAliasClassID["7ma"] = 215; NTIPAliasClassID["reinforcedmace"] = 215; +NTIPAliasClassID["7mt"] = 216; NTIPAliasClassID["devilstar"] = 216; +NTIPAliasClassID["7fl"] = 217; NTIPAliasClassID["scourge"] = 217; +NTIPAliasClassID["7wh"] = 218; NTIPAliasClassID["legendarymallet"] = 218; +NTIPAliasClassID["7m7"] = 219; NTIPAliasClassID["ogremaul"] = 219; +NTIPAliasClassID["7gm"] = 220; NTIPAliasClassID["thundermaul"] = 220; +NTIPAliasClassID["7ss"] = 221; NTIPAliasClassID["falcata"] = 221; +NTIPAliasClassID["7sm"] = 222; NTIPAliasClassID["ataghan"] = 222; +NTIPAliasClassID["7sb"] = 223; NTIPAliasClassID["elegantblade"] = 223; +NTIPAliasClassID["7fc"] = 224; NTIPAliasClassID["hydraedge"] = 224; +NTIPAliasClassID["7cr"] = 225; NTIPAliasClassID["phaseblade"] = 225; +NTIPAliasClassID["7bs"] = 226; NTIPAliasClassID["conquestsword"] = 226; +NTIPAliasClassID["7ls"] = 227; NTIPAliasClassID["crypticsword"] = 227; +NTIPAliasClassID["7wd"] = 228; NTIPAliasClassID["mythicalsword"] = 228; +NTIPAliasClassID["72h"] = 229; NTIPAliasClassID["legendsword"] = 229; +NTIPAliasClassID["7cm"] = 230; NTIPAliasClassID["highlandblade"] = 230; +NTIPAliasClassID["7gs"] = 231; NTIPAliasClassID["balrogblade"] = 231; +NTIPAliasClassID["7b7"] = 232; NTIPAliasClassID["championsword"] = 232; +NTIPAliasClassID["7fb"] = 233; NTIPAliasClassID["colossussword"] = 233; +NTIPAliasClassID["7gd"] = 234; NTIPAliasClassID["colossusblade"] = 234; +NTIPAliasClassID["7dg"] = 235; NTIPAliasClassID["boneknife"] = 235; +NTIPAliasClassID["7di"] = 236; NTIPAliasClassID["mithrilpoint"] = 236; +NTIPAliasClassID["7kr"] = 237; NTIPAliasClassID["fangedknife"] = 237; +NTIPAliasClassID["7bl"] = 238; NTIPAliasClassID["legendspike"] = 238; +NTIPAliasClassID["7tk"] = 239; NTIPAliasClassID["flyingknife"] = 239; +NTIPAliasClassID["7ta"] = 240; NTIPAliasClassID["flyingaxe"] = 240; +NTIPAliasClassID["7bk"] = 241; NTIPAliasClassID["wingedknife"] = 241; +NTIPAliasClassID["7b8"] = 242; NTIPAliasClassID["wingedaxe"] = 242; +NTIPAliasClassID["7ja"] = 243; NTIPAliasClassID["hyperionjavelin"] = 243; +NTIPAliasClassID["7pi"] = 244; NTIPAliasClassID["stygianpilum"] = 244; +NTIPAliasClassID["7s7"] = 245; NTIPAliasClassID["balrogspear"] = 245; +NTIPAliasClassID["7gl"] = 246; NTIPAliasClassID["ghostglaive"] = 246; +NTIPAliasClassID["7ts"] = 247; NTIPAliasClassID["wingedharpoon"] = 247; +NTIPAliasClassID["7sr"] = 248; NTIPAliasClassID["hyperionspear"] = 248; +NTIPAliasClassID["7tr"] = 249; NTIPAliasClassID["stygianpike"] = 249; +NTIPAliasClassID["7br"] = 250; NTIPAliasClassID["mancatcher"] = 250; +NTIPAliasClassID["7st"] = 251; NTIPAliasClassID["ghostspear"] = 251; +NTIPAliasClassID["7p7"] = 252; NTIPAliasClassID["warpike"] = 252; +NTIPAliasClassID["7o7"] = 253; NTIPAliasClassID["ogreaxe"] = 253; +NTIPAliasClassID["7vo"] = 254; NTIPAliasClassID["colossusvoulge"] = 254; +NTIPAliasClassID["7s8"] = 255; NTIPAliasClassID["thresher"] = 255; +NTIPAliasClassID["7pa"] = 256; NTIPAliasClassID["crypticaxe"] = 256; +NTIPAliasClassID["7h7"] = 257; NTIPAliasClassID["greatpoleaxe"] = 257; +NTIPAliasClassID["7wc"] = 258; NTIPAliasClassID["giantthresher"] = 258; +NTIPAliasClassID["6ss"] = 259; NTIPAliasClassID["walkingstick"] = 259; +NTIPAliasClassID["6ls"] = 260; NTIPAliasClassID["stalagmite"] = 260; +NTIPAliasClassID["6cs"] = 261; NTIPAliasClassID["elderstaff"] = 261; +NTIPAliasClassID["6bs"] = 262; NTIPAliasClassID["shillelagh"] = 262; +NTIPAliasClassID["6ws"] = 263; NTIPAliasClassID["archonstaff"] = 263; +NTIPAliasClassID["6sb"] = 264; NTIPAliasClassID["spiderbow"] = 264; +NTIPAliasClassID["6hb"] = 265; NTIPAliasClassID["bladebow"] = 265; +NTIPAliasClassID["6lb"] = 266; NTIPAliasClassID["shadowbow"] = 266; +NTIPAliasClassID["6cb"] = 267; NTIPAliasClassID["greatbow"] = 267; +NTIPAliasClassID["6s7"] = 268; NTIPAliasClassID["diamondbow"] = 268; +NTIPAliasClassID["6l7"] = 269; NTIPAliasClassID["crusaderbow"] = 269; +NTIPAliasClassID["6sw"] = 270; NTIPAliasClassID["wardbow"] = 270; +NTIPAliasClassID["6lw"] = 271; NTIPAliasClassID["hydrabow"] = 271; +NTIPAliasClassID["6lx"] = 272; NTIPAliasClassID["pelletbow"] = 272; +NTIPAliasClassID["6mx"] = 273; NTIPAliasClassID["gorgoncrossbow"] = 273; +NTIPAliasClassID["6hx"] = 274; NTIPAliasClassID["colossuscrossbow"] = 274; +NTIPAliasClassID["6rx"] = 275; NTIPAliasClassID["demoncrossbow"] = 275; +NTIPAliasClassID["ob1"] = 276; NTIPAliasClassID["eagleorb"] = 276; +NTIPAliasClassID["ob2"] = 277; NTIPAliasClassID["sacredglobe"] = 277; +NTIPAliasClassID["ob3"] = 278; NTIPAliasClassID["smokedsphere"] = 278; +NTIPAliasClassID["ob4"] = 279; NTIPAliasClassID["claspedorb"] = 279; +NTIPAliasClassID["ob5"] = 280; NTIPAliasClassID["jared'sstone"] = 280; +NTIPAliasClassID["am1"] = 281; NTIPAliasClassID["stagbow"] = 281; +NTIPAliasClassID["am2"] = 282; NTIPAliasClassID["reflexbow"] = 282; +NTIPAliasClassID["am3"] = 283; NTIPAliasClassID["maidenspear"] = 283; +NTIPAliasClassID["am4"] = 284; NTIPAliasClassID["maidenpike"] = 284; +NTIPAliasClassID["am5"] = 285; NTIPAliasClassID["maidenjavelin"] = 285; +NTIPAliasClassID["ob6"] = 286; NTIPAliasClassID["glowingorb"] = 286; +NTIPAliasClassID["ob7"] = 287; NTIPAliasClassID["crystallineglobe"] = 287; +NTIPAliasClassID["ob8"] = 288; NTIPAliasClassID["cloudysphere"] = 288; +NTIPAliasClassID["ob9"] = 289; NTIPAliasClassID["sparklingball"] = 289; +NTIPAliasClassID["oba"] = 290; NTIPAliasClassID["swirlingcrystal"] = 290; +NTIPAliasClassID["am6"] = 291; NTIPAliasClassID["ashwoodbow"] = 291; +NTIPAliasClassID["am7"] = 292; NTIPAliasClassID["ceremonialbow"] = 292; +NTIPAliasClassID["am8"] = 293; NTIPAliasClassID["ceremonialspear"] = 293; +NTIPAliasClassID["am9"] = 294; NTIPAliasClassID["ceremonialpike"] = 294; +NTIPAliasClassID["ama"] = 295; NTIPAliasClassID["ceremonialjavelin"] = 295; +NTIPAliasClassID["obb"] = 296; NTIPAliasClassID["heavenlystone"] = 296; +NTIPAliasClassID["obc"] = 297; NTIPAliasClassID["eldritchorb"] = 297; +NTIPAliasClassID["obd"] = 298; NTIPAliasClassID["demonheart"] = 298; +NTIPAliasClassID["obe"] = 299; NTIPAliasClassID["vortexorb"] = 299; +NTIPAliasClassID["obf"] = 300; NTIPAliasClassID["dimensionalshard"] = 300; +NTIPAliasClassID["amb"] = 301; NTIPAliasClassID["matriarchalbow"] = 301; +NTIPAliasClassID["amc"] = 302; NTIPAliasClassID["grandmatronbow"] = 302; +NTIPAliasClassID["amd"] = 303; NTIPAliasClassID["matriarchalspear"] = 303; +NTIPAliasClassID["ame"] = 304; NTIPAliasClassID["matriarchalpike"] = 304; +NTIPAliasClassID["amf"] = 305; NTIPAliasClassID["matriarchaljavelin"] = 305; +NTIPAliasClassID["cap"] = 306; +NTIPAliasClassID["skp"] = 307; NTIPAliasClassID["skullcap"] = 307; +NTIPAliasClassID["hlm"] = 308; NTIPAliasClassID["helm"] = 308; +NTIPAliasClassID["fhl"] = 309; NTIPAliasClassID["fullhelm"] = 309; +NTIPAliasClassID["ghm"] = 310; NTIPAliasClassID["greathelm"] = 310; +NTIPAliasClassID["crn"] = 311; NTIPAliasClassID["crown"] = 311; +NTIPAliasClassID["msk"] = 312; NTIPAliasClassID["mask"] = 312; +NTIPAliasClassID["qui"] = 313; NTIPAliasClassID["quiltedarmor"] = 313; +NTIPAliasClassID["lea"] = 314; NTIPAliasClassID["leatherarmor"] = 314; +NTIPAliasClassID["hla"] = 315; NTIPAliasClassID["hardleatherarmor"] = 315; +NTIPAliasClassID["stu"] = 316; NTIPAliasClassID["studdedleather"] = 316; +NTIPAliasClassID["rng"] = 317; NTIPAliasClassID["ringmail"] = 317; +NTIPAliasClassID["scl"] = 318; NTIPAliasClassID["scalemail"] = 318; +NTIPAliasClassID["chn"] = 319; NTIPAliasClassID["chainmail"] = 319; +NTIPAliasClassID["brs"] = 320; NTIPAliasClassID["breastplate"] = 320; +NTIPAliasClassID["spl"] = 321; NTIPAliasClassID["splintmail"] = 321; +NTIPAliasClassID["plt"] = 322; NTIPAliasClassID["platemail"] = 322; +NTIPAliasClassID["fld"] = 323; NTIPAliasClassID["fieldplate"] = 323; +NTIPAliasClassID["gth"] = 324; NTIPAliasClassID["gothicplate"] = 324; +NTIPAliasClassID["ful"] = 325; NTIPAliasClassID["fullplatemail"] = 325; +NTIPAliasClassID["aar"] = 326; NTIPAliasClassID["ancientarmor"] = 326; +NTIPAliasClassID["ltp"] = 327; NTIPAliasClassID["lightplate"] = 327; +NTIPAliasClassID["buc"] = 328; NTIPAliasClassID["buckler"] = 328; +NTIPAliasClassID["sml"] = 329; NTIPAliasClassID["smallshield"] = 329; +NTIPAliasClassID["lrg"] = 330; NTIPAliasClassID["largeshield"] = 330; +NTIPAliasClassID["kit"] = 331; NTIPAliasClassID["kiteshield"] = 331; +NTIPAliasClassID["tow"] = 332; NTIPAliasClassID["towershield"] = 332; +NTIPAliasClassID["gts"] = 333; NTIPAliasClassID["gothicshield"] = 333; +NTIPAliasClassID["lgl"] = 334; NTIPAliasClassID["leathergloves"] = 334; +NTIPAliasClassID["vgl"] = 335; NTIPAliasClassID["heavygloves"] = 335; +NTIPAliasClassID["mgl"] = 336; NTIPAliasClassID["chaingloves"] = 336; +NTIPAliasClassID["tgl"] = 337; NTIPAliasClassID["lightgauntlets"] = 337; +NTIPAliasClassID["hgl"] = 338; NTIPAliasClassID["gauntlets"] = 338; +NTIPAliasClassID["lbt"] = 339; NTIPAliasClassID["boots"] = 339; +NTIPAliasClassID["vbt"] = 340; NTIPAliasClassID["heavyboots"] = 340; +NTIPAliasClassID["mbt"] = 341; NTIPAliasClassID["chainboots"] = 341; +NTIPAliasClassID["tbt"] = 342; NTIPAliasClassID["lightplatedboots"] = 342; +NTIPAliasClassID["hbt"] = 343; NTIPAliasClassID["greaves"] = 343; +NTIPAliasClassID["lbl"] = 344; NTIPAliasClassID["sash"] = 344; +NTIPAliasClassID["vbl"] = 345; NTIPAliasClassID["lightbelt"] = 345; +NTIPAliasClassID["mbl"] = 346; NTIPAliasClassID["belt"] = 346; +NTIPAliasClassID["tbl"] = 347; NTIPAliasClassID["heavybelt"] = 347; +NTIPAliasClassID["hbl"] = 348; NTIPAliasClassID["platedbelt"] = 348; +NTIPAliasClassID["bhm"] = 349; NTIPAliasClassID["bonehelm"] = 349; +NTIPAliasClassID["bsh"] = 350; NTIPAliasClassID["boneshield"] = 350; +NTIPAliasClassID["spk"] = 351; NTIPAliasClassID["spikedshield"] = 351; +NTIPAliasClassID["xap"] = 352; NTIPAliasClassID["warhat"] = 352; +NTIPAliasClassID["xkp"] = 353; NTIPAliasClassID["sallet"] = 353; +NTIPAliasClassID["xlm"] = 354; NTIPAliasClassID["casque"] = 354; +NTIPAliasClassID["xhl"] = 355; NTIPAliasClassID["basinet"] = 355; +NTIPAliasClassID["xhm"] = 356; NTIPAliasClassID["wingedhelm"] = 356; +NTIPAliasClassID["xrn"] = 357; NTIPAliasClassID["grandcrown"] = 357; +NTIPAliasClassID["xsk"] = 358; NTIPAliasClassID["deathmask"] = 358; +NTIPAliasClassID["xui"] = 359; NTIPAliasClassID["ghostarmor"] = 359; +NTIPAliasClassID["xea"] = 360; NTIPAliasClassID["serpentskinarmor"] = 360; +NTIPAliasClassID["xla"] = 361; NTIPAliasClassID["demonhidearmor"] = 361; +NTIPAliasClassID["xtu"] = 362; NTIPAliasClassID["trellisedarmor"] = 362; +NTIPAliasClassID["xng"] = 363; NTIPAliasClassID["linkedmail"] = 363; +NTIPAliasClassID["xcl"] = 364; NTIPAliasClassID["tigulatedmail"] = 364; +NTIPAliasClassID["xhn"] = 365; NTIPAliasClassID["mesharmor"] = 365; +NTIPAliasClassID["xrs"] = 366; NTIPAliasClassID["cuirass"] = 366; +NTIPAliasClassID["xpl"] = 367; NTIPAliasClassID["russetarmor"] = 367; +NTIPAliasClassID["xlt"] = 368; NTIPAliasClassID["templarcoat"] = 368; +NTIPAliasClassID["xld"] = 369; NTIPAliasClassID["sharktootharmor"] = 369; +NTIPAliasClassID["xth"] = 370; NTIPAliasClassID["embossedplate"] = 370; +NTIPAliasClassID["xul"] = 371; NTIPAliasClassID["chaosarmor"] = 371; +NTIPAliasClassID["xar"] = 372; NTIPAliasClassID["ornateplate"] = 372; +NTIPAliasClassID["xtp"] = 373; NTIPAliasClassID["mageplate"] = 373; +NTIPAliasClassID["xuc"] = 374; NTIPAliasClassID["defender"] = 374; +NTIPAliasClassID["xml"] = 375; NTIPAliasClassID["roundshield"] = 375; +NTIPAliasClassID["xrg"] = 376; NTIPAliasClassID["scutum"] = 376; +NTIPAliasClassID["xit"] = 377; NTIPAliasClassID["dragonshield"] = 377; +NTIPAliasClassID["xow"] = 378; NTIPAliasClassID["pavise"] = 378; +NTIPAliasClassID["xts"] = 379; NTIPAliasClassID["ancientshield"] = 379; +NTIPAliasClassID["xlg"] = 380; NTIPAliasClassID["demonhidegloves"] = 380; +NTIPAliasClassID["xvg"] = 381; NTIPAliasClassID["sharkskingloves"] = 381; +NTIPAliasClassID["xmg"] = 382; NTIPAliasClassID["heavybracers"] = 382; +NTIPAliasClassID["xtg"] = 383; NTIPAliasClassID["battlegauntlets"] = 383; +NTIPAliasClassID["xhg"] = 384; NTIPAliasClassID["wargauntlets"] = 384; +NTIPAliasClassID["xlb"] = 385; NTIPAliasClassID["demonhideboots"] = 385; +NTIPAliasClassID["xvb"] = 386; NTIPAliasClassID["sharkskinboots"] = 386; +NTIPAliasClassID["xmb"] = 387; NTIPAliasClassID["meshboots"] = 387; +NTIPAliasClassID["xtb"] = 388; NTIPAliasClassID["battleboots"] = 388; +NTIPAliasClassID["xhb"] = 389; NTIPAliasClassID["warboots"] = 389; +NTIPAliasClassID["zlb"] = 390; NTIPAliasClassID["demonhidesash"] = 390; +NTIPAliasClassID["zvb"] = 391; NTIPAliasClassID["sharkskinbelt"] = 391; +NTIPAliasClassID["zmb"] = 392; NTIPAliasClassID["meshbelt"] = 392; +NTIPAliasClassID["ztb"] = 393; NTIPAliasClassID["battlebelt"] = 393; +NTIPAliasClassID["zhb"] = 394; NTIPAliasClassID["warbelt"] = 394; +NTIPAliasClassID["xh9"] = 395; NTIPAliasClassID["grimhelm"] = 395; +NTIPAliasClassID["xsh"] = 396; NTIPAliasClassID["grimshield"] = 396; +NTIPAliasClassID["xpk"] = 397; NTIPAliasClassID["barbedshield"] = 397; +NTIPAliasClassID["dr1"] = 398; NTIPAliasClassID["wolfhead"] = 398; +NTIPAliasClassID["dr2"] = 399; NTIPAliasClassID["hawkhelm"] = 399; +NTIPAliasClassID["dr3"] = 400; NTIPAliasClassID["antlers"] = 400; +NTIPAliasClassID["dr4"] = 401; NTIPAliasClassID["falconmask"] = 401; +NTIPAliasClassID["dr5"] = 402; NTIPAliasClassID["spiritmask"] = 402; +NTIPAliasClassID["ba1"] = 403; NTIPAliasClassID["jawbonecap"] = 403; +NTIPAliasClassID["ba2"] = 404; NTIPAliasClassID["fangedhelm"] = 404; +NTIPAliasClassID["ba3"] = 405; NTIPAliasClassID["hornedhelm"] = 405; +NTIPAliasClassID["ba4"] = 406; NTIPAliasClassID["assaulthelmet"] = 406; +NTIPAliasClassID["ba5"] = 407; NTIPAliasClassID["avengerguard"] = 407; +NTIPAliasClassID["pa1"] = 408; NTIPAliasClassID["targe"] = 408; +NTIPAliasClassID["pa2"] = 409; NTIPAliasClassID["rondache"] = 409; +NTIPAliasClassID["pa3"] = 410; NTIPAliasClassID["heraldicshield"] = 410; +NTIPAliasClassID["pa4"] = 411; NTIPAliasClassID["aerinshield"] = 411; +NTIPAliasClassID["pa5"] = 412; NTIPAliasClassID["crownshield"] = 412; +NTIPAliasClassID["ne1"] = 413; NTIPAliasClassID["preservedhead"] = 413; +NTIPAliasClassID["ne2"] = 414; NTIPAliasClassID["zombiehead"] = 414; +NTIPAliasClassID["ne3"] = 415; NTIPAliasClassID["unravellerhead"] = 415; +NTIPAliasClassID["ne4"] = 416; NTIPAliasClassID["gargoylehead"] = 416; +NTIPAliasClassID["ne5"] = 417; NTIPAliasClassID["demonhead"] = 417; +NTIPAliasClassID["ci0"] = 418; NTIPAliasClassID["circlet"] = 418; +NTIPAliasClassID["ci1"] = 419; NTIPAliasClassID["coronet"] = 419; +NTIPAliasClassID["ci2"] = 420; NTIPAliasClassID["tiara"] = 420; +NTIPAliasClassID["ci3"] = 421; NTIPAliasClassID["diadem"] = 421; +NTIPAliasClassID["uap"] = 422; NTIPAliasClassID["shako"] = 422; +NTIPAliasClassID["ukp"] = 423; NTIPAliasClassID["hydraskull"] = 423; +NTIPAliasClassID["ulm"] = 424; NTIPAliasClassID["armet"] = 424; +NTIPAliasClassID["uhl"] = 425; NTIPAliasClassID["giantconch"] = 425; +NTIPAliasClassID["uhm"] = 426; NTIPAliasClassID["spiredhelm"] = 426; +NTIPAliasClassID["urn"] = 427; NTIPAliasClassID["corona"] = 427; +NTIPAliasClassID["usk"] = 428; NTIPAliasClassID["demonhead"] = 428; +NTIPAliasClassID["uui"] = 429; NTIPAliasClassID["duskshroud"] = 429; +NTIPAliasClassID["uea"] = 430; NTIPAliasClassID["wyrmhide"] = 430; +NTIPAliasClassID["ula"] = 431; NTIPAliasClassID["scarabhusk"] = 431; +NTIPAliasClassID["utu"] = 432; NTIPAliasClassID["wirefleece"] = 432; +NTIPAliasClassID["ung"] = 433; NTIPAliasClassID["diamondmail"] = 433; +NTIPAliasClassID["ucl"] = 434; NTIPAliasClassID["loricatedmail"] = 434; +NTIPAliasClassID["uhn"] = 435; NTIPAliasClassID["boneweave"] = 435; +NTIPAliasClassID["urs"] = 436; NTIPAliasClassID["greathauberk"] = 436; +NTIPAliasClassID["upl"] = 437; NTIPAliasClassID["balrogskin"] = 437; +NTIPAliasClassID["ult"] = 438; NTIPAliasClassID["hellforgeplate"] = 438; +NTIPAliasClassID["uld"] = 439; NTIPAliasClassID["krakenshell"] = 439; +NTIPAliasClassID["uth"] = 440; NTIPAliasClassID["lacqueredplate"] = 440; +NTIPAliasClassID["uul"] = 441; NTIPAliasClassID["shadowplate"] = 441; +NTIPAliasClassID["uar"] = 442; NTIPAliasClassID["sacredarmor"] = 442; +NTIPAliasClassID["utp"] = 443; NTIPAliasClassID["archonplate"] = 443; +NTIPAliasClassID["uuc"] = 444; NTIPAliasClassID["heater"] = 444; +NTIPAliasClassID["uml"] = 445; NTIPAliasClassID["luna"] = 445; +NTIPAliasClassID["urg"] = 446; NTIPAliasClassID["hyperion"] = 446; +NTIPAliasClassID["uit"] = 447; NTIPAliasClassID["monarch"] = 447; +NTIPAliasClassID["uow"] = 448; NTIPAliasClassID["aegis"] = 448; +NTIPAliasClassID["uts"] = 449; NTIPAliasClassID["ward"] = 449; +NTIPAliasClassID["ulg"] = 450; NTIPAliasClassID["bramblemitts"] = 450; +NTIPAliasClassID["uvg"] = 451; NTIPAliasClassID["vampirebonegloves"] = 451; +NTIPAliasClassID["umg"] = 452; NTIPAliasClassID["vambraces"] = 452; +NTIPAliasClassID["utg"] = 453; NTIPAliasClassID["crusadergauntlets"] = 453; +NTIPAliasClassID["uhg"] = 454; NTIPAliasClassID["ogregauntlets"] = 454; +NTIPAliasClassID["ulb"] = 455; NTIPAliasClassID["wyrmhideboots"] = 455; +NTIPAliasClassID["uvb"] = 456; NTIPAliasClassID["scarabshellboots"] = 456; +NTIPAliasClassID["umb"] = 457; NTIPAliasClassID["boneweaveboots"] = 457; +NTIPAliasClassID["utb"] = 458; NTIPAliasClassID["mirroredboots"] = 458; +NTIPAliasClassID["uhb"] = 459; NTIPAliasClassID["myrmidongreaves"] = 459; +NTIPAliasClassID["ulc"] = 460; NTIPAliasClassID["spiderwebsash"] = 460; +NTIPAliasClassID["uvc"] = 461; NTIPAliasClassID["vampirefangbelt"] = 461; +NTIPAliasClassID["umc"] = 462; NTIPAliasClassID["mithrilcoil"] = 462; +NTIPAliasClassID["utc"] = 463; NTIPAliasClassID["trollbelt"] = 463; +NTIPAliasClassID["uhc"] = 464; NTIPAliasClassID["colossusgirdle"] = 464; +NTIPAliasClassID["uh9"] = 465; NTIPAliasClassID["bonevisage"] = 465; +NTIPAliasClassID["ush"] = 466; NTIPAliasClassID["trollnest"] = 466; +NTIPAliasClassID["upk"] = 467; NTIPAliasClassID["bladebarrier"] = 467; +NTIPAliasClassID["dr6"] = 468; NTIPAliasClassID["alphahelm"] = 468; +NTIPAliasClassID["dr7"] = 469; NTIPAliasClassID["griffonheaddress"] = 469; +NTIPAliasClassID["dr8"] = 470; NTIPAliasClassID["hunter'sguise"] = 470; +NTIPAliasClassID["dr9"] = 471; NTIPAliasClassID["sacredfeathers"] = 471; +NTIPAliasClassID["dra"] = 472; NTIPAliasClassID["totemicmask"] = 472; +NTIPAliasClassID["ba6"] = 473; NTIPAliasClassID["jawbonevisor"] = 473; +NTIPAliasClassID["ba7"] = 474; NTIPAliasClassID["lionhelm"] = 474; +NTIPAliasClassID["ba8"] = 475; NTIPAliasClassID["ragemask"] = 475; +NTIPAliasClassID["ba9"] = 476; NTIPAliasClassID["savagehelmet"] = 476; +NTIPAliasClassID["baa"] = 477; NTIPAliasClassID["slayerguard"] = 477; +NTIPAliasClassID["pa6"] = 478; NTIPAliasClassID["akarantarge"] = 478; +NTIPAliasClassID["pa7"] = 479; NTIPAliasClassID["akaranrondache"] = 479; +NTIPAliasClassID["pa8"] = 480; NTIPAliasClassID["protectorshield"] = 480; +NTIPAliasClassID["pa9"] = 481; NTIPAliasClassID["gildedshield"] = 481; +NTIPAliasClassID["paa"] = 482; NTIPAliasClassID["royalshield"] = 482; +NTIPAliasClassID["ne6"] = 483; NTIPAliasClassID["mummifiedtrophy"] = 483; +NTIPAliasClassID["ne7"] = 484; NTIPAliasClassID["fetishtrophy"] = 484; +NTIPAliasClassID["ne8"] = 485; NTIPAliasClassID["sextontrophy"] = 485; +NTIPAliasClassID["ne9"] = 486; NTIPAliasClassID["cantortrophy"] = 486; +NTIPAliasClassID["nea"] = 487; NTIPAliasClassID["hierophanttrophy"] = 487; +NTIPAliasClassID["drb"] = 488; NTIPAliasClassID["bloodspirit"] = 488; +NTIPAliasClassID["drc"] = 489; NTIPAliasClassID["sunspirit"] = 489; +NTIPAliasClassID["drd"] = 490; NTIPAliasClassID["earthspirit"] = 490; +NTIPAliasClassID["dre"] = 491; NTIPAliasClassID["skyspirit"] = 491; +NTIPAliasClassID["drf"] = 492; NTIPAliasClassID["dreamspirit"] = 492; +NTIPAliasClassID["bab"] = 493; NTIPAliasClassID["carnagehelm"] = 493; +NTIPAliasClassID["bac"] = 494; NTIPAliasClassID["furyvisor"] = 494; +NTIPAliasClassID["bad"] = 495; NTIPAliasClassID["destroyerhelm"] = 495; +NTIPAliasClassID["bae"] = 496; NTIPAliasClassID["conquerorcrown"] = 496; +NTIPAliasClassID["baf"] = 497; NTIPAliasClassID["guardiancrown"] = 497; +NTIPAliasClassID["pab"] = 498; NTIPAliasClassID["sacredtarge"] = 498; +NTIPAliasClassID["pac"] = 499; NTIPAliasClassID["sacredrondache"] = 499; +NTIPAliasClassID["pad"] = 500; NTIPAliasClassID["kurastshield"] = 500; +NTIPAliasClassID["pae"] = 501; NTIPAliasClassID["zakarumshield"] = 501; +NTIPAliasClassID["paf"] = 502; NTIPAliasClassID["vortexshield"] = 502; +NTIPAliasClassID["neb"] = 503; NTIPAliasClassID["minionskull"] = 503; +NTIPAliasClassID["neg"] = 504; NTIPAliasClassID["hellspawnskull"] = 504; +NTIPAliasClassID["ned"] = 505; NTIPAliasClassID["overseerskull"] = 505; +NTIPAliasClassID["nee"] = 506; NTIPAliasClassID["succubusskull"] = 506; +NTIPAliasClassID["nef"] = 507; NTIPAliasClassID["bloodlordskull"] = 507; +NTIPAliasClassID["elx"] = 508; NTIPAliasClassID["elixir"] = 508; +NTIPAliasClassID["hpo"] = 509; +NTIPAliasClassID["mpo"] = 510; +NTIPAliasClassID["hpf"] = 511; +NTIPAliasClassID["mpf"] = 512; +NTIPAliasClassID["vps"] = 513; NTIPAliasClassID["staminapotion"] = 513; +NTIPAliasClassID["yps"] = 514; NTIPAliasClassID["antidotepotion"] = 514; +NTIPAliasClassID["rvs"] = 515; NTIPAliasClassID["rejuvenationpotion"] = 515; +NTIPAliasClassID["rvl"] = 516; NTIPAliasClassID["fullrejuvenationpotion"] = 516; +NTIPAliasClassID["wms"] = 517; NTIPAliasClassID["thawingpotion"] = 517; +NTIPAliasClassID["tbk"] = 518; NTIPAliasClassID["tomeoftownportal"] = 518; +NTIPAliasClassID["ibk"] = 519; NTIPAliasClassID["tomeofidentify"] = 519; +NTIPAliasClassID["amu"] = 520; NTIPAliasClassID["amulet"] = 520; +NTIPAliasClassID["vip"] = 521; NTIPAliasClassID["topofthehoradricstaff"] = 521; +NTIPAliasClassID["rin"] = 522; NTIPAliasClassID["ring"] = 522; +NTIPAliasClassID["gld"] = 523; NTIPAliasClassID["gold"] = 523; +NTIPAliasClassID["bks"] = 524; NTIPAliasClassID["scrollofinifuss"] = 524; +NTIPAliasClassID["bkd"] = 525; NTIPAliasClassID["keytothecairnstones"] = 525; +NTIPAliasClassID["aqv"] = 526; NTIPAliasClassID["arrows"] = 526; +NTIPAliasClassID["tch"] = 527; NTIPAliasClassID["torch"] = 527; +NTIPAliasClassID["cqv"] = 528; NTIPAliasClassID["bolts"] = 528; +NTIPAliasClassID["tsc"] = 529; NTIPAliasClassID["scrolloftownportal"] = 529; +NTIPAliasClassID["isc"] = 530; NTIPAliasClassID["scrollofidentify"] = 530; +NTIPAliasClassID["hrt"] = 531; NTIPAliasClassID["heart"] = 531; +NTIPAliasClassID["brz"] = 532; NTIPAliasClassID["brain"] = 532; +NTIPAliasClassID["jaw"] = 533; NTIPAliasClassID["jawbone"] = 533; +NTIPAliasClassID["eyz"] = 534; NTIPAliasClassID["eye"] = 534; +NTIPAliasClassID["hrn"] = 535; NTIPAliasClassID["horn"] = 535; +NTIPAliasClassID["tal"] = 536; NTIPAliasClassID["tail"] = 536; +NTIPAliasClassID["flg"] = 537; NTIPAliasClassID["flag"] = 537; +NTIPAliasClassID["fng"] = 538; NTIPAliasClassID["fang"] = 538; +NTIPAliasClassID["qll"] = 539; NTIPAliasClassID["quill"] = 539; +NTIPAliasClassID["sol"] = 540; NTIPAliasClassID["soul"] = 540; +NTIPAliasClassID["scz"] = 541; NTIPAliasClassID["scalp"] = 541; +NTIPAliasClassID["spe"] = 542; NTIPAliasClassID["spleen"] = 542; +NTIPAliasClassID["key"] = 543; +NTIPAliasClassID["luv"] = 544; NTIPAliasClassID["theblacktowerkey"] = 544; +NTIPAliasClassID["xyz"] = 545; NTIPAliasClassID["potionoflife"] = 545; +NTIPAliasClassID["j34"] = 546; NTIPAliasClassID["ajadefigurine"] = 546; +NTIPAliasClassID["g34"] = 547; NTIPAliasClassID["thegoldenbird"] = 547; +NTIPAliasClassID["bbb"] = 548; NTIPAliasClassID["lamesen'stome"] = 548; +NTIPAliasClassID["box"] = 549; NTIPAliasClassID["horadriccube"] = 549; +NTIPAliasClassID["tr1"] = 550; NTIPAliasClassID["horadricscroll"] = 550; +NTIPAliasClassID["mss"] = 551; NTIPAliasClassID["mephisto'ssoulstone"] = 551; +NTIPAliasClassID["ass"] = 552; NTIPAliasClassID["bookofskill"] = 552; +NTIPAliasClassID["qey"] = 553; NTIPAliasClassID["khalim'seye"] = 553; +NTIPAliasClassID["qhr"] = 554; NTIPAliasClassID["khalim'sheart"] = 554; +NTIPAliasClassID["qbr"] = 555; NTIPAliasClassID["khalim'sbrain"] = 555; +NTIPAliasClassID["ear"] = 556; +NTIPAliasClassID["gcv"] = 557; NTIPAliasClassID["chippedamethyst"] = 557; +NTIPAliasClassID["gfv"] = 558; NTIPAliasClassID["flawedamethyst"] = 558; +NTIPAliasClassID["gsv"] = 559; NTIPAliasClassID["amethyst"] = 559; +NTIPAliasClassID["gzv"] = 560; NTIPAliasClassID["flawlessamethyst"] = 560; +NTIPAliasClassID["gpv"] = 561; NTIPAliasClassID["perfectamethyst"] = 561; +NTIPAliasClassID["gcy"] = 562; NTIPAliasClassID["chippedtopaz"] = 562; +NTIPAliasClassID["gfy"] = 563; NTIPAliasClassID["flawedtopaz"] = 563; +NTIPAliasClassID["gsy"] = 564; NTIPAliasClassID["topaz"] = 564; +NTIPAliasClassID["gly"] = 565; NTIPAliasClassID["flawlesstopaz"] = 565; +NTIPAliasClassID["gpy"] = 566; NTIPAliasClassID["perfecttopaz"] = 566; +NTIPAliasClassID["gcb"] = 567; NTIPAliasClassID["chippedsapphire"] = 567; +NTIPAliasClassID["gfb"] = 568; NTIPAliasClassID["flawedsapphire"] = 568; +NTIPAliasClassID["gsb"] = 569; NTIPAliasClassID["sapphire"] = 569; +NTIPAliasClassID["glb"] = 570; NTIPAliasClassID["flawlesssapphire"] = 570; +NTIPAliasClassID["gpb"] = 571; NTIPAliasClassID["perfectsapphire"] = 571; +NTIPAliasClassID["gcg"] = 572; NTIPAliasClassID["chippedemerald"] = 572; +NTIPAliasClassID["gfg"] = 573; NTIPAliasClassID["flawedemerald"] = 573; +NTIPAliasClassID["gsg"] = 574; NTIPAliasClassID["emerald"] = 574; +NTIPAliasClassID["glg"] = 575; NTIPAliasClassID["flawlessemerald"] = 575; +NTIPAliasClassID["gpg"] = 576; NTIPAliasClassID["perfectemerald"] = 576; +NTIPAliasClassID["gcr"] = 577; NTIPAliasClassID["chippedruby"] = 577; +NTIPAliasClassID["gfr"] = 578; NTIPAliasClassID["flawedruby"] = 578; +NTIPAliasClassID["gsr"] = 579; NTIPAliasClassID["ruby"] = 579; +NTIPAliasClassID["glr"] = 580; NTIPAliasClassID["flawlessruby"] = 580; +NTIPAliasClassID["gpr"] = 581; NTIPAliasClassID["perfectruby"] = 581; +NTIPAliasClassID["gcw"] = 582; NTIPAliasClassID["chippeddiamond"] = 582; +NTIPAliasClassID["gfw"] = 583; NTIPAliasClassID["flaweddiamond"] = 583; +NTIPAliasClassID["gsw"] = 584; NTIPAliasClassID["diamond"] = 584; +NTIPAliasClassID["glw"] = 585; NTIPAliasClassID["flawlessdiamond"] = 585; +NTIPAliasClassID["gpw"] = 586; NTIPAliasClassID["perfectdiamond"] = 586; +NTIPAliasClassID["hp1"] = 587; NTIPAliasClassID["minorhealingpotion"] = 587; +NTIPAliasClassID["hp2"] = 588; NTIPAliasClassID["lighthealingpotion"] = 588; +NTIPAliasClassID["hp3"] = 589; NTIPAliasClassID["healingpotion"] = 589; +NTIPAliasClassID["hp4"] = 590; NTIPAliasClassID["greaterhealingpotion"] = 590; +NTIPAliasClassID["hp5"] = 591; NTIPAliasClassID["superhealingpotion"] = 591; +NTIPAliasClassID["mp1"] = 592; NTIPAliasClassID["minormanapotion"] = 592; +NTIPAliasClassID["mp2"] = 593; NTIPAliasClassID["lightmanapotion"] = 593; +NTIPAliasClassID["mp3"] = 594; NTIPAliasClassID["manapotion"] = 594; +NTIPAliasClassID["mp4"] = 595; NTIPAliasClassID["greatermanapotion"] = 595; +NTIPAliasClassID["mp5"] = 596; NTIPAliasClassID["supermanapotion"] = 596; +NTIPAliasClassID["skc"] = 597; NTIPAliasClassID["chippedskull"] = 597; +NTIPAliasClassID["skf"] = 598; NTIPAliasClassID["flawedskull"] = 598; +NTIPAliasClassID["sku"] = 599; NTIPAliasClassID["skull"] = 599; +NTIPAliasClassID["skl"] = 600; NTIPAliasClassID["flawlessskull"] = 600; +NTIPAliasClassID["skz"] = 601; NTIPAliasClassID["perfectskull"] = 601; +NTIPAliasClassID["hrb"] = 602; NTIPAliasClassID["herb"] = 602; +NTIPAliasClassID["cm1"] = 603; NTIPAliasClassID["smallcharm"] = 603; +NTIPAliasClassID["cm2"] = 604; NTIPAliasClassID["largecharm"] = 604; +NTIPAliasClassID["cm3"] = 605; NTIPAliasClassID["grandcharm"] = 605; +NTIPAliasClassID["rps"] = 606; +NTIPAliasClassID["rpl"] = 607; +NTIPAliasClassID["bps"] = 608; +NTIPAliasClassID["bpl"] = 609; +NTIPAliasClassID["r01"] = 610; NTIPAliasClassID["elrune"] = 610; +NTIPAliasClassID["r02"] = 611; NTIPAliasClassID["eldrune"] = 611; +NTIPAliasClassID["r03"] = 612; NTIPAliasClassID["tirrune"] = 612; +NTIPAliasClassID["r04"] = 613; NTIPAliasClassID["nefrune"] = 613; +NTIPAliasClassID["r05"] = 614; NTIPAliasClassID["ethrune"] = 614; +NTIPAliasClassID["r06"] = 615; NTIPAliasClassID["ithrune"] = 615; +NTIPAliasClassID["r07"] = 616; NTIPAliasClassID["talrune"] = 616; +NTIPAliasClassID["r08"] = 617; NTIPAliasClassID["ralrune"] = 617; +NTIPAliasClassID["r09"] = 618; NTIPAliasClassID["ortrune"] = 618; +NTIPAliasClassID["r10"] = 619; NTIPAliasClassID["thulrune"] = 619; +NTIPAliasClassID["r11"] = 620; NTIPAliasClassID["amnrune"] = 620; +NTIPAliasClassID["r12"] = 621; NTIPAliasClassID["solrune"] = 621; +NTIPAliasClassID["r13"] = 622; NTIPAliasClassID["shaelrune"] = 622; +NTIPAliasClassID["r14"] = 623; NTIPAliasClassID["dolrune"] = 623; +NTIPAliasClassID["r15"] = 624; NTIPAliasClassID["helrune"] = 624; +NTIPAliasClassID["r16"] = 625; NTIPAliasClassID["iorune"] = 625; +NTIPAliasClassID["r17"] = 626; NTIPAliasClassID["lumrune"] = 626; +NTIPAliasClassID["r18"] = 627; NTIPAliasClassID["korune"] = 627; +NTIPAliasClassID["r19"] = 628; NTIPAliasClassID["falrune"] = 628; +NTIPAliasClassID["r20"] = 629; NTIPAliasClassID["lemrune"] = 629; +NTIPAliasClassID["r21"] = 630; NTIPAliasClassID["pulrune"] = 630; +NTIPAliasClassID["r22"] = 631; NTIPAliasClassID["umrune"] = 631; +NTIPAliasClassID["r23"] = 632; NTIPAliasClassID["malrune"] = 632; +NTIPAliasClassID["r24"] = 633; NTIPAliasClassID["istrune"] = 633; +NTIPAliasClassID["r25"] = 634; NTIPAliasClassID["gulrune"] = 634; +NTIPAliasClassID["r26"] = 635; NTIPAliasClassID["vexrune"] = 635; +NTIPAliasClassID["r27"] = 636; NTIPAliasClassID["ohmrune"] = 636; +NTIPAliasClassID["r28"] = 637; NTIPAliasClassID["lorune"] = 637; +NTIPAliasClassID["r29"] = 638; NTIPAliasClassID["surrune"] = 638; +NTIPAliasClassID["r30"] = 639; NTIPAliasClassID["berrune"] = 639; +NTIPAliasClassID["r31"] = 640; NTIPAliasClassID["jahrune"] = 640; +NTIPAliasClassID["r32"] = 641; NTIPAliasClassID["chamrune"] = 641; +NTIPAliasClassID["r33"] = 642; NTIPAliasClassID["zodrune"] = 642; +NTIPAliasClassID["jew"] = 643; NTIPAliasClassID["jewel"] = 643; +NTIPAliasClassID["ice"] = 644; NTIPAliasClassID["malah'spotion"] = 644; +NTIPAliasClassID["0sc"] = 645; NTIPAliasClassID["scrollofknowledge"] = 645; +NTIPAliasClassID["tr2"] = 646; NTIPAliasClassID["scrollofresistance"] = 646; +NTIPAliasClassID["pk1"] = 647; NTIPAliasClassID["keyofterror"] = 647; +NTIPAliasClassID["pk2"] = 648; NTIPAliasClassID["keyofhate"] = 648; +NTIPAliasClassID["pk3"] = 649; NTIPAliasClassID["keyofdestruction"] = 649; +NTIPAliasClassID["dhn"] = 650; NTIPAliasClassID["diablo'shorn"] = 650; +NTIPAliasClassID["bey"] = 651; NTIPAliasClassID["baal'seye"] = 651; +NTIPAliasClassID["mbr"] = 652; NTIPAliasClassID["mephisto'sbrain"] = 652; +NTIPAliasClassID["toa"] = 653; NTIPAliasClassID["tokenofabsolution"] = 653; +NTIPAliasClassID["tes"] = 654; NTIPAliasClassID["twistedessenceofsuffering"] = 654; +NTIPAliasClassID["ceh"] = 655; NTIPAliasClassID["chargedessenceofhatred"] = 655; +NTIPAliasClassID["bet"] = 656; NTIPAliasClassID["burningessenceofterror"] = 656; +NTIPAliasClassID["fed"] = 657; NTIPAliasClassID["festeringessenceofdestruction"] = 657; +NTIPAliasClassID["std"] = 658; NTIPAliasClassID["standardofheroes"] = 658; + +/** @global */ +const NTIPAliasClass = {}; +NTIPAliasClass["normal"] = 0; +NTIPAliasClass["exceptional"] = 1; +NTIPAliasClass["elite"] = 2; + +/** @global */ +const NTIPAliasQuality = {}; +NTIPAliasQuality["lowquality"] = 1; +NTIPAliasQuality["normal"] = 2; +NTIPAliasQuality["superior"] = 3; +NTIPAliasQuality["magic"] = 4; +NTIPAliasQuality["set"] = 5; +NTIPAliasQuality["rare"] = 6; +NTIPAliasQuality["unique"] = 7; +NTIPAliasQuality["crafted"] = 8; + +/** @global */ +const NTIPAliasFlag = {}; +NTIPAliasFlag["identified"] = 0x10; +NTIPAliasFlag["eth"] = 0x400000; NTIPAliasFlag["ethereal"] = 0x400000; +NTIPAliasFlag["runeword"] = 0x4000000; + +// rare item colors +/** @global */ +const NTIPAliasColor = {}; +NTIPAliasColor["black"] = 3; +NTIPAliasColor["white"] = 20; +NTIPAliasColor["orange"] = 19; +NTIPAliasColor["lightyellow"] = 13; +NTIPAliasColor["lightred"] = 7; +NTIPAliasColor["lightgold"] = 15; +NTIPAliasColor["lightblue"] = 4; +NTIPAliasColor["lightpurple"] = 17; +NTIPAliasColor["crystalblue"] = 6; +NTIPAliasColor["crystalred"] = 9; +NTIPAliasColor["crystalgreen"] = 12; +NTIPAliasColor["darkyellow"] = 14; +NTIPAliasColor["darkred"] = 8; +NTIPAliasColor["darkgold"] = 16; +NTIPAliasColor["darkgreen"] = 11; +NTIPAliasColor["darkblue"] = 5; + +/** @global */ +const NTIPAliasStat = {}; +NTIPAliasStat["strength"] = 0; +NTIPAliasStat["energy"] = 1; +NTIPAliasStat["dexterity"] = 2; +NTIPAliasStat["vitality"] = 3; +NTIPAliasStat["statpts"] = 4; +NTIPAliasStat["newskills"] = 5; +NTIPAliasStat["hitpoints"] = 6; +NTIPAliasStat["maxhp"] = 7; +NTIPAliasStat["mana"] = 8; +NTIPAliasStat["maxmana"] = 9; +NTIPAliasStat["stamina"] = 10; +NTIPAliasStat["maxstamina"] = 11; +NTIPAliasStat["level"] = 12; +NTIPAliasStat["experience"] = 13; +NTIPAliasStat["gold"] = 14; +NTIPAliasStat["goldbank"] = 15; +NTIPAliasStat["itemarmorpercent"] = [16, 0]; NTIPAliasStat["enhanceddefense"] = [16, 0]; +NTIPAliasStat["itemmaxdamagepercent"] = [17, 0]; +NTIPAliasStat["itemmindamagepercent"] = [18, 0]; NTIPAliasStat["enhanceddamage"] = [18, 0]; +NTIPAliasStat["tohit"] = 19; +NTIPAliasStat["toblock"] = 20; +NTIPAliasStat["plusmindamage"] = [21, 1]; +NTIPAliasStat["mindamage"] = 21; +NTIPAliasStat["plusmaxdamage"] = [22, 1]; +NTIPAliasStat["maxdamage"] = 22; +NTIPAliasStat["secondarymindamage"] = 23; +NTIPAliasStat["secondarymaxdamage"] = 24; +NTIPAliasStat["damagepercent"] = 25; +NTIPAliasStat["manarecovery"] = 26; +NTIPAliasStat["manarecoverybonus"] = 27; +NTIPAliasStat["staminarecoverybonus"] = 28; +NTIPAliasStat["lastexp"] = 29; +NTIPAliasStat["nextexp"] = 30; + +NTIPAliasStat["armorclass"] = 31; NTIPAliasStat["defense"] = 31; +NTIPAliasStat["plusdefense"] = [31, 0]; + +NTIPAliasStat["armorclassvsmissile"] = 32; +NTIPAliasStat["armorclassvshth"] = 33; +NTIPAliasStat["normaldamagereduction"] = 34; +NTIPAliasStat["magicdamagereduction"] = 35; +NTIPAliasStat["damageresist"] = 36; +NTIPAliasStat["magicresist"] = 37; +NTIPAliasStat["maxmagicresist"] = 38; +NTIPAliasStat["fireresist"] = 39; +NTIPAliasStat["maxfireresist"] = 40; +NTIPAliasStat["lightresist"] = 41; +NTIPAliasStat["maxlightresist"] = 42; +NTIPAliasStat["coldresist"] = 43; +NTIPAliasStat["maxcoldresist"] = 44; +NTIPAliasStat["poisonresist"] = 45; +NTIPAliasStat["maxpoisonresist"] = 46; +NTIPAliasStat["damageaura"] = 47; +NTIPAliasStat["firemindam"] = 48; +NTIPAliasStat["firemaxdam"] = 49; +NTIPAliasStat["lightmindam"] = 50; +NTIPAliasStat["lightmaxdam"] = 51; +NTIPAliasStat["magicmindam"] = 52; +NTIPAliasStat["magicmaxdam"] = 53; +NTIPAliasStat["coldmindam"] = 54; +NTIPAliasStat["coldmaxdam"] = 55; +NTIPAliasStat["coldlength"] = 56; +NTIPAliasStat["poisondamage"] = [57, 1]; +NTIPAliasStat["poisonmindam"] = 57; +NTIPAliasStat["poisonmaxdam"] = 58; +NTIPAliasStat["poisonlength"] = 59; +NTIPAliasStat["lifedrainmindam"] = 60; NTIPAliasStat["lifeleech"] = 60; +NTIPAliasStat["lifedrainmaxdam"] = 61; +NTIPAliasStat["manadrainmindam"] = 62; NTIPAliasStat["manaleech"] = 62; +NTIPAliasStat["manadrainmaxdam"] = 63; +NTIPAliasStat["stamdrainmindam"] = 64; +NTIPAliasStat["stamdrainmaxdam"] = 65; +NTIPAliasStat["stunlength"] = 66; +NTIPAliasStat["velocitypercent"] = 67; +NTIPAliasStat["attackrate"] = 68; +NTIPAliasStat["otheranimrate"] = 69; +NTIPAliasStat["quantity"] = 70; +NTIPAliasStat["value"] = 71; +NTIPAliasStat["durability"] = 72; +NTIPAliasStat["maxdurability"] = 73; +NTIPAliasStat["hpregen"] = 74; +NTIPAliasStat["itemmaxdurabilitypercent"] = 75; +NTIPAliasStat["itemmaxhppercent"] = 76; +NTIPAliasStat["itemmaxmanapercent"] = 77; +NTIPAliasStat["itemattackertakesdamage"] = 78; +NTIPAliasStat["itemgoldbonus"] = 79; +NTIPAliasStat["itemmagicbonus"] = 80; +NTIPAliasStat["itemknockback"] = 81; +NTIPAliasStat["itemtimeduration"] = 82; + +NTIPAliasStat["itemaddclassskills"] = 83; +NTIPAliasStat["itemaddamazonskills"] = [83, 0]; NTIPAliasStat["amazonskills"] = [83, 0]; +NTIPAliasStat["itemaddsorceressskills"] = [83, 1]; NTIPAliasStat["sorceressskills"] = [83, 1]; +NTIPAliasStat["itemaddnecromancerskills"] = [83, 2]; NTIPAliasStat["necromancerskills"] = [83, 2]; +NTIPAliasStat["itemaddpaladinskills"] = [83, 3]; NTIPAliasStat["paladinskills"] = [83, 3]; +NTIPAliasStat["itemaddbarbarianskills"] = [83, 4]; NTIPAliasStat["barbarianskills"] = [83, 4]; +NTIPAliasStat["itemadddruidskills"] = [83, 5]; NTIPAliasStat["druidskills"] = [83, 5]; +NTIPAliasStat["itemaddassassinskills"] = [83, 6]; NTIPAliasStat["assassinskills"] = [83, 6]; + +NTIPAliasStat["unsentparam1"] = 84; +NTIPAliasStat["itemaddexperience"] = 85; +NTIPAliasStat["itemhealafterkill"] = 86; +NTIPAliasStat["itemreducedprices"] = 87; +NTIPAliasStat["itemdoubleherbduration"] = 88; +NTIPAliasStat["itemlightradius"] = 89; +NTIPAliasStat["itemlightcolor"] = 90; +NTIPAliasStat["itemreqpercent"] = 91; +NTIPAliasStat["itemlevelreq"] = 92; +NTIPAliasStat["itemfasterattackrate"] = 93; NTIPAliasStat["ias"] = 93; +NTIPAliasStat["itemlevelreqpct"] = 94; +NTIPAliasStat["lastblockframe"] = 95; +NTIPAliasStat["itemfastermovevelocity"] = 96; NTIPAliasStat["frw"] = 96; + +// oskill +NTIPAliasStat["itemnonclassskill"] = 97; +// Amazon +NTIPAliasStat["plusskillcriticalstrike"] = [97, 9]; +NTIPAliasStat["plusskillguidedarrow"] = [97, 22]; +NTIPAliasStat["plusskillvalkyrie"] = [97, sdk.skills.Valkyrie]; +// Sorceress +NTIPAliasStat["plusskillwarmth"] = [97, sdk.skills.Warmth]; +NTIPAliasStat["plusskillinferno"] = [97, sdk.skills.Inferno]; +NTIPAliasStat["plusskillfireball"] = [97, sdk.skills.FireBall]; +NTIPAliasStat["plusskillfirewall"] = [97, sdk.skills.FireWall]; +NTIPAliasStat["plusskillteleport"] = [97, sdk.skills.Teleport]; +NTIPAliasStat["plusskillmeteor"] = [97, sdk.skills.Meteor]; +NTIPAliasStat["plusskillfiremastery"] = [97, sdk.skills.FireMastery]; +NTIPAliasStat["plusskillhydra"] = [97, sdk.skills.Hydra]; +// Barbarian +NTIPAliasStat["plusskillbattlecry"] = [97, 146]; +NTIPAliasStat["plusskillbattleorders"] = [97, 149]; +NTIPAliasStat["plusskillbattlecommand"] = [97, 155]; +NTIPAliasStat["plusskillwhirlwind"] = [97, sdk.skills.Whirlwind]; +NTIPAliasStat["plusskillberserk"] = [97, sdk.skills.Berserk]; +// Druid +NTIPAliasStat["plusskillwerewolf"] = [97, 223]; +NTIPAliasStat["plusskillwerebear"] = [97, sdk.skills.Werebear]; +NTIPAliasStat["plusskillshapeshifting"] = [97, 224]; NTIPAliasStat["plusskilllycanthropy"] = [97, 224]; +NTIPAliasStat["plusskillsummonspiritwolf"] = [97, 227]; +NTIPAliasStat["plusskillferalrage"] = [97, 232]; +NTIPAliasStat["plusskillarticblast"] = [97, sdk.skills.ArcticBlast]; +// paladin +NTIPAliasStat["plusskillzeal"] = [97, sdk.skills.Zeal]; +NTIPAliasStat["plusskillvengeance"] = [97, sdk.skills.Vengeance]; + +NTIPAliasStat["state"] = 98; +NTIPAliasStat["itemfastergethitrate"] = 99; NTIPAliasStat["fhr"] = 99; +NTIPAliasStat["monsterplayercount"] = 100; +NTIPAliasStat["skillpoisonoverridelength"] = 101; +NTIPAliasStat["itemfasterblockrate"] = 102; NTIPAliasStat["fbr"] = 102; +NTIPAliasStat["skillbypassundead"] = 103; +NTIPAliasStat["skillbypassdemons"] = 104; +NTIPAliasStat["itemfastercastrate"] = 105; NTIPAliasStat["fcr"] = 105; +NTIPAliasStat["skillbypassbeasts"] = 106; + +NTIPAliasStat["itemsingleskill"] = 107; +// Amazon skills +NTIPAliasStat["skillmagicarrow"] = [107, 6]; +NTIPAliasStat["skillfirearrow"] = [107, 7]; +NTIPAliasStat["skillinnersight"] = [107, 8]; +NTIPAliasStat["skillcriticalstrike"] = [107, 9]; +NTIPAliasStat["skilljab"] = [107, 10]; +NTIPAliasStat["skillcoldarrow"] = [107, 11]; +NTIPAliasStat["skillmultipleshot"] = [107, 12]; +NTIPAliasStat["skilldodge"] = [107, 13]; +NTIPAliasStat["skillpowerstrike"] = [107, 14]; +NTIPAliasStat["skillpoisonjavelin"] = [107, 15]; +NTIPAliasStat["skillexplodingarrow"] = [107, 16]; +NTIPAliasStat["skillslowmissiles"] = [107, 17]; +NTIPAliasStat["skillavoid"] = [107, 18]; +NTIPAliasStat["skillimpale"] = [107, 19]; +NTIPAliasStat["skilllightningbolt"] = [107, 20]; +NTIPAliasStat["skillicearrow"] = [107, 21]; +NTIPAliasStat["skillguidedarrow"] = [107, 22]; +NTIPAliasStat["skillpenetrate"] = [107, 23]; +NTIPAliasStat["skillchargedstrike"] = [107, 24]; +NTIPAliasStat["skillplaguejavelin"] = [107, 25]; +NTIPAliasStat["skillstrafe"] = [107, 26]; +NTIPAliasStat["skillimmolationarrow"] = [107, 27]; +NTIPAliasStat["skilldecoy"] = [107, 28]; +NTIPAliasStat["skillevade"] = [107, 29]; +NTIPAliasStat["skillfend"] = [107, 30]; +NTIPAliasStat["skillfreezingarrow"] = [107, 31]; +NTIPAliasStat["skillvalkyrie"] = [107, 32]; +NTIPAliasStat["skillpierce"] = [107, 33]; +NTIPAliasStat["skilllightningstrike"] = [107, 34]; +NTIPAliasStat["skilllightningfury"] = [107, 35]; +// Sorceress skills +NTIPAliasStat["skillfirebolt"] = [107, 36]; +NTIPAliasStat["skillwarmth"] = [107, 37]; +NTIPAliasStat["skillchargedbolt"] = [107, 38]; +NTIPAliasStat["skillicebolt"] = [107, 39]; +NTIPAliasStat["skillfrozenarmor"] = [107, 40]; +NTIPAliasStat["skillinferno"] = [107, 41]; +NTIPAliasStat["skillstaticfield"] = [107, 42]; +NTIPAliasStat["skilltelekinesis"] = [107, 43]; +NTIPAliasStat["skillfrostnova"] = [107, 44]; +NTIPAliasStat["skilliceblast"] = [107, 45]; +NTIPAliasStat["skillblaze"] = [107, 46]; +NTIPAliasStat["skillfireball"] = [107, 47]; +NTIPAliasStat["skillnova"] = [107, 48]; +NTIPAliasStat["skilllightning"] = [107, 49]; +NTIPAliasStat["skillshiverarmor"] = [107, 50]; +NTIPAliasStat["skillfirewall"] = [107, 51]; +NTIPAliasStat["skillenchant"] = [107, 52]; +NTIPAliasStat["skillchainlightning"] = [107, 53]; +NTIPAliasStat["skillteleport"] = [107, 54]; +NTIPAliasStat["skillglacialspike"] = [107, 55]; +NTIPAliasStat["skillmeteor"] = [107, 56]; +NTIPAliasStat["skillthunderstorm"] = [107, 57]; +NTIPAliasStat["skillenergyshield"] = [107, 58]; +NTIPAliasStat["skillblizzard"] = [107, 59]; +NTIPAliasStat["skillchillingarmor"] = [107, 60]; +NTIPAliasStat["skillfiremastery"] = [107, 61]; +NTIPAliasStat["skillhydra"] = [107, 62]; +NTIPAliasStat["skilllightningmastery"] = [107, 63]; +NTIPAliasStat["skillfrozenorb"] = [107, 64]; +NTIPAliasStat["skillcoldmastery"] = [107, 65]; +// Necromancer skills +NTIPAliasStat["skillamplifydamage"] = [107, 66]; +NTIPAliasStat["skillteeth"] = [107, 67]; +NTIPAliasStat["skillbonearmor"] = [107, 68]; +NTIPAliasStat["skillskeletonmastery"] = [107, 69]; +NTIPAliasStat["skillraiseskeleton"] = [107, 70]; +NTIPAliasStat["skilldimvision"] = [107, 71]; +NTIPAliasStat["skillweaken"] = [107, 72]; +NTIPAliasStat["skillpoisondagger"] = [107, 73]; +NTIPAliasStat["skillcorpseexplosion"] = [107, 74]; +NTIPAliasStat["skillclaygolem"] = [107, 75]; +NTIPAliasStat["skillironmaiden"] = [107, 76]; +NTIPAliasStat["skillterror"] = [107, 77]; +NTIPAliasStat["skillbonewall"] = [107, 78]; +NTIPAliasStat["skillgolemmastery"] = [107, 79]; +NTIPAliasStat["skillskeletalmage"] = [107, 80]; +NTIPAliasStat["skillconfuse"] = [107, 81]; +NTIPAliasStat["skilllifetap"] = [107, 82]; +NTIPAliasStat["skillpoisonexplosion"] = [107, 83]; +NTIPAliasStat["skillbonespear"] = [107, 84]; +NTIPAliasStat["skillbloodgolem"] = [107, 85]; +NTIPAliasStat["skillattract"] = [107, 86]; +NTIPAliasStat["skilldecrepify"] = [107, 87]; +NTIPAliasStat["skillboneprison"] = [107, 88]; +NTIPAliasStat["skillsummonresist"] = [107, 89]; +NTIPAliasStat["skillirongolem"] = [107, 90]; +NTIPAliasStat["skilllowerresist"] = [107, 91]; +NTIPAliasStat["skillpoisonnova"] = [107, 92]; +NTIPAliasStat["skillbonespirit"] = [107, 93]; +NTIPAliasStat["skillfiregolem"] = [107, 94]; +NTIPAliasStat["skillrevive"] = [107, 95]; +// Paladin skills +NTIPAliasStat["skillsacrifice"] = [107, 96]; +NTIPAliasStat["skillsmite"] = [107, 97]; +NTIPAliasStat["skillmight"] = [107, 98]; +NTIPAliasStat["skillprayer"] = [107, 99]; +NTIPAliasStat["skillresistfire"] = [107, 100]; +NTIPAliasStat["skillholybolt"] = [107, 101]; +NTIPAliasStat["skillholyfire"] = [107, 102]; +NTIPAliasStat["skillthorns"] = [107, 103]; +NTIPAliasStat["skilldefiance"] = [107, 104]; +NTIPAliasStat["skillresistcold"] = [107, 105]; +NTIPAliasStat["skillzeal"] = [107, 106]; +NTIPAliasStat["skillcharge"] = [107, 107]; +NTIPAliasStat["skillblessedaim"] = [107, 108]; +NTIPAliasStat["skillcleansing"] = [107, 109]; +NTIPAliasStat["skillresistlightning"] = [107, 110]; +NTIPAliasStat["skillvengeance"] = [107, 111]; +NTIPAliasStat["skillblessedhammer"] = [107, 112]; +NTIPAliasStat["skillconcentration"] = [107, 113]; +NTIPAliasStat["skillholyfreeze"] = [107, 114]; +NTIPAliasStat["skillvigor"] = [107, 115]; +NTIPAliasStat["skillconversion"] = [107, 116]; +NTIPAliasStat["skillholyshield"] = [107, 117]; +NTIPAliasStat["skillholyshock"] = [107, 118]; +NTIPAliasStat["skillsanctuary"] = [107, 119]; +NTIPAliasStat["skillmeditation"] = [107, 120]; +NTIPAliasStat["skillfistoftheheavens"] = [107, 121]; +NTIPAliasStat["skillfanaticism"] = [107, 122]; +NTIPAliasStat["skillconviction"] = [107, 123]; +NTIPAliasStat["skillredemption"] = [107, 124]; +NTIPAliasStat["skillsalvation"] = [107, 125]; +// Barbarian skills +NTIPAliasStat["skillbash"] = [107, 126]; +NTIPAliasStat["skillswordmastery"] = [107, 127]; +NTIPAliasStat["skillaxemastery"] = [107, 128]; +NTIPAliasStat["skillmacemastery"] = [107, 129]; +NTIPAliasStat["skillhowl"] = [107, 130]; +NTIPAliasStat["skillfindpotion"] = [107, 131]; +NTIPAliasStat["skillleap"] = [107, 132]; +NTIPAliasStat["skilldoubleswing"] = [107, 133]; +NTIPAliasStat["skillpolearmmastery"] = [107, 134]; +NTIPAliasStat["skillthrowingmastery"] = [107, 135]; +NTIPAliasStat["skillspearmastery"] = [107, 136]; +NTIPAliasStat["skilltaunt"] = [107, 137]; +NTIPAliasStat["skillshout"] = [107, 138]; +NTIPAliasStat["skillstun"] = [107, 139]; +NTIPAliasStat["skilldoublethrow"] = [107, 140]; +NTIPAliasStat["skillincreasedstamina"] = [107, 141]; +NTIPAliasStat["skillfinditem"] = [107, 142]; +NTIPAliasStat["skillleapattack"] = [107, 143]; +NTIPAliasStat["skillconcentrate"] = [107, 144]; +NTIPAliasStat["skillironskin"] = [107, 145]; +NTIPAliasStat["skillbattlecry"] = [107, 146]; +NTIPAliasStat["skillfrenzy"] = [107, 147]; +NTIPAliasStat["skillincreasedspeed"] = [107, 148]; +NTIPAliasStat["skillbattleorders"] = [107, 149]; +NTIPAliasStat["skillgrimward"] = [107, 150]; +NTIPAliasStat["skillwhirlwind"] = [107, 151]; +NTIPAliasStat["skillberserk"] = [107, 152]; +NTIPAliasStat["skillnaturalresistance"] = [107, 153]; +NTIPAliasStat["skillwarcry"] = [107, 154]; +NTIPAliasStat["skillbattlecommand"] = [107, 155]; +// Druid skills +NTIPAliasStat["skillraven"] = [107, 221]; +NTIPAliasStat["skillpoisoncreeper"] = [107, 222]; +NTIPAliasStat["skillwerewolf"] = [107, 223]; +NTIPAliasStat["skilllycanthropy"] = [107, 224]; +NTIPAliasStat["skillfirestorm"] = [107, 225]; +NTIPAliasStat["skilloaksage"] = [107, 226]; +NTIPAliasStat["skillsummonspiritwolf"] = [107, 227]; +NTIPAliasStat["skillwerebear"] = [107, 228]; +NTIPAliasStat["skillmoltenboulder"] = [107, 229]; +NTIPAliasStat["skillarcticblast"] = [107, 230]; +NTIPAliasStat["skillcarrionvine"] = [107, 231]; +NTIPAliasStat["skillferalrage"] = [107, 232]; +NTIPAliasStat["skillmaul"] = [107, 233]; +NTIPAliasStat["skillfissure"] = [107, 234]; +NTIPAliasStat["skillcyclonearmor"] = [107, 235]; +NTIPAliasStat["skillheartofwolverine"] = [107, 236]; +NTIPAliasStat["skillsummondirewolf"] = [107, 237]; +NTIPAliasStat["skillrabies"] = [107, 238]; +NTIPAliasStat["skillfireclaws"] = [107, 239]; +NTIPAliasStat["skilltwister"] = [107, 240]; +NTIPAliasStat["skillsolarcreeper"] = [107, 241]; +NTIPAliasStat["skillhunger"] = [107, 242]; +NTIPAliasStat["skillshockwave"] = [107, 243]; +NTIPAliasStat["skillvolcano"] = [107, 244]; +NTIPAliasStat["skilltornado"] = [107, 245]; +NTIPAliasStat["skillspiritofbarbs"] = [107, 246]; +NTIPAliasStat["skillsummongrizzly"] = [107, 247]; +NTIPAliasStat["skillfury"] = [107, 248]; +NTIPAliasStat["skillarmageddon"] = [107, 249]; +NTIPAliasStat["skillhurricane"] = [107, 250]; +// Assassin skills +NTIPAliasStat["skillfireblast"] = [107, 251]; +NTIPAliasStat["skillclawmastery"] = [107, 252]; +NTIPAliasStat["skillpsychichammer"] = [107, 253]; +NTIPAliasStat["skilltigerstrike"] = [107, 254]; +NTIPAliasStat["skilldragontalon"] = [107, 255]; +NTIPAliasStat["skillshockweb"] = [107, 256]; +NTIPAliasStat["skillbladesentinel"] = [107, 257]; +NTIPAliasStat["skillburstofspeed"] = [107, 258]; +NTIPAliasStat["skillfistsoffire"] = [107, 259]; +NTIPAliasStat["skilldragonclaw"] = [107, 260]; +NTIPAliasStat["skillchargedboltsentry"] = [107, 261]; +NTIPAliasStat["skillwakeoffire"] = [107, 262]; +NTIPAliasStat["skillweaponblock"] = [107, 263]; +NTIPAliasStat["skillcloakofshadows"] = [107, 264]; +NTIPAliasStat["skillcobrastrike"] = [107, 265]; +NTIPAliasStat["skillbladefury"] = [107, 266]; +NTIPAliasStat["skillfade"] = [107, 267]; +NTIPAliasStat["skillshadowwarrior"] = [107, 268]; +NTIPAliasStat["skillclawsofthunder"] = [107, 269]; +NTIPAliasStat["skilldragontail"] = [107, 270]; +NTIPAliasStat["skilllightningsentry"] = [107, 271]; +NTIPAliasStat["skillwakeofinferno"] = [107, 272]; +NTIPAliasStat["skillmindblast"] = [107, 273]; +NTIPAliasStat["skillbladesofice"] = [107, 274]; +NTIPAliasStat["skilldragonflight"] = [107, 275]; +NTIPAliasStat["skilldeathsentry"] = [107, 276]; +NTIPAliasStat["skillbladeshield"] = [107, 277]; +NTIPAliasStat["skillvenom"] = [107, 278]; +NTIPAliasStat["skillshadowmaster"] = [107, 279]; +NTIPAliasStat["skillphoenixstrike"] = [107, 280]; + +NTIPAliasStat["itemrestinpeace"] = 108; +NTIPAliasStat["curseresistance"] = 109; +NTIPAliasStat["itempoisonlengthresist"] = 110; +NTIPAliasStat["itemnormaldamage"] = 111; +NTIPAliasStat["itemhowl"] = 112; +NTIPAliasStat["itemstupidity"] = 113; +NTIPAliasStat["itemdamagetomana"] = 114; +NTIPAliasStat["itemignoretargetac"] = 115; +NTIPAliasStat["itemfractionaltargetac"] = 116; +NTIPAliasStat["itempreventheal"] = 117; +NTIPAliasStat["itemhalffreezeduration"] = 118; +NTIPAliasStat["itemtohitpercent"] = 119; +NTIPAliasStat["itemdamagetargetac"] = 120; +NTIPAliasStat["itemdemondamagepercent"] = 121; +NTIPAliasStat["itemundeaddamagepercent"] = 122; +NTIPAliasStat["itemdemontohit"] = 123; +NTIPAliasStat["itemundeadtohit"] = 124; +NTIPAliasStat["itemthrowable"] = 125; +NTIPAliasStat["itemelemskill"] = 126; +NTIPAliasStat["itemallskills"] = 127; +NTIPAliasStat["itemattackertakeslightdamage"] = 128; +NTIPAliasStat["ironmaidenlevel"] = 129; +NTIPAliasStat["lifetaplevel"] = 130; +NTIPAliasStat["thornspercent"] = 131; +NTIPAliasStat["bonearmor"] = 132; +NTIPAliasStat["bonearmormax"] = 133; +NTIPAliasStat["itemfreeze"] = 134; +NTIPAliasStat["itemopenwounds"] = 135; +NTIPAliasStat["itemcrushingblow"] = 136; +NTIPAliasStat["itemkickdamage"] = 137; +NTIPAliasStat["itemmanaafterkill"] = 138; +NTIPAliasStat["itemhealafterdemonkill"] = 139; +NTIPAliasStat["itemextrablood"] = 140; +NTIPAliasStat["itemdeadlystrike"] = 141; +NTIPAliasStat["itemabsorbfirepercent"] = 142; +NTIPAliasStat["itemabsorbfire"] = 143; +NTIPAliasStat["itemabsorblightpercent"] = 144; +NTIPAliasStat["itemabsorblight"] = 145; +NTIPAliasStat["itemabsorbmagicpercent"] = 146; +NTIPAliasStat["itemabsorbmagic"] = 147; +NTIPAliasStat["itemabsorbcoldpercent"] = 148; +NTIPAliasStat["itemabsorbcold"] = 149; +NTIPAliasStat["itemslow"] = 150; + +NTIPAliasStat["itemaura"] = 151; +NTIPAliasStat["mightaura"] = [151, 98]; +NTIPAliasStat["holyfireaura"] = [151, 102]; +NTIPAliasStat["thornsaura"] = [151, 103]; +NTIPAliasStat["defianceaura"] = [151, 104]; +NTIPAliasStat["concentrationaura"] = [151, 113]; +NTIPAliasStat["holyfreezeaura"] = [151, 114]; +NTIPAliasStat["vigoraura"] = [151, 115]; +NTIPAliasStat["holyshockaura"] = [151, 118]; +NTIPAliasStat["sanctuaryaura"] = [151, 119]; +NTIPAliasStat["meditationaura"] = [151, 120]; +NTIPAliasStat["fanaticismaura"] = [151, 122]; +NTIPAliasStat["convictionaura"] = [151, 123]; +NTIPAliasStat["redemptionaura"] = [151, 124]; + +NTIPAliasStat["itemindestructible"] = 152; +NTIPAliasStat["itemcannotbefrozen"] = 153; +NTIPAliasStat["itemstaminadrainpct"] = 154; +NTIPAliasStat["itemreanimate"] = 155; +NTIPAliasStat["itempierce"] = 156; +NTIPAliasStat["itemmagicarrow"] = 157; +NTIPAliasStat["itemexplosivearrow"] = 158; +NTIPAliasStat["itemthrowmindamage"] = 159; +NTIPAliasStat["itemthrowmaxdamage"] = 160; +NTIPAliasStat["itemskillhandofathena"] = 161; +NTIPAliasStat["itemskillstaminapercent"] = 162; +NTIPAliasStat["itemskillpassivestaminapercent"] = 163; +NTIPAliasStat["itemskillconcentration"] = 164; +NTIPAliasStat["itemskillenchant"] = 165; +NTIPAliasStat["itemskillpierce"] = 166; +NTIPAliasStat["itemskillconviction"] = 167; +NTIPAliasStat["itemskillchillingarmor"] = 168; +NTIPAliasStat["itemskillfrenzy"] = 169; +NTIPAliasStat["itemskilldecrepify"] = 170; +NTIPAliasStat["itemskillarmorpercent"] = 171; +NTIPAliasStat["alignment"] = 172; +NTIPAliasStat["target0"] = 173; +NTIPAliasStat["target1"] = 174; +NTIPAliasStat["goldlost"] = 175; +NTIPAliasStat["conversionlevel"] = 176; +NTIPAliasStat["conversionmaxhp"] = 177; +NTIPAliasStat["unitdooverlay"] = 178; +NTIPAliasStat["attackvsmontype"] = 179; +NTIPAliasStat["damagevsmontype"] = 180; +NTIPAliasStat["fade"] = 181; +NTIPAliasStat["armoroverridepercent"] = 182; +NTIPAliasStat["unused183"] = 183; +NTIPAliasStat["unused184"] = 184; +NTIPAliasStat["unused185"] = 185; +NTIPAliasStat["unused186"] = 186; +NTIPAliasStat["unused187"] = 187; + +NTIPAliasStat["itemaddskilltab"] = 188; +NTIPAliasStat["itemaddbowandcrossbowskilltab"] = [188, 0]; +NTIPAliasStat["bowandcrossbowskilltab"] = [188, 0]; +NTIPAliasStat["itemaddpassiveandmagicskilltab"] = [188, 1]; +NTIPAliasStat["passiveandmagicskilltab"] = [188, 1]; +NTIPAliasStat["itemaddjavelinandspearskilltab"] = [188, 2]; +NTIPAliasStat["javelinandspearskilltab"] = [188, 2]; +NTIPAliasStat["itemaddfireskilltab"] = [188, 8]; +NTIPAliasStat["fireskilltab"] = [188, 8]; +NTIPAliasStat["itemaddlightningskilltab"] = [188, 9]; +NTIPAliasStat["lightningskilltab"] = [188, 9]; +NTIPAliasStat["itemaddcoldskilltab"] = [188, 10]; +NTIPAliasStat["coldskilltab"] = [188, 10]; +NTIPAliasStat["itemaddcursesskilltab"] = [188, 16]; +NTIPAliasStat["cursesskilltab"] = [188, 16]; +NTIPAliasStat["itemaddpoisonandboneskilltab"] = [188, 17]; +NTIPAliasStat["poisonandboneskilltab"] = [188, 17]; +NTIPAliasStat["itemaddnecromancersummoningskilltab"] = [188, 18]; +NTIPAliasStat["necromancersummoningskilltab"] = [188, 18]; +NTIPAliasStat["itemaddpalicombatskilltab"] = [188, 24]; +NTIPAliasStat["palicombatskilltab"] = [188, 24]; +NTIPAliasStat["itemaddoffensiveaurasskilltab"] = [188, 25]; +NTIPAliasStat["offensiveaurasskilltab"] = [188, 25]; +NTIPAliasStat["itemadddefensiveaurasskilltab"] = [188, 26]; +NTIPAliasStat["defensiveaurasskilltab"] = [188, 26]; +NTIPAliasStat["itemaddbarbcombatskilltab"] = [188, 32]; +NTIPAliasStat["barbcombatskilltab"] = [188, 32]; +NTIPAliasStat["itemaddmasteriesskilltab"] = [188, 33]; +NTIPAliasStat["masteriesskilltab"] = [188, 33]; +NTIPAliasStat["itemaddwarcriesskilltab"] = [188, 34]; +NTIPAliasStat["warcriesskilltab"] = [188, 34]; +NTIPAliasStat["itemadddruidsummoningskilltab"] = [188, 40]; +NTIPAliasStat["druidsummoningskilltab"] = [188, 40]; +NTIPAliasStat["itemaddshapeshiftingskilltab"] = [188, 41]; +NTIPAliasStat["shapeshiftingskilltab"] = [188, 41]; +NTIPAliasStat["itemaddelementalskilltab"] = [188, 42]; +NTIPAliasStat["elementalskilltab"] = [188, 42]; +NTIPAliasStat["itemaddtrapsskilltab"] = [188, 48]; +NTIPAliasStat["trapsskilltab"] = [188, 48]; +NTIPAliasStat["itemaddshadowdisciplinesskilltab"] = [188, 49]; +NTIPAliasStat["shadowdisciplinesskilltab"] = [188, 49]; +NTIPAliasStat["itemaddmartialartsskilltab"] = [188, 50]; +NTIPAliasStat["martialartsskilltab"] = [188, 50]; + +NTIPAliasStat["unused189"] = 189; +NTIPAliasStat["unused190"] = 190; +NTIPAliasStat["unused191"] = 191; +NTIPAliasStat["unused192"] = 192; +NTIPAliasStat["unused193"] = 193; +NTIPAliasStat["itemnumsockets"] = 194; NTIPAliasStat["sockets"] = 194; +NTIPAliasStat["itemskillonattack"] = [195, 1]; +NTIPAliasStat["itemskillonattacklevel"] = [195, 2]; +NTIPAliasStat["itemskillonkill"] = [196, 1]; +NTIPAliasStat["itemskillonkilllevel"] = [196, 2]; +NTIPAliasStat["itemskillondeath"] = [197, 1]; +NTIPAliasStat["itemskillondeathlevel"] = [197, 2]; + +NTIPAliasStat["itemskillonhit"] = [198, 1]; +NTIPAliasStat["itemskillonhitlevel"] = [198, 2]; +NTIPAliasStat["amplifydamageonhit"] = [198, 4225]; + +NTIPAliasStat["itemskillonlevelup"] = [199, 1]; +NTIPAliasStat["itemskillonleveluplevel"] = [199, 2]; +NTIPAliasStat["unused200"] = 200; +NTIPAliasStat["itemskillongethit"] = [201, 1]; +NTIPAliasStat["itemskillongethitlevel"] = [201, 2]; +NTIPAliasStat["unused202"] = 202; +NTIPAliasStat["unused203"] = 203; + +NTIPAliasStat["itemchargedskill"] = [204, 1]; +NTIPAliasStat["itemchargedskilllevel"] = [204, 2]; +NTIPAliasStat["teleportcharges"] = [204, 3461]; + +NTIPAliasStat["unused204"] = 205; +NTIPAliasStat["unused205"] = 206; +NTIPAliasStat["unused206"] = 207; +NTIPAliasStat["unused207"] = 208; +NTIPAliasStat["unused208"] = 209; +NTIPAliasStat["unused209"] = 210; +NTIPAliasStat["unused210"] = 211; +NTIPAliasStat["unused211"] = 212; +NTIPAliasStat["unused212"] = 213; +NTIPAliasStat["itemarmorperlevel"] = 214; +NTIPAliasStat["itemarmorpercentperlevel"] = 215; +NTIPAliasStat["itemhpperlevel"] = 216; +NTIPAliasStat["itemmanaperlevel"] = 217; +NTIPAliasStat["itemmaxdamageperlevel"] = 218; +NTIPAliasStat["itemmaxdamagepercentperlevel"] = 219; +NTIPAliasStat["itemstrengthperlevel"] = 220; +NTIPAliasStat["itemdexterityperlevel"] = 221; +NTIPAliasStat["itemenergyperlevel"] = 222; +NTIPAliasStat["itemvitalityperlevel"] = 223; +NTIPAliasStat["itemtohitperlevel"] = 224; +NTIPAliasStat["itemtohitpercentperlevel"] = 225; +NTIPAliasStat["itemcolddamagemaxperlevel"] = 226; +NTIPAliasStat["itemfiredamagemaxperlevel"] = 227; +NTIPAliasStat["itemltngdamagemaxperlevel"] = 228; +NTIPAliasStat["itempoisdamagemaxperlevel"] = 229; +NTIPAliasStat["itemresistcoldperlevel"] = 230; +NTIPAliasStat["itemresistfireperlevel"] = 231; +NTIPAliasStat["itemresistltngperlevel"] = 232; +NTIPAliasStat["itemresistpoisperlevel"] = 233; +NTIPAliasStat["itemabsorbcoldperlevel"] = 234; +NTIPAliasStat["itemabsorbfireperlevel"] = 235; +NTIPAliasStat["itemabsorbltngperlevel"] = 236; +NTIPAliasStat["itemabsorbpoisperlevel"] = 237; +NTIPAliasStat["itemthornsperlevel"] = 238; +NTIPAliasStat["itemfindgoldperlevel"] = 239; +NTIPAliasStat["itemfindmagicperlevel"] = 240; +NTIPAliasStat["itemregenstaminaperlevel"] = 241; +NTIPAliasStat["itemstaminaperlevel"] = 242; +NTIPAliasStat["itemdamagedemonperlevel"] = 243; +NTIPAliasStat["itemdamageundeadperlevel"] = 244; +NTIPAliasStat["itemtohitdemonperlevel"] = 245; +NTIPAliasStat["itemtohitundeadperlevel"] = 246; +NTIPAliasStat["itemcrushingblowperlevel"] = 247; +NTIPAliasStat["itemopenwoundsperlevel"] = 248; +NTIPAliasStat["itemkickdamageperlevel"] = 249; +NTIPAliasStat["itemdeadlystrikeperlevel"] = 250; +NTIPAliasStat["itemfindgemsperlevel"] = 251; +NTIPAliasStat["itemreplenishdurability"] = 252; +NTIPAliasStat["itemreplenishquantity"] = 253; +NTIPAliasStat["itemextrastack"] = 254; +NTIPAliasStat["itemfinditem"] = 255; +NTIPAliasStat["itemslashdamage"] = 256; +NTIPAliasStat["itemslashdamagepercent"] = 257; +NTIPAliasStat["itemcrushdamage"] = 258; +NTIPAliasStat["itemcrushdamagepercent"] = 259; +NTIPAliasStat["itemthrustdamage"] = 260; +NTIPAliasStat["itemthrustdamagepercent"] = 261; +NTIPAliasStat["itemabsorbslash"] = 262; +NTIPAliasStat["itemabsorbcrush"] = 263; +NTIPAliasStat["itemabsorbthrust"] = 264; +NTIPAliasStat["itemabsorbslashpercent"] = 265; +NTIPAliasStat["itemabsorbcrushpercent"] = 266; +NTIPAliasStat["itemabsorbthrustpercent"] = 267; +NTIPAliasStat["itemarmorbytime"] = 268; +NTIPAliasStat["itemarmorpercentbytime"] = 269; +NTIPAliasStat["itemhpbytime"] = 270; +NTIPAliasStat["itemmanabytime"] = 271; +NTIPAliasStat["itemmaxdamagebytime"] = 272; +NTIPAliasStat["itemmaxdamagepercentbytime"] = 273; +NTIPAliasStat["itemstrengthbytime"] = 274; +NTIPAliasStat["itemdexteritybytime"] = 275; +NTIPAliasStat["itemenergybytime"] = 276; +NTIPAliasStat["itemvitalitybytime"] = 277; +NTIPAliasStat["itemtohitbytime"] = 278; +NTIPAliasStat["itemtohitpercentbytime"] = 279; +NTIPAliasStat["itemcolddamagemaxbytime"] = 280; +NTIPAliasStat["itemfiredamagemaxbytime"] = 281; +NTIPAliasStat["itemltngdamagemaxbytime"] = 282; +NTIPAliasStat["itempoisdamagemaxbytime"] = 283; +NTIPAliasStat["itemresistcoldbytime"] = 284; +NTIPAliasStat["itemresistfirebytime"] = 285; +NTIPAliasStat["itemresistltngbytime"] = 286; +NTIPAliasStat["itemresistpoisbytime"] = 287; +NTIPAliasStat["itemabsorbcoldbytime"] = 288; +NTIPAliasStat["itemabsorbfirebytime"] = 289; +NTIPAliasStat["itemabsorbltngbytime"] = 290; +NTIPAliasStat["itemabsorbpoisbytime"] = 291; +NTIPAliasStat["itemfindgoldbytime"] = 292; +NTIPAliasStat["itemfindmagicbytime"] = 293; +NTIPAliasStat["itemregenstaminabytime"] = 294; +NTIPAliasStat["itemstaminabytime"] = 295; +NTIPAliasStat["itemdamagedemonbytime"] = 296; +NTIPAliasStat["itemdamageundeadbytime"] = 297; +NTIPAliasStat["itemtohitdemonbytime"] = 298; +NTIPAliasStat["itemtohitundeadbytime"] = 299; +NTIPAliasStat["itemcrushingblowbytime"] = 300; +NTIPAliasStat["itemopenwoundsbytime"] = 301; +NTIPAliasStat["itemkickdamagebytime"] = 302; +NTIPAliasStat["itemdeadlystrikebytime"] = 303; +NTIPAliasStat["itemfindgemsbytime"] = 304; +NTIPAliasStat["itempiercecold"] = 305; +NTIPAliasStat["itempiercefire"] = 306; +NTIPAliasStat["itempierceltng"] = 307; +NTIPAliasStat["itempiercepois"] = 308; +NTIPAliasStat["itemdamagevsmonster"] = 309; +NTIPAliasStat["itemdamagepercentvsmonster"] = 310; +NTIPAliasStat["itemtohitvsmonster"] = 311; +NTIPAliasStat["itemtohitpercentvsmonster"] = 312; +NTIPAliasStat["itemacvsmonster"] = 313; +NTIPAliasStat["itemacpercentvsmonster"] = 314; +NTIPAliasStat["firelength"] = 315; +NTIPAliasStat["burningmin"] = 316; +NTIPAliasStat["burningmax"] = 317; +NTIPAliasStat["progressivedamage"] = 318; +NTIPAliasStat["progressivesteal"] = 319; +NTIPAliasStat["progressiveother"] = 320; +NTIPAliasStat["progressivefire"] = 321; +NTIPAliasStat["progressivecold"] = 322; +NTIPAliasStat["progressivelightning"] = 323; +NTIPAliasStat["itemextracharges"] = 324; +NTIPAliasStat["progressivetohit"] = 325; +NTIPAliasStat["poisoncount"] = 326; +NTIPAliasStat["damageframerate"] = 327; +NTIPAliasStat["pierceidx"] = 328; +NTIPAliasStat["passivefiremastery"] = 329; +NTIPAliasStat["passiveltngmastery"] = 330; +NTIPAliasStat["passivecoldmastery"] = 331; +NTIPAliasStat["passivepoismastery"] = 332; +NTIPAliasStat["passivefirepierce"] = 333; +NTIPAliasStat["passiveltngpierce"] = 334; +NTIPAliasStat["passivecoldpierce"] = 335; +NTIPAliasStat["passivepoispierce"] = 336; +NTIPAliasStat["passivecriticalstrike"] = 337; +NTIPAliasStat["passivedodge"] = 338; +NTIPAliasStat["passiveavoid"] = 339; +NTIPAliasStat["passiveevade"] = 340; +NTIPAliasStat["passivewarmth"] = 341; +NTIPAliasStat["passivemasterymeleeth"] = 342; +NTIPAliasStat["passivemasterymeleedmg"] = 343; +NTIPAliasStat["passivemasterymeleecrit"] = 344; +NTIPAliasStat["passivemasterythrowth"] = 345; +NTIPAliasStat["passivemasterythrowdmg"] = 346; +NTIPAliasStat["passivemasterythrowcrit"] = 347; +NTIPAliasStat["passiveweaponblock"] = 348; +NTIPAliasStat["passivesummonresist"] = 349; +NTIPAliasStat["modifierlistskill"] = 350; +NTIPAliasStat["modifierlistlevel"] = 351; +NTIPAliasStat["lastsenthppct"] = 352; +NTIPAliasStat["sourceunittype"] = 353; +NTIPAliasStat["sourceunitid"] = 354; +NTIPAliasStat["shortparam1"] = 355; +NTIPAliasStat["questitemdifficulty"] = 356; +NTIPAliasStat["passivemagmastery"] = 357; +NTIPAliasStat["passivemagpierce"] = 358; + + +// Doesnt really exists, but is calculated in getStatEx +NTIPAliasStat["allres"] = 555; diff --git a/d2bs/kolbot/libs/core/GameData/QuestData.js b/d2bs/kolbot/libs/core/GameData/QuestData.js new file mode 100644 index 000000000..ccb709f36 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/QuestData.js @@ -0,0 +1,179 @@ +/** +* @filename QuestData.js +* @author theBGuy +* @desc quest data library, make checking quests easier +* +*/ + +(function (module) { + /** + * @todo Fill out more, items for quests, npcs, etc + */ + const QuestData = (function () { + let _lastRefresh = 0; + + /** @type {Set} */ + const _specials = new Set(); + [ + sdk.quest.id.SpokeToWarriv, sdk.quest.id.AbleToGotoActII, + sdk.quest.id.SpokeToJerhyn, sdk.quest.id.AbleToGotoActIII, + sdk.quest.id.SpokeToHratli, sdk.quest.id.AbleToGotoActIV, + sdk.quest.id.SpokeToTyrael, sdk.quest.id.AbleToGotoActV, + ].forEach(questId => _specials.add(questId)); + + const refresh = function () { + if (getTickCount() - _lastRefresh > 500) { + Packet.questRefresh(); + _lastRefresh = getTickCount(); + } + }; + + /** + * @constructor + * @param {number} questId + * @param {number} act + */ + function Quest (questId, act) { + this.id = questId; + this.act = act; + this.states = new Array(16).fill(0); // todo figure a method to ensure the length is immutable + this.completed = false; + this.reqComplete = false; + this.cannotComplete = false; + } + + Quest.prototype.complete = function (reqCheck = false) { + if (this.completed) return true; + if (this.cannotComplete) return false; + if (reqCheck && this.reqComplete) return true; + refresh(); + + let completedStatus = me.getQuest(this.id, sdk.quest.states.Completed); + if (completedStatus) { + this.completed = true; + return true; + } + + let cannotCompleteStatus = me.getQuest(this.id, sdk.quest.states.CannotComplete); + if (cannotCompleteStatus) { + this.cannotComplete = true; + return false; + } + + if (reqCheck) { + let reqCompleteStatus = me.getQuest(this.id, sdk.quest.states.ReqComplete); + if (reqCompleteStatus) { + this.reqComplete = true; + return true; + } + } + + return false; + }; + + /** + * @param {number} state - quest state (0 - 15) + * @param {boolean} complete - if true, will check if state bit is 1 (active) otherwise 0 (inactive) + * @returns {boolean} + */ + Quest.prototype.checkState = function (state, complete = true) { + // handle the ones we already know + if (state === sdk.quest.states.Completed && this.completed) return complete; + if (state === sdk.quest.states.CannotComplete && this.cannotComplete) return complete; + if (state === sdk.quest.states.ReqComplete && this.reqComplete) return complete; + + refresh(); + let val = me.getQuest(this.id, state); + this.states[state] = val; + return complete ? val === 1 : val === 0; + }; + + Quest.prototype.getStates = function () { + refresh(); + + // the non-visible quests can stop after 1 + let max = _specials.has(this.id) ? 1 : 16; + + for (let state = 0; state < max; state++) { + this.states[state] = me.getQuest(this.id, state); + delay(10); + } + + this.completed = this.states[sdk.quest.states.Completed] === 1; + this.cannotComplete = this.states[sdk.quest.states.CannotComplete] === 1; + this.reqComplete = this.states[sdk.quest.states.ReqComplete] === 1; + + return this.states; + }; + + /** @type {Map} */ + const questMap = new Map(); + [ + [ + sdk.quest.id.SpokeToWarriv, sdk.quest.id.DenofEvil, + sdk.quest.id.SistersBurialGrounds, sdk.quest.id.ToolsoftheTrade, + sdk.quest.id.TheSearchForCain, sdk.quest.id.ForgottenTower, + sdk.quest.id.SistersToTheSlaughter, sdk.quest.id.Respec + ], + [ + sdk.quest.id.AbleToGotoActII, sdk.quest.id.SpokeToJerhyn, + sdk.quest.id.RadamentsLair, sdk.quest.id.TheHoradricStaff, + sdk.quest.id.TheTaintedSun, sdk.quest.id.TheSummoner, + sdk.quest.id.TheArcaneSanctuary, sdk.quest.id.TheSevenTombs + ], + [ + sdk.quest.id.AbleToGotoActIII, sdk.quest.id.SpokeToHratli, + sdk.quest.id.TheGoldenBird, sdk.quest.id.BladeoftheOldReligion, + sdk.quest.id.LamEsensTome, sdk.quest.id.KhalimsWill, + sdk.quest.id.TheBlackenedTemple, sdk.quest.id.TheGuardian, + ], + [ + sdk.quest.id.AbleToGotoActIV, sdk.quest.id.TheFallenAngel, + sdk.quest.id.SpokeToTyrael, sdk.quest.id.HellsForge, sdk.quest.id.TerrorsEnd, + ], + [ + sdk.quest.id.AbleToGotoActV, sdk.quest.id.SiegeOnHarrogath, + sdk.quest.id.RescueonMountArreat, sdk.quest.id.PrisonofIce, + sdk.quest.id.BetrayalofHarrogath, sdk.quest.id.RiteofPassage, sdk.quest.id.EyeofDestruction, + ] + ].forEach((questIds, act) => { + for (let questId of questIds) { + questMap.set(questId, new Quest(questId, act + 1)); + } + }); + + return { + /** + * @param {number} questId + * @returns {Quest | undefined} + */ + get: function (questId) { + return questMap.get(questId); + }, + + /** + * @param {number} questId + * @returns {boolean} + */ + has: function (questId) { + return questMap.has(questId); + }, + + init: function () { + console.time("QuestData.init"); + questMap.forEach(quest => quest.getStates()); + console.timeEnd("QuestData.init"); + }, + + /** + * @param {number} questId + * @returns {number} + */ + getActForQuest: function (questId) { + return questMap.get(questId).act; + }, + }; + })(); + + module.exports = QuestData; +})(module); diff --git a/d2bs/kolbot/libs/core/GameData/RuneData.js b/d2bs/kolbot/libs/core/GameData/RuneData.js new file mode 100644 index 000000000..503c9714d --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/RuneData.js @@ -0,0 +1,298 @@ +(function (module) { + const RunesData = (function () { + /** @type {Array} runewords - Array of runeword objects. */ + const runewords = []; + const ladder = me.ladder > 0; + const RUNES_COUNT = 169; + + const validRunes = Object.values(sdk.items.runes).filter(v => !isNaN(v)); + const validInsertable = (id) => { + if (validRunes.includes(id)) return true; + if (id === sdk.items.Jewel) return true; + return id >= sdk.items.gems.Chipped.Amethyst && id <= sdk.items.gems.Perfect.Skull; + }; + const anyShield = [sdk.items.type.Shield, sdk.items.type.AuricShields, sdk.items.type.VoodooHeads]; + const missileWeapon = [sdk.items.type.Bow, sdk.items.type.Crossbow, sdk.items.type.AmazonBow]; + const meleeWeapons = [ + sdk.items.type.Scepter, sdk.items.type.Wand, sdk.items.type.AmazonSpear, + sdk.items.type.Axe, sdk.items.type.Hammer, sdk.items.type.Mace, + sdk.items.type.Sword, sdk.items.type.Knife, sdk.items.type.AssassinClaw, + sdk.items.type.Polearm, sdk.items.type.Scepter, sdk.items.type.HandtoHand + ]; + const ladderRws = [ + "Brand", + "Death", + "Destruction", + "Dream", + "Dragon", + "Edge", + "Faith", + "Fortitude", + "Grief", + "Harmony", + "Ice", + "Infinity", + "Insight", + "LastWish", + "Lawbringer", + "Oath", + "Obedience", + "Phoenix", + "Pride", + "Rift", + "Spirit", + "VoiceofReason", + "Wrath", + ]; + + function getItemType (iType) { + switch (iType) { + case sdk.items.type.AnyShield: + return anyShield; + case sdk.items.type.Weapon: + return [].concat(missileWeapon, meleeWeapons); + case sdk.items.type.MissileWeapon: + return missileWeapon; + case sdk.items.type.MeleeWeapon: + return meleeWeapons; + case sdk.items.type.Helm: + return [sdk.items.type.Helm, sdk.items.type.Circlet, sdk.items.type.Pelt, sdk.items.type.PrimalHelm]; + default: + return [iType]; + } + } + + /** + * @constructor + * @param {string} name - The name of the recipe. + * @param {number} sockets - The number of sockets required for the recipe. + * @param {number[]} runes - Array of insertable IDs required for the recipe. + * @param {number[]} itemTypes - Array of item type IDs the recipe can be applied to. + */ + function RunewordObj (name, sockets, runes, itemTypes) { + this.name = name; + this.sockets = sockets; + this.runes = runes; + this.itemTypes = itemTypes; + this._ladder = ladderRws.includes(name); + let highestItem = runes.toSorted((a, b) => b - a).first(); + let reqLvl = getBaseStat("items", highestItem, "levelreq"); + this.reqLvl = reqLvl > 0 ? reqLvl : 1; + } + + RunewordObj.prototype.ladderRestricted = function () { + // not ladder restricted or we are on ladder + if (!this._ladder || ladder) return false; + // ladder restricted and we have enabled ladder override + if (Config.LadderOveride) return false; + // ladder restricted + return true; + }; + + /** + * Finds a runeword by name. + * @param {string} name - The name of the runeword. + * @returns {Runeword} - The runeword object. + */ + const findByName = function (name) { + return runewords.find(r => String.isEqual(r.name, name)); + }; + + /** + * Find all runewords that have the given rune. + * @param {number} rune - classid of rune + * @returns {Array} + */ + const findByRune = function (rune) { + return runewords.filter(r => r.runes.includes(rune)); + }; + + /** + * Find all runewords that can be applied to the given item type. + * @param {number} type - item type + * @returns {Array} + */ + const findByType = function (type) { + return runewords.filter(r => r.itemTypes.includes(type)); + }; + + /** + * Create a new non standard runeword. + * @param {string} name - The name of the recipe. + * @param {number} sockets - The number of sockets required for the recipe. + * @param {number[]} runes - Array of insertable IDs required for the recipe. + * @param {number[]} itemTypes - Array of item type IDs the recipe can be applied to. + * @returns {runeword} - The new runeword object. + */ + const addRuneword = function (name, sockets, runes, itemTypes) { + if (!name || !sockets || !runes || !itemTypes) return false; + !Array.isArray(runes) && (runes = [runes]); + if (!runes.every(validInsertable)) return false; + !Array.isArray(itemTypes) && (itemTypes = [itemTypes]); + + let rw = new RunewordObj(name, runes.length, runes, itemTypes.map(getItemType).flat()); + runewords.push(rw); + + return rw; + }; + + for (let i = 0; i < RUNES_COUNT; i++) { + const index = i; + if (!getBaseStat("runes", index, "complete")) continue; + + const runes = []; + + for (let r = 1; r < 7; r++) { + const rune = getBaseStat("runes", index, "rune" + r); + if (rune > -1 && validRunes.includes(rune)) { + runes.push(rune); + } else { + break; + } + } + + const itemTypes = [ + getBaseStat("runes", index, "itype1"), + getBaseStat("runes", index, "itype2"), + getBaseStat("runes", index, "itype3"), + getBaseStat("runes", index, "itype4"), + getBaseStat("runes", index, "itype5"), + getBaseStat("runes", index, "itype6"), + ].filter(el => el && el !== 65535).map(getItemType).flat(); + + const name = (() => { + let temp = getBaseStat("runes", index, "rune name"); + + switch (temp) { + case "The Beast": + return "Beast"; + case "Bound by Duty": + return "ChainsofHonor"; + case "Doomsayer": + return "Doom"; + case "Exile's Path": + return "Exile"; + case "Widowmaker": + return "Grief"; + case "Winter": + return "VoiceofReason"; + default: + return temp.replace(/[^a-zA-Z0-9]/g, ""); + } + })(); + + runewords.push(new RunewordObj(name, runes.length, runes, itemTypes)); + } + + return { + // runewords: runewords, // other files don't actually need this + // 1.09 + AncientsPledge: findByName("AncientsPledge"), + Black: findByName("Black"), + Fury: findByName("Fury"), + HolyThunder: findByName("HolyThunder"), + Honor: findByName("Honor"), + KingsGrace: findByName("KingsGrace"), + Leaf: findByName("Leaf"), + Lionheart: findByName("Lionheart"), + Lore: findByName("Lore"), + Malice: findByName("Malice"), + Melody: findByName("Melody"), + Memory: findByName("Memory"), + Nadir: findByName("Nadir"), + Radiance: findByName("Radiance"), + Rhyme: findByName("Rhyme"), + Silence: findByName("Silence"), + Smoke: findByName("Smoke"), + Stealth: findByName("Stealth"), + Steel: findByName("Steel"), + Strength: findByName("Strength"), + Venom: findByName("Venom"), + Wealth: findByName("Wealth"), + White: findByName("White"), + Zephyr: findByName("Zephyr"), + + // 1.10 + Beast: findByName("Beast"), + Bramble: findByName("Bramble"), + BreathoftheDying: findByName("BreathoftheDying"), + CallToArms: findByName("CallToArms"), + ChainsofHonor: findByName("ChainsofHonor"), + Chaos: findByName("Chaos"), + CrescentMoon: findByName("CrescentMoon"), + Delirium: findByName("Delirium"), + Doom: findByName("Doom"), + Duress: findByName("Duress"), + Enigma: findByName("Enigma"), + Eternity: findByName("Eternity"), + Exile: findByName("Exile"), + Famine: findByName("Famine"), + Gloom: findByName("Gloom"), + HandofJustice: findByName("HandofJustice"), + HeartoftheOak: findByName("HeartoftheOak"), + Kingslayer: findByName("Kingslayer"), + Passion: findByName("Passion"), + Prudence: findByName("Prudence"), + Sanctuary: findByName("Sanctuary"), + Splendor: findByName("Splendor"), + Stone: findByName("Stone"), + Wind: findByName("Wind"), + + // ladder only + Brand: findByName("Brand"), + Death: findByName("Death"), + Destruction: findByName("Destruction"), + Dragon: findByName("Dragon"), + Dream: findByName("Dream"), + Edge: findByName("Edge"), + Faith: findByName("Faith"), + Fortitude: findByName("Fortitude"), + Grief: findByName("Grief"), + Harmony: findByName("Harmony"), + Ice: findByName("Ice"), + Infinity: findByName("Infinity"), + Insight: findByName("Insight"), + LastWish: findByName("LastWish"), + Lawbringer: findByName("Lawbringer"), + Oath: findByName("Oath"), + Obedience: findByName("Obedience"), + Phoenix: findByName("Phoenix"), + Pride: findByName("Pride"), + Rift: findByName("Rift"), + Spirit: findByName("Spirit"), + VoiceofReason: findByName("VoiceofReason"), + Wrath: findByName("Wrath"), + + // 1.11 + Bone: findByName("Bone"), + Enlightenment: findByName("Enlightenment"), + Myth: findByName("Myth"), + Peace: findByName("Peace"), + Principle: findByName("Principle"), + Rain: findByName("Rain"), + Treachery: findByName("Treachery"), + + Test: (() => { + addRuneword("Test", 3, + [sdk.items.runes.Hel, sdk.items.runes.Hel, sdk.items.runes.Hel], + [sdk.items.type.Armor, sdk.items.type.AnyShield, sdk.items.type.Weapon, sdk.items.type.Helm] + ); + return findByName("Test"); + })(), + + addRuneword: addRuneword, + findByName: findByName, + findByRune: findByRune, + findByType: findByType, + }; + })(); + + Object.defineProperties(RunesData, { + "addRuneword": { enumerable: false }, + "findByName": { enumerable: false }, + "findByRune": { enumerable: false }, + "findByType": { enumerable: false }, + }); + + module.exports = RunesData; +})(module); diff --git a/d2bs/kolbot/libs/core/GameData/ShrineData.js b/d2bs/kolbot/libs/core/GameData/ShrineData.js new file mode 100644 index 000000000..551be62dc --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/ShrineData.js @@ -0,0 +1,76 @@ +/** +* @filename ShrineData.js +* @author theBGuy +* @desc shrine data library, handles shrine types, states, durations, and regen times +* +*/ + +(function (module) { + const ShrineData = (function () { + /** + * @constructor + * @param {number} state + * @param {number} duration + * @param {number} regen + */ + function Shrine (state, duration, regen) { + this.state = state || 0; + this.duration = duration || 0; + this.regenTime = Time.minutes(regen) || Infinity; + } + const _shrines = new Map([ + [sdk.shrines.Refilling, new Shrine(sdk.shrines.None, 0, 2)], + [sdk.shrines.Health, new Shrine(sdk.shrines.None, 0, 5)], + [sdk.shrines.Mana, new Shrine(sdk.shrines.None, 0, 5)], + [sdk.shrines.HealthExchange, new Shrine()], + [sdk.shrines.ManaExchange, new Shrine()], + [sdk.shrines.Armor, new Shrine(sdk.states.ShrineArmor, 2400, 5)], + [sdk.shrines.Combat, new Shrine(sdk.states.ShrineCombat, 2400, 5)], + [sdk.shrines.ResistFire, new Shrine(sdk.states.ShrineResFire, 3600, 5)], + [sdk.shrines.ResistCold, new Shrine(sdk.states.ShrineResCold, 3600, 5)], + [sdk.shrines.ResistLightning, new Shrine(sdk.states.ShrineResLighting, 3600, 5)], + [sdk.shrines.ResistPoison, new Shrine(sdk.states.ShrineResPoison, 3600, 5)], + [sdk.shrines.Skill, new Shrine(sdk.states.ShrineSkill, 2400, 5)], + [sdk.shrines.ManaRecharge, new Shrine(sdk.states.ShrineManaRegen, 2400, 5)], + [sdk.shrines.Stamina, new Shrine(sdk.states.ShrineStamina, 4800, 5)], + [sdk.shrines.Experience, new Shrine(sdk.states.ShrineResCold, 3600)], + [sdk.shrines.Enirhs, new Shrine()], + [sdk.shrines.Portal, new Shrine()], + [sdk.shrines.Gem, new Shrine()], + [sdk.shrines.Fire, new Shrine()], + [sdk.shrines.Monster, new Shrine()], + [sdk.shrines.Exploding, new Shrine()], + [sdk.shrines.Poison, new Shrine()], + ]); + + return { + /** @param {number} shrineType */ + get: function (shrineType) { + return _shrines.get(shrineType); + }, + + /** @param {number} shrineType */ + has: function (shrineType) { + return _shrines.has(shrineType); + }, + + /** @param {number} shrineType */ + getState: function (shrineType) { + if (!_shrines.has(shrineType)) return 0; + return _shrines.get(shrineType).state || 0; + }, + + /** @param {number} shrineType */ + getDuration: function (shrineType) { + return _shrines.get(shrineType).duration || 0; + }, + + /** @param {number} shrineType */ + getRegenTime: function (shrineType) { + return _shrines.get(shrineType).regenTime || Infinity; + }, + }; + })(); + + module.exports = ShrineData; +})(module); diff --git a/d2bs/kolbot/libs/core/GameData/SkillData.js b/d2bs/kolbot/libs/core/GameData/SkillData.js new file mode 100644 index 000000000..f591427f8 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/SkillData.js @@ -0,0 +1,1574 @@ +/** +* @filename SkillData.js +* @author theBGuy +* @desc skill data library +* +*/ + + +(function (module) { + /** + * @typedef {Object} SkillInterface + * @property {number} hand + * @property {boolean} [missile] + * @property {number | () => number} range + * @property {number} [state] + * @property {number} [summonType] + * @property {() => boolean} [condition] + * @property {() => number} [summonCount] + */ + + /** @type {Map} */ + const skillMap = new Map(); + // basics + { + skillMap.set(sdk.skills.Attack, { + hand: sdk.skills.hand.LeftNoShift, + range: () => Attack.usingBow() ? 20 : 3, + }); + skillMap.set(sdk.skills.Kick, { + hand: sdk.skills.hand.Right, + range: 3, + }); + skillMap.set(sdk.skills.Throw, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Unsummon, { + hand: sdk.skills.hand.Right, + range: 20, + }); + skillMap.set(sdk.skills.LeftHandThrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.LeftHandSwing, { + hand: sdk.skills.hand.Right, + range: 3, + }); + } + // ~~~ start of amazon skills ~~~ // + { + skillMap.set(sdk.skills.MagicArrow, { + hand: sdk.skills.hand.Left, + missile: true, + }); + skillMap.set(sdk.skills.FireArrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.InnerSight, { + hand: sdk.skills.hand.Right, + range: 13, + state: sdk.states.InnerSight, + condition: () => Config.UseInnerSight, + }); + skillMap.set(sdk.skills.CriticalStrike, { + hand: -1, + range: -1, + state: sdk.states.CriticalStrike, + }); + skillMap.set(sdk.skills.Jab, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.ColdArrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.MultipleShot, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Dodge, { + hand: -1, + range: -1, + state: sdk.states.Dodge, + }); + skillMap.set(sdk.skills.PowerStrike, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.PoisonJavelin, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.ExplodingArrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.SlowMissiles, { + hand: sdk.skills.hand.Right, + range: 13, + state: sdk.states.SlowMissiles, + condition: () => Config.UseSlowMissiles, + }); + skillMap.set(sdk.skills.Avoid, { + hand: -1, + range: -1, + state: sdk.states.Avoid, + }); + skillMap.set(sdk.skills.Impale, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.LightningBolt, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.IceArrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.GuidedArrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Penetrate, { + hand: -1, + range: -1, + state: sdk.states.Penetrate, + }); + skillMap.set(sdk.skills.ChargedStrike, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.PlagueJavelin, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Strafe, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.ImmolationArrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Decoy, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.Dopplezon, + duration: () => ((10 + me.getSkill(sdk.skills.Decoy, sdk.skills.subindex.SoftPoints) * 5)), + condition: () => Config.UseDecoy, + }); + skillMap.set(sdk.skills.Evade, { + hand: -1, + range: -1, + state: sdk.states.Evade, + }); + skillMap.set(sdk.skills.Fend, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.FreezingArrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 30, + AoE: () => 3, + }); + skillMap.set(sdk.skills.Valkyrie, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.Valkyrie, + summonCount: () => 1, + condition: () => Config.SummonValkyrie, + }); + skillMap.set(sdk.skills.Pierce, { + hand: -1, + range: -1, + state: sdk.states.Pierce, + }); + skillMap.set(sdk.skills.LightningStrike, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.LightningFury, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + } + // ~~~ start of sorc skills ~~~ // + { + skillMap.set(sdk.skills.FireBolt, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Warmth, { + hand: -1, + range: -1, + state: sdk.states.Warmth, + }); + skillMap.set(sdk.skills.ChargedBolt, { + hand: sdk.skills.hand.Left, + missile: true, + range: 10, + }); + skillMap.set(sdk.skills.IceBolt, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.FrozenArmor, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.FrozenArmor, + duration: () => ( + ((12 * me.getSkill(sdk.skills.FrozenArmor, sdk.skills.subindex.SoftPoints) + 108) + + ((me.getSkill(sdk.skills.ShiverArmor, sdk.skills.subindex.HardPoints) + + me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.HardPoints)) * 10)) + ), + }); + skillMap.set(sdk.skills.Inferno, { + hand: sdk.skills.hand.Left, + missile: true, + range: () => (((17 + (me.getSkill(sdk.skills.Inferno, sdk.skills.subindex.SoftPoints) * 3)) / 4) * 2 / 3), + }); + skillMap.set(sdk.skills.StaticField, { + hand: sdk.skills.hand.Right, + range: () => Math.floor((me.getSkill(sdk.skills.StaticField, sdk.skills.subindex.SoftPoints) + 4) * 2 / 3), + }); + skillMap.set(sdk.skills.Telekinesis, { + hand: sdk.skills.hand.Right, + range: 40, + condition: () => Config.UseTelekinesis, + }); + skillMap.set(sdk.skills.FrostNova, { + hand: sdk.skills.hand.Right, + range: 5, + }); + skillMap.set(sdk.skills.IceBlast, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Blaze, { + hand: sdk.skills.hand.Right, + range: 1, + }); + skillMap.set(sdk.skills.FireBall, { + hand: sdk.skills.hand.Left, + missile: true, + range: (pvpRange) => pvpRange ? 40 : 20, + }); + skillMap.set(sdk.skills.Nova, { + hand: sdk.skills.hand.Right, + range: 7, + }); + skillMap.set(sdk.skills.Lightning, { + hand: sdk.skills.hand.Left, + missile: true, + range: 25, + }); + skillMap.set(sdk.skills.ShiverArmor, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.ShiverArmor, + duration: () => ( + ((12 * me.getSkill(sdk.skills.ShiverArmor, sdk.skills.subindex.SoftPoints) + 108) + + ((me.getSkill(sdk.skills.FrozenArmor, sdk.skills.subindex.HardPoints) + + me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.HardPoints)) * 10)) + ), + }); + skillMap.set(sdk.skills.FireWall, { + hand: sdk.skills.hand.Right, + range: (pvpRange) => pvpRange ? 40 : 30, + }); + skillMap.set(sdk.skills.Enchant, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Enchant, + duration: () => (120 + (24 * me.getSkill(sdk.skills.Enchant, sdk.skills.subindex.SoftPoints))), + }); + skillMap.set(sdk.skills.ChainLightning, { + hand: sdk.skills.hand.Left, + missile: true, + range: 25, + }); + skillMap.set(sdk.skills.Teleport, { + hand: sdk.skills.hand.Right, + range: 40, + }); + skillMap.set(sdk.skills.GlacialSpike, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.Meteor, { + hand: sdk.skills.hand.Right, + range: (pvpRange) => pvpRange ? 40 : 30, + }); + skillMap.set(sdk.skills.ThunderStorm, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.ThunderStorm, + townSkill: true, + duration: () => (24 + (8 * me.getSkill(sdk.skills.ThunderStorm, sdk.skills.subindex.SoftPoints))), + }); + skillMap.set(sdk.skills.EnergyShield, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.EnergyShield, + duration: () => (84 + (60 * me.getSkill(sdk.skills.EnergyShield, sdk.skills.subindex.SoftPoints))), + condition: () => Config.UseEnergyShield, + }); + skillMap.set(sdk.skills.Blizzard, { + hand: sdk.skills.hand.Right, + range: (pvpRange) => pvpRange ? 40 : 30, + }); + skillMap.set(sdk.skills.ChillingArmor, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.ChillingArmor, + duration: () => ( + ((6 * me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.SoftPoints) + 138) + + ((me.getSkill(sdk.skills.FrozenArmor, sdk.skills.subindex.HardPoints) + + me.getSkill(sdk.skills.ChillingArmor, sdk.skills.subindex.HardPoints)) * 10)) + ), + }); + skillMap.set(sdk.skills.FireMastery, { + hand: -1, + range: -1, + state: sdk.states.FireMastery, + }); + skillMap.set(sdk.skills.Hydra, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.Hydra, + duration: () => 10, + AoE: () => 14, + }); + skillMap.set(sdk.skills.LightningMastery, { + hand: -1, + range: -1, + state: sdk.states.LightningMastery, + }); + skillMap.set(sdk.skills.FrozenOrb, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.ColdMastery, { + hand: -1, + range: -1, + state: sdk.states.ColdMastery, + }); + } + // ~~~ start of necro skills ~~~ // + { + skillMap.set(sdk.skills.AmplifyDamage, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.AmplifyDamage, + duration: () => (5 + (3 * me.getSkill(sdk.skills.AmplifyDamage, sdk.skills.subindex.SoftPoints))), + AoE: () => ((4 + (2 * me.getSkill(sdk.skills.AmplifyDamage, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Teeth, { + hand: sdk.skills.hand.Left, + missile: true, + range: 15, + }); + skillMap.set(sdk.skills.BoneArmor, { + hand: sdk.skills.hand.Right, + state: sdk.states.BoneArmor, + range: 1, + }); + skillMap.set(sdk.skills.SkeletonMastery, { + hand: -1, + range: -1, + }); + skillMap.set(sdk.skills.RaiseSkeleton, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Skeleton, + summonCount: () => { + let skillNum = me.getSkill(sdk.skills.RaiseSkeleton, sdk.skills.subindex.SoftPoints); + return skillNum < 4 ? skillNum : (Math.floor(skillNum / 3) + 2); + }, + }); + skillMap.set(sdk.skills.DimVision, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.DimVision, + duration: () => { + switch (me.diff) { + case sdk.difficulty.Normal: + return (5 + (2 * me.getSkill(sdk.skills.DimVision, sdk.skills.subindex.SoftPoints))); + case sdk.difficulty.Nightmare: + return ((125 + (50 * me.getSkill(sdk.skills.DimVision, sdk.skills.subindex.SoftPoints))) * 0.5) / 25; + default: + return ((125 + (50 * me.getSkill(sdk.skills.DimVision, sdk.skills.subindex.SoftPoints))) * 0.25) / 25; + } + }, + AoE: () => ((6 + (2 * me.getSkill(sdk.skills.DimVision, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Weaken, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Weaken, + duration: () => (11.6 + (2.4 * me.getSkill(sdk.skills.Weaken, sdk.skills.subindex.SoftPoints))), + AoE: () => ((16 + (2 * me.getSkill(sdk.skills.Weaken, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.PoisonDagger, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.CorpseExplosion, { + hand: sdk.skills.hand.Right, + range: 40, + AoE: () => (((7 + me.getSkill(sdk.skills.AmplifyDamage, sdk.skills.subindex.SoftPoints)) / 2) * (2 / 3)), + }); + skillMap.set(sdk.skills.ClayGolem, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Golem, + summonCount: () => 1, + }); + skillMap.set(sdk.skills.IronMaiden, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.IronMaiden, + duration: () => (9.6 + (2.4 * me.getSkill(sdk.skills.IronMaiden, sdk.skills.subindex.SoftPoints))), + AoE: () => 4, + }); + skillMap.set(sdk.skills.Terror, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Terror, + duration: () => { + switch (me.diff) { + case sdk.difficulty.Normal: + return (7 + me.getSkill(sdk.skills.Terror, sdk.skills.subindex.SoftPoints)); + case sdk.difficulty.Nightmare: + return ((175 + (25 * me.getSkill(sdk.skills.Terror, sdk.skills.subindex.SoftPoints))) * 0.5) / 25; + default: + return ((175 + (25 * me.getSkill(sdk.skills.Terror, sdk.skills.subindex.SoftPoints))) * 0.25) / 25; + } + }, + AoE: () => 2.66, + }); + skillMap.set(sdk.skills.BoneWall, { + hand: sdk.skills.hand.Right, + range: 40, + }); + skillMap.set(sdk.skills.GolemMastery, { + hand: -1, + range: -1, + }); + skillMap.set(sdk.skills.RaiseSkeletalMage, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.SkeletonMage, + summonCount: () => { + let skillNum = me.getSkill(sdk.skills.RaiseSkeletalMage, sdk.skills.subindex.SoftPoints); + return skillNum < 4 ? skillNum : (Math.floor(skillNum / 3) + 2); + }, + }); + skillMap.set(sdk.skills.Confuse, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Confuse, + duration: () => { + switch (me.diff) { + case sdk.difficulty.Normal: + return (8 + (2 * me.getSkill(sdk.skills.Confuse, sdk.skills.subindex.SoftPoints))); + case sdk.difficulty.Nightmare: + return ((200 + (50 * me.getSkill(sdk.skills.Confuse, sdk.skills.subindex.SoftPoints))) * 0.5) / 25; + default: + return ((200 + (50 * me.getSkill(sdk.skills.Confuse, sdk.skills.subindex.SoftPoints))) * 0.25) / 25; + } + }, + AoE: () => ((10 + (2 * me.getSkill(sdk.skills.Confuse, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.LifeTap, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.LifeTap, + duration: () => (13.6 + (2.4 * me.getSkill(sdk.skills.LifeTap, sdk.skills.subindex.SoftPoints))), + AoE: () => ((6 + (2 * me.getSkill(sdk.skills.LifeTap, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.PoisonExplosion, { + hand: sdk.skills.hand.Right, + range: 40, + AoE: () => 2, + }); + skillMap.set(sdk.skills.BoneSpear, { + hand: sdk.skills.hand.Left, + missile: true, + range: (pvpRange) => pvpRange ? 35 : 25, + }); + skillMap.set(sdk.skills.BloodGolem, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Golem, + summonCount: () => 1, + }); + skillMap.set(sdk.skills.Attract, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Attract, + duration: () => { + switch (me.diff) { + case sdk.difficulty.Normal: + return (8.4 + (3.6 * me.getSkill(sdk.skills.Attract, sdk.skills.subindex.SoftPoints))); + case sdk.difficulty.Nightmare: + return ((210 + (90 * me.getSkill(sdk.skills.Attract, sdk.skills.subindex.SoftPoints))) * 0.5) / 25; + default: + return ((210 + (90 * me.getSkill(sdk.skills.Attract, sdk.skills.subindex.SoftPoints))) * 0.25) / 25; + } + }, + AoE: () => 6, + }); + skillMap.set(sdk.skills.Decrepify, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Decrepify, + duration: () => (3.4 + (0.6 * me.getSkill(sdk.skills.Decrepify, sdk.skills.subindex.SoftPoints))), + AoE: () => 4, + }); + skillMap.set(sdk.skills.BonePrison, { + hand: sdk.skills.hand.Right, + range: 40, + }); + skillMap.set(sdk.skills.SummonResist, { + hand: -1, + range: -1, + }); + skillMap.set(sdk.skills.IronGolem, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Golem, + summonCount: () => 1, + }); + skillMap.set(sdk.skills.LowerResist, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.LowerResist, + duration: () => (18 + (2 * me.getSkill(sdk.skills.LowerResist, sdk.skills.subindex.SoftPoints))), + AoE: () => ((12 + (2 * me.getSkill(sdk.skills.LowerResist, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.PoisonNova, { + hand: sdk.skills.hand.Right, + range: 20, + state: sdk.states.Poison, + AoE: () => 11, + }); + skillMap.set(sdk.skills.BoneSpirit, { + hand: sdk.skills.hand.Left, + missile: true, + range: (pvpRange) => pvpRange ? 40 : 30, + }); + skillMap.set(sdk.skills.FireGolem, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Golem, + summonCount: () => 1, + }); + skillMap.set(sdk.skills.Revive, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Revive, + summonCount: () => me.getSkill(sdk.skills.Revive, sdk.skills.subindex.SoftPoints), + }); + } + // ~~~ start of paladin skills ~~~ // + { + skillMap.set(sdk.skills.Sacrifice, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.Smite, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.Might, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.Might, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Might, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Prayer, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.Prayer, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Prayer, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.ResistFire, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.ResistFire, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.ResistFire, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.HolyBolt, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.HolyFire, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.HolyFire, + AoE: () => ((10 + (2 * me.getSkill(sdk.skills.HolyFire, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Thorns, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.Thorns, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Thorns, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Defiance, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.Defiance, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Defiance, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.ResistCold, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.ResistCold, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.ResistCold, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Zeal, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.Charge, { + hand: sdk.skills.hand.Left, + range: 10, + condition: () => Config.Charge, + }); + skillMap.set(sdk.skills.BlessedAim, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.BlessedAim, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.BlessedHammer, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Cleansing, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.Cleansing, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Cleansing, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.ResistLightning, { + range: 1, + state: sdk.states.ResistLightning, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.ResistLightning, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Vengeance, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.BlessedHammer, { + hand: sdk.skills.hand.Left, + range: 3, + AoE: () => 10, + }); + skillMap.set(sdk.skills.Concentration, { + range: 1, + state: sdk.states.Concentration, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Concentration, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.HolyFreeze, { + range: 1, + state: sdk.states.HolyFreeze, + AoE: () => ((10 + (2 * me.getSkill(sdk.skills.HolyFreeze, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Vigor, { + range: 1, + state: sdk.states.Stamina, + condition: () => Config.Vigor || me.inTown, + AoE: () => ((26 + (6 * me.getSkill(sdk.skills.Vigor, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Conversion, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + duration: () => 16, + }); + skillMap.set(sdk.skills.HolyShield, { + range: 1, + state: sdk.states.HolyShield, + duration: () => (5 + (25 * me.getSkill(sdk.skills.HolyShield, sdk.skills.subindex.SoftPoints))), + }); + skillMap.set(sdk.skills.HolyShock, { + range: 1, + state: sdk.states.HolyShock, + AoE: () => ((10 + (2 * me.getSkill(sdk.skills.HolyShock, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Sanctuary, { + range: 1, + state: sdk.states.Sanctuary, + AoE: () => ((8 + (2 * me.getSkill(sdk.skills.Sanctuary, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Meditation, { + range: 1, + state: sdk.states.Meditation, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Meditation, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.FistoftheHeavens, { + hand: sdk.skills.hand.Left, + range: 30, + }); + skillMap.set(sdk.skills.Fanaticism, { + range: 1, + state: sdk.states.Fanaticism, + AoE: () => ((20 + (2 * me.getSkill(sdk.skills.Fanaticism, sdk.skills.subindex.SoftPoints)) / 3)), + }); + skillMap.set(sdk.skills.Conviction, { + range: 1, + state: sdk.states.Conviction, + AoE: () => 13, + }); + skillMap.set(sdk.skills.Redemption, { + range: 1, + state: sdk.states.Redemption, + AoE: () => 10, + }); + skillMap.set(sdk.skills.Salvation, { + range: 1, + state: sdk.states.ResistAll, + AoE: () => ((28 + (4 * me.getSkill(sdk.skills.Salvation, sdk.skills.subindex.SoftPoints)) / 3)), + }); + } + // ~~~ start of barbarian skills ~~~ // + { + skillMap.set(sdk.skills.Bash, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.SwordMastery, { + hand: -1, + range: -1, + state: sdk.states.SwordMastery, + }); + skillMap.set(sdk.skills.AxeMastery, { + hand: -1, + range: -1, + state: sdk.states.AxeMastery, + }); + skillMap.set(sdk.skills.MaceMastery, { + hand: -1, + range: -1, + state: sdk.states.MaceMastery, + }); + skillMap.set(sdk.skills.Howl, { + range: 5, + state: sdk.states.Terror, + duration: () => (2 + me.getSkill(sdk.skills.Howl, sdk.skills.subindex.SoftPoints)), + }); + skillMap.set(sdk.skills.FindPotion, { + hand: sdk.skills.hand.RightShift, + range: 30, + }); + skillMap.set(sdk.skills.Leap, { + hand: sdk.skills.hand.Left, + range: () => { + let skLvl = me.getSkill(sdk.skills.Leap, sdk.skills.subindex.SoftPoints); + return Math.floor(Math.min(4 + (26 * ((110 * skLvl / (skLvl + 6)) / 100)), 30) * (2 / 3)); + }, + }); + skillMap.set(sdk.skills.DoubleSwing, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.PoleArmMastery, { + hand: -1, + range: -1, + state: sdk.states.PoleArmMastery, + }); + skillMap.set(sdk.skills.ThrowingMastery, { + hand: -1, + range: -1, + state: sdk.states.ThrowingMastery, + }); + skillMap.set(sdk.skills.SpearMastery, { + hand: -1, + range: -1, + state: sdk.states.SpearMastery, + }); + skillMap.set(sdk.skills.Taunt, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Taunt, + }); + skillMap.set(sdk.skills.Shout, { + hand: sdk.skills.hand.Right, + range: 20, + state: sdk.states.Shout, + duration: () => ( + ((10 + me.getSkill(sdk.skills.Shout, sdk.skills.subindex.SoftPoints) * 10) + + ((me.getSkill(sdk.skills.BattleOrders, sdk.skills.subindex.HardPoints) + + me.getSkill(sdk.skills.BattleCommand, sdk.skills.subindex.HardPoints)) * 5)) + ), + }); + skillMap.set(sdk.skills.Stun, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + state: sdk.states.Stunned, + duration: () => { + let skLvl = me.getSkill(sdk.skills.Stun, sdk.skills.subindex.SoftPoints); + let wcSkAddition = (me.getSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints) * 5); + return skLvl < 16 + ? 1 + (skLvl * 0.2) + wcSkAddition + : Math.min(2.92 + (skLvl * 0.08) + wcSkAddition, 10); + }, + }); + skillMap.set(sdk.skills.DoubleThrow, { + hand: sdk.skills.hand.Left, + missile: true, + range: 20, + }); + skillMap.set(sdk.skills.IncreasedStamina, { + hand: -1, + range: -1, + state: sdk.states.IncreasedStamina, + }); + skillMap.set(sdk.skills.FindItem, { + hand: sdk.skills.hand.RightShift, + range: 30, + condition: () => Config.FindItem, + }); + skillMap.set(sdk.skills.LeapAttack, { + hand: sdk.skills.hand.Left, + range: 10, + }); + skillMap.set(sdk.skills.Concentrate, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + state: sdk.states.Concentrate, + }); + skillMap.set(sdk.skills.IronSkin, { + hand: -1, + range: -1, + state: sdk.states.IronSkin, + }); + skillMap.set(sdk.skills.BattleCry, { + hand: sdk.skills.hand.Right, + range: 5, + state: sdk.states.BattleCry, + duration: () => ( + (9.6 + me.getSkill(sdk.skills.BattleCry, sdk.skills.subindex.SoftPoints) * 2.4) + ), + }); + skillMap.set(sdk.skills.Frenzy, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + state: sdk.states.Frenzy, + }); + skillMap.set(sdk.skills.IncreasedSpeed, { + hand: -1, + range: -1, + state: sdk.states.IncreasedSpeed, + }); + skillMap.set(sdk.skills.BattleOrders, { + hand: sdk.skills.hand.Right, + range: 20, + state: sdk.states.BattleOrders, + duration: () => ( + ((20 + me.getSkill(sdk.skills.BattleOrders, sdk.skills.subindex.SoftPoints) * 10) + + ((me.getSkill(sdk.skills.Shout, sdk.skills.subindex.HardPoints) + + me.getSkill(sdk.skills.BattleCommand, sdk.skills.subindex.HardPoints)) * 5)) + ), + }); + skillMap.set(sdk.skills.GrimWard, { + hand: sdk.skills.hand.RightShift, + range: 40, + state: sdk.states.Terror, + summonType: sdk.summons.type.Totem, + duration: () => 40, + AoE: () => (2 + me.getSkill(sdk.skills.GrimWard, sdk.skills.subindex.SoftPoints) * (2 / 3)), + }); + skillMap.set(sdk.skills.Whirlwind, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.Berserk, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + state: sdk.states.Berserk, + }); + skillMap.set(sdk.skills.NaturalResistance, { + hand: -1, + range: -1, + state: sdk.states.NaturalResistance, + }); + skillMap.set(sdk.skills.WarCry, { + hand: sdk.skills.hand.Right, + range: 5, + state: sdk.states.Stunned, + duration: () => ( + Math.min(0.8 + me.getSkill(sdk.skills.WarCry, sdk.skills.subindex.SoftPoints) * 0.2, 10) + ), + }); + skillMap.set(sdk.skills.BattleCommand, { + hand: sdk.skills.hand.Right, + range: 20, + state: sdk.states.BattleCommand, + duration: () => ( + ((10 * me.getSkill(sdk.skills.BattleCommand, sdk.skills.subindex.SoftPoints) - 5) + + ((me.getSkill(sdk.skills.Shout, sdk.skills.subindex.HardPoints) + + me.getSkill(sdk.skills.BattleOrders, sdk.skills.subindex.HardPoints)) * 5)) + ), + }); + } + // misc skills - scrolls and books + { + skillMap.set(sdk.skills.IdentifyScroll, { + hand: sdk.skills.hand.Right, + range: 1, + }); + skillMap.set(sdk.skills.BookofIdentify, { + hand: sdk.skills.hand.Right, + range: 1, + }); + skillMap.set(sdk.skills.TownPortalScroll, { + hand: sdk.skills.hand.Right, + range: 1, + }); + skillMap.set(sdk.skills.BookofTownPortal, { + hand: sdk.skills.hand.Right, + range: 1, + }); + } + // ~~~ start of druid skills ~~~ // + { + skillMap.set(sdk.skills.Raven, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Raven, + summonCount: () => Math.min(me.getSkill(sdk.skills.Raven, sdk.skills.subindex.SoftPoints), 5), + condition: () => Config.SummonRaven, + }); + skillMap.set(sdk.skills.PoisonCreeper, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Vine, + // condition: () => (typeof Config.SummonVine === "string" + // ? Config.SummonVine.toLowerCase() === "poison" + // : Config.SummonVine === sdk.skills.PoisonCreeper), + summonCount: () => 1, + }); + skillMap.set(sdk.skills.Werewolf, { + hand: sdk.skills.hand.Right, + range: 1, + duration: () => (40 + (20 * me.getSkill(sdk.skills.Lycanthropy, sdk.skills.subindex.SoftPoints) + 20)), + }); + skillMap.set(sdk.skills.Lycanthropy, { + hand: -1, + range: -1, + }); + skillMap.set(sdk.skills.Firestorm, { + hand: sdk.skills.hand.Left, + missile: true, + range: 10, + }); + skillMap.set(sdk.skills.OakSage, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.OakSage, + summonType: sdk.summons.type.Spirit, + // condition: () => (typeof Config.SummonSpirit === "string" + // ? Config.SummonSpirit.toLowerCase() === "oak" + // : Config.SummonSpirit === sdk.skills.OakSage), + summonCount: () => 1, + }); + skillMap.set(sdk.skills.SpiritWolf, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.SpiritWolf, + // condition: () => (typeof Config.SummonAnimal === "string" + // ? Config.SummonAnimal.toLowerCase() === "spirit wolf" + // : Config.SummonAnimal === sdk.skills.SpiritWolf), + summonCount: () => Math.min(me.getSkill(sdk.skills.SpiritWolf, sdk.skills.subindex.SoftPoints), 5), + }); + skillMap.set(sdk.skills.Werebear, { + hand: sdk.skills.hand.Right, + range: 1, + duration: () => (40 + (20 * me.getSkill(sdk.skills.Lycanthropy, sdk.skills.subindex.SoftPoints) + 20)), + }); + skillMap.set(sdk.skills.MoltenBoulder, { + hand: sdk.skills.hand.Left, + missile: true, + range: 10, + }); + skillMap.set(sdk.skills.ArcticBlast, { + hand: sdk.skills.hand.Left, + missile: true, + range: () => { + let skLvl = me.getSkill(sdk.skills.ArcticBlast, sdk.skills.subindex.SoftPoints); + let range = Math.floor(((33 + (2 * skLvl)) / 4) * (2 / 3)); + // Druid using this on physical immunes needs the monsters to be within range of hurricane + range > 6 && Config.AttackSkill[5] === sdk.skills.ArcticBlast && (range = 6); + + return range; + }, + }); + skillMap.set(sdk.skills.CarrionVine, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Vine, + // condition: () => (typeof Config.SummonVine === "string" + // ? Config.SummonVine.toLowerCase() === "carion" + // : Config.SummonVine === sdk.skills.CarrionVine), + summonCount: () => 1, + }); + skillMap.set(sdk.skills.FeralRage, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + state: sdk.states.FeralRage, + duration: () => 20, + }); + skillMap.set(sdk.skills.Maul, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + state: sdk.states.Maul, + }); + skillMap.set(sdk.skills.Fissure, { + hand: sdk.skills.hand.Right, + range: 20, + }); + skillMap.set(sdk.skills.CycloneArmor, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.CycloneArmor, + // todo - armor percent like I did for bonearmor + }); + skillMap.set(sdk.skills.HeartofWolverine, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.HeartofWolverine, + summonType: sdk.summons.type.Spirit, + // condition: () => (typeof Config.SummonSpirit === "string" + // ? Config.SummonSpirit.toLowerCase() === "wolverine" + // : Config.SummonSpirit === sdk.skills.HeartofWolverine), + summonCount: () => 1, + }); + skillMap.set(sdk.skills.SummonDireWolf, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.DireWolf, + // condition: () => (typeof Config.SummonAnimal === "string" + // ? Config.SummonAnimal.toLowerCase() === "dire wolf" + // : Config.SummonAnimal === sdk.skills.SummonDireWolf), + summonCount: () => Math.min(me.getSkill(sdk.skills.SummonDireWolf, sdk.skills.subindex.SoftPoints), 3), + }); + skillMap.set(sdk.skills.Rabies, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + state: sdk.states.Rabies, + duration: () => (3.6 + me.getSkill(sdk.skills.Rabies, sdk.skills.subindex.SoftPoints) * 0.4), + }); + skillMap.set(sdk.skills.FireClaws, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.Twister, { + hand: sdk.skills.hand.Left, + missile: true, + range: 5, + }); + skillMap.set(sdk.skills.SolarCreeper, { + hand: sdk.skills.hand.Right, + range: 40, + summonType: sdk.summons.type.Vine, + // condition: () => (typeof Config.SummonVine === "string" + // ? Config.SummonVine.toLowerCase() === "solar" + // : Config.SummonVine === sdk.skills.SolarCreeper), + summonCount: () => 1, + }); + skillMap.set(sdk.skills.Hunger, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.ShockWave, { + hand: sdk.skills.hand.Left, + range: 8, + duration: () => Math.min(1 + me.getSkill(sdk.skills.ShockWave, sdk.skills.subindex.SoftPoints) * 0.6, 10), + }); + skillMap.set(sdk.skills.Volcano, { + hand: sdk.skills.hand.Right, + range: 30, + }); + skillMap.set(sdk.skills.Tornado, { + hand: sdk.skills.hand.Left, + missile: true, + range: 5, + }); + skillMap.set(sdk.skills.SpiritofBarbs, { + hand: sdk.skills.hand.Right, + range: 40, + state: sdk.states.Barbs, + summonType: sdk.summons.type.Spirit, + // condition: () => (typeof Config.SummonSpirit === "string" + // ? Config.SummonSpirit.toLowerCase() === "barbs" + // : Config.SummonSpirit === sdk.skills.SpiritofBarbs), + summonCount: () => 1, + }); + skillMap.set(sdk.skills.SummonGrizzly, { + hand: sdk.skills.hand.Right, + range: 40, + timed: true, + summonType: sdk.summons.type.Grizzly, + // condition: () => (typeof Config.SummonAnimal === "string" + // ? Config.SummonAnimal.toLowerCase() === "grizzly" + // : Config.SummonAnimal === sdk.skills.SummonGrizzly), + summonCount: () => 1, + }); + skillMap.set(sdk.skills.Fury, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.Armageddon, { + hand: sdk.skills.hand.Right, + range: 10, + state: sdk.states.Armageddon, + duration: () => (10 + me.getSkill(sdk.skills.Fissure, sdk.skills.subindex.HardPoints) * 2), + AoE: () => 11, + }); + skillMap.set(sdk.skills.Hurricane, { + hand: sdk.skills.hand.Right, + range: 10, + state: sdk.states.Hurricane, + duration: () => (10 + me.getSkill(sdk.skills.CycloneArmor, sdk.skills.subindex.HardPoints) * 2), + AoE: () => 6, + }); + } + // ~~~ start of assassin skills ~~~ // + { + skillMap.set(sdk.skills.FireBlast, { + hand: sdk.skills.hand.Left, + missile: true, + range: 15, + }); + skillMap.set(sdk.skills.ClawMastery, { + hand: -1, + range: -1, + state: sdk.states.ClawMastery, + }); + skillMap.set(sdk.skills.PsychicHammer, { + hand: sdk.skills.hand.Right, + range: 40, + }); + skillMap.set(sdk.skills.TigerStrike, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.DragonTalon, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.ShockWeb, { + hand: sdk.skills.hand.Left, + range: 15, + AoE: () => { + let baseMissiles = Math.floor((6 + me.getSkill(sdk.skills.ShockWeb, sdk.skills.subindex.SoftPoints)) / 4); + let extraMissiles = Math.floor(me.getSkill(sdk.skills.FireBlast, sdk.skills.subindex.HardPoints) / 3); + return ((((baseMissiles + extraMissiles) / 4) + 1) * (2 / 3)); + }, + }); + skillMap.set(sdk.skills.BladeSentinel, { + hand: sdk.skills.hand.Left, + range: 15, + }); + skillMap.set(sdk.skills.BurstofSpeed, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.BurstofSpeed, + duration: () => (108 + (12 * me.getSkill(sdk.skills.BurstofSpeed, sdk.skills.subindex.SoftPoints))), + condition: () => !Config.UseBoS && !me.inTown, + }); + skillMap.set(sdk.skills.FistsofFire, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.DragonClaw, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.ChargedBoltSentry, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.AssassinTrap, + summonCount: () => 5, + }); + skillMap.set(sdk.skills.WakeofFireSentry, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.AssassinTrap, + summonCount: () => 5, + }); + skillMap.set(sdk.skills.WeaponBlock, { + hand: -1, + range: -1, + state: sdk.states.WeaponBlock, + }); + skillMap.set(sdk.skills.CloakofShadows, { + hand: sdk.skills.hand.Right, + range: 30, + state: sdk.states.CloakofShadows, + duration: () => (7 + me.getSkill(sdk.skills.CloakofShadows, sdk.skills.subindex.SoftPoints)), + }); + skillMap.set(sdk.skills.CobraStrike, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.BladeFury, { + hand: sdk.skills.hand.Left, + range: 15, + }); + skillMap.set(sdk.skills.Fade, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.Fade, + duration: () => (108 + (12 * me.getSkill(sdk.skills.Fade, sdk.skills.subindex.SoftPoints))), + condition: () => Config.UseFade, + }); + skillMap.set(sdk.skills.ShadowWarrior, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.Shadow, + // condition: () => Config.SummonValkyrie, + summonCount: () => 1, + }); + skillMap.set(sdk.skills.ClawsofThunder, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.DragonTail, { + hand: sdk.skills.hand.LeftNoShift, + range: 3, + }); + skillMap.set(sdk.skills.LightningSentry, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.AssassinTrap, + summonCount: () => 5, + }); + skillMap.set(sdk.skills.InfernoSentry, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.AssassinTrap, + summonCount: () => 5, + }); + skillMap.set(sdk.skills.MindBlast, { + hand: sdk.skills.hand.Right, + range: 40, + AoE: () => 2.66, + }); + skillMap.set(sdk.skills.BladesofIce, { + hand: sdk.skills.hand.Left, + range: 3, + }); + skillMap.set(sdk.skills.DragonFlight, { + hand: sdk.skills.hand.Left, + range: 10, + }); + skillMap.set(sdk.skills.DeathSentry, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.AssassinTrap, + summonCount: () => 5, + }); + skillMap.set(sdk.skills.BladeShield, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.BladeShield, + timed: true, + duration: () => (15 + (5 * me.getSkill(sdk.skills.BladeShield, sdk.skills.subindex.SoftPoints))), + condition: () => Config.UseBladeShield, + }); + skillMap.set(sdk.skills.Venom, { + hand: sdk.skills.hand.Right, + range: 1, + state: sdk.states.Venom, + duration: () => (116 + (4 * me.getSkill(sdk.skills.Venom, sdk.skills.subindex.SoftPoints))), + condition: () => Config.UseVenom, + }); + skillMap.set(sdk.skills.ShadowMaster, { + hand: sdk.skills.hand.Right, + range: 30, + summonType: sdk.summons.type.Shadow, + // condition: () => Config.SummonValkyrie, + summonCount: () => 1, + }); + skillMap.set(sdk.skills.RoyalStrike, { + hand: sdk.skills.hand.Right, + range: 3, + }); + } + + const damageTypes = [ + "Physical", "Fire", "Lightning", + "Magic", "Cold", "Poison", "None", + "None", "None", "Physical" + ]; + + /** + * probably an easier way to get tab id from getBaseStat + * @type {{ id: number, skills: number[] }[]} + */ + const skillTabs = Object.keys(sdk.skillTabs) + .map((key) => Object.values(sdk.skillTabs[key])) + .flat(); + + /** + * @constructor + * @param {number} skillId + */ + function Skill (skillId) { + let _skillData = skillMap.get(skillId); + /** @type {number} */ + this.skillId = skillId; + /** @type {number} */ + this.hand = (_skillData.hand || sdk.skills.hand.Right); + /** @type {number} */ + this.state = (_skillData.state || sdk.states.None); + /** @type {() => number} */ + this.summonCount = (_skillData.summonCount || (() => 0)); + /** @type {number} */ + this.summonType = (_skillData.summonType || 0); + /** @type {() => boolean} */ + this.condition = (_skillData.condition || (() => true)); + /** @type {boolean} */ + this.townSkill = (_skillData.townSkill || getBaseStat("skills", skillId, "InTown") === 1); + /** @type {boolean} */ + this.timed = (_skillData.timed || getBaseStat("skills", skillId, "delay") > 0); + /** @type {boolean} */ + this.missleSkill = (_skillData.missile || false); + /** @type {boolean} */ + this.aura = getBaseStat("skills", skillId, "aura") === 1; + /** @type {number} */ + this.charClass = getBaseStat("skills", skillId, "charClass"); + /** @type {number} */ + this.skillTab = (function () { + return skillTabs.find((tab) => tab.skills.includes(skillId)) || { id: -1 }; + })().id; + /** @type {number} */ + this.reqLevel = getBaseStat("skills", skillId, "reqlevel"); + /** @type {number[]} */ + this.preReqs = (function () { + let preReqs = []; + + for (let t = sdk.stats.PreviousSkillLeft; t >= sdk.stats.PreviousSkillRight; t--) { + let preReq = (getBaseStat("skills", skillId, t)); + + if (preReq > sdk.skills.Attack && preReq < sdk.skills.RoyalStrike) { + return preReqs.push(preReq); + } + } + + return preReqs; + })(); + this.damageType = damageTypes[getBaseStat("skills", skillId, "EType")]; + + /** + * @private + * @type {number | () => number} + */ + this._range = (_skillData.range || 1); + /** + * @private + * @type {() => number} + */ + this._AoE = (_skillData.AoE || (() => 0)); + /** + * @private + * @type {() => number} + */ + this._duration = (_skillData.duration || (() => 0)); + /** + * @private + * @type {number} + */ + this._manaCost = Infinity; + /** + * @private + * @type {number} + */ + this._mana = getBaseStat("skills", this.skillId, "mana"); + /** + * @private + * @type {number} + */ + this._minMana = getBaseStat("skills", this.skillId, "minmana"); + /** + * @private + * @type {number} + */ + this._lvlMana = getBaseStat("skills", this.skillId, "lvlmana"); + let effectiveShift = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]; + /** + * @private + * @type {number} + */ + this._manaShift = (effectiveShift[getBaseStat("skills", this.skillId, "manashift")] / 256); + /** + * @private + * @type {number} + */ + this._bestSlot = 0; + /** + * @private + * @type {number} + */ + this._dmg = 0; + /** + * @private + * @type {number} + */ + this._hardPoints = 0; + /** + * @private + * @type {number} + */ + this._softPoints = 0; + /** + * @private + * @type {boolean} + */ + this._checked = false; + } + + /** + * @this Skill + * @returns {number} + */ + Skill.prototype.duration = function () { + return Time.seconds(this._duration()); + }; + + /** + * @this Skill + * @returns {number} + */ + Skill.prototype.manaCost = function () { + if (this._manaCost !== Infinity) return this._manaCost; + if (this.skillId < sdk.skills.MagicArrow) { + return (this._manaCost = 0); + } + let skillLvl = me.getSkill(this.skillId, sdk.skills.subindex.SoftPoints); + if (skillLvl !== this._softPoints) { + this._softPoints = skillLvl; + this._hardPoints = me.getSkill(this.skillId, sdk.skills.subindex.HardPoints); + } + // Decoy wasn't reading from skill bin + if (this.skillId === sdk.skills.Decoy) { + return (this._manaCost = Math.max(19.75 - (0.75 * skillLvl), 1)); + } + let lvlmana = this._lvlMana === 65535 + ? -1 + : this._lvlMana; // Correction for skills that need less mana with levels (kolton) + let ret = Math.max((this._mana + lvlmana * (skillLvl - 1)) * this._manaShift, this._minMana); + return (this._manaCost = ret); + }; + + /** + * @this Skill + * @property {boolean} pvpRange + * @returns {number} + */ + Skill.prototype.range = function (pvpRange = false) { + return typeof this._range === "function" ? this._range(pvpRange) : this._range; + }; + + /** + * @this Skill + * @returns {number} + */ + Skill.prototype.AoE = function () { + return this._AoE(); + }; + + /** + * @this Skill + * @returns {boolean} + */ + Skill.prototype.have = function () { + if (!this.condition()) return false; + if (this._hardPoints > 0) return true; + if (!this._checked) { + this._checked = true; + this._hardPoints = me.getSkill(this.skillId, sdk.skills.subindex.HardPoints); + // this._softPoints = me.getSkill(this.skillId, sdk.skills.subindex.SoftPoints); + } + return this._hardPoints > 0 || (this._softPoints = me.getSkill(this.skillId, sdk.skills.subindex.SoftPoints)) > 0; + }; + + Skill.prototype.reset = function () { + this._manaCost = Infinity; + this._dmg = 0; + this._hardPoints = 0; + this._softPoints = 0; + this._checked = false; + }; + + /** + * @todo Damage calculations, best slot, etc. + */ + + /** @type {Map 0; + }, + + /** @param {ItemUnit} item */ + canEquip: function (item) { + // Not an item or unid + if (!item || item.type !== sdk.unittype.Item || !item.identified) return false; + // Higher requirements + if (item.getStat(sdk.stats.LevelReq) > me.getStat(sdk.stats.Level) + || item.dexreq > me.getStat(sdk.stats.Dexterity) || item.strreq > me.getStat(sdk.stats.Strength)) { + return false; + } + return true; + }, + + /** + * Equips an item and throws away the old equipped item + * @param {ItemUnit} item + * @param {number} bodyLoc + * @returns {boolean} + */ + equip: function (item, bodyLoc) { + if (!this.canEquip(item)) return false; + + // Already equipped in the right slot + if (item.mode === sdk.items.mode.Equipped && item.bodylocation === bodyLoc) return true; + if (item.isInStash && !Town.openStash()) return false; + + for (let i = 0; i < 3; i += 1) { + if (item.toCursor()) { + clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc); + + if (item.bodylocation === bodyLoc) { + if (getCursorType() === 3) { + let cursorItem = Game.getCursorUnit(); + + if (cursorItem) { + if (!Storage.Inventory.CanFit(cursorItem) || !Storage.Inventory.MoveTo(cursorItem)) { + cursorItem.drop(); + } + } + } + + return true; + } + } + } + + return false; + }, + + getEquippedItem: function (bodyLoc) { + let item = me.getItemsEx(-1, sdk.items.mode.Equipped) + .filter(function (item) { + return item.bodylocation === bodyLoc; + }).first(); + + return { + classid: item ? item.classid : -1, + tier: item ? NTIP.GetTier(item) : -1 + }; + }, + + /** @param {ItemUnit} item */ + getBodyLoc: function (item) { + if (!item) return [-1]; + let bodyLoc = item.getBodyLoc(); + + if (bodyLoc.first() === sdk.body.RightArm) { + if (me.barbarian) { + if (!item.strictlyTwoHanded) { + return [sdk.body.RightArm, sdk.body.LeftArm]; + } + } else if (me.assassin) { + if ([sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw].includes(item.itemType)) { + return [sdk.body.RightArm, sdk.body.LeftArm]; + } + } + } + + return bodyLoc; + }, + + /** @param {ItemUnit} item */ + autoEquipCheck: function (item) { + if (!Config.AutoEquip) return true; + + let tier = NTIP.GetTier(item); + let bodyLoc = this.getBodyLoc(item); + + if (tier > 0 && bodyLoc) { + for (let i = 0; i < bodyLoc.length; i += 1) { + // Low tier items shouldn't be kept if they can't be equipped + if (tier > this.getEquippedItem(bodyLoc[i]).tier + && (this.canEquip(item) || !item.getFlag(sdk.items.flags.Identified))) { + return true; + } + } + } + + // Sell/ignore low tier items, keep high tier + if (tier > 0 && tier < 100) return false; + + return true; + }, + + // returns true if the item should be kept+logged, false if not + autoEquip: function () { + if (!Config.AutoEquip) return true; + + function sortEq (a, b) { + if (Item.canEquip(a)) return -1; + if (Item.canEquip(b)) return 1; + + return 0; + } + + let items = me.getItemsEx(-1, sdk.items.mode.inStorage) + .filter(function (item) { + return NTIP.GetTier(item) > 0; + }); + if (!items.length) return false; + + me.cancel(); + + while (items.length > 0) { + items.sort(sortEq); + + let tier = NTIP.GetTier(items[0]); + if ((tier <= 0 || !items[0].isInStorage) && items.shift()) { + continue; + } + let bodyLoc = this.getBodyLoc(items[0]); + + for (let loc of bodyLoc) { + // khalim's will adjustment + const equippedItem = this.getEquippedItem(loc); + if (equippedItem.classid === sdk.items.quest.KhalimsWill) { + continue; + } + if (tier > equippedItem.tier) { + if (!items[0].identified) { + let tome = me.getTome(sdk.items.TomeofIdentify); + + if (tome && tome.getStat(sdk.stats.Quantity) > 0) { + items[0].isInStash && Town.openStash(); + Town.identifyItem(items[0], tome); + } + } + + let gid = items[0].gid; + console.log(items[0].name); + + if (this.equip(items[0], loc)) { + Item.logItem("Equipped", me.getItem(-1, -1, gid)); + } + + break; + } + } + + items.shift(); + } + + return true; + }, + + /** + * @param {ItemUnit} unit + * @param {boolean} logILvl + * @returns {string} + */ + getItemDesc: function (unit, logILvl = true) { + let stringColor = ""; + let desc = unit.description; + + if (!desc) return ""; + desc = desc.split("\n"); + + // Lines are normally in reverse. Add color tags if needed and reverse order. + for (let i = 0; i < desc.length; i += 1) { + // Remove sell value + if (desc[i].includes(getLocaleString(sdk.locale.text.SellValue))) { + desc.splice(i, 1); + + i -= 1; + } else { + // Add color info + if (!desc[i].match(/^(y|ÿ)c/)) { + desc[i] = stringColor + desc[i]; + } + + // Find and store new color info + let index = desc[i].lastIndexOf("ÿc"); + + if (index > -1) { + stringColor = desc[i].substring(index, index + "ÿ".length + 2); + } + } + + desc[i] = desc[i].replace(/(y|ÿ)c([0-9!"+<:;.*])/g, "\\xffc$2"); + } + + if (logILvl && desc[desc.length - 1]) { + desc[desc.length - 1] = desc[desc.length - 1].trim() + " (" + unit.ilvl + ")"; + } + + desc = desc.reverse().join("\n"); + + return desc; + }, + + /** @param {ItemUnit} unit */ + getItemCode: function (unit) { + if (unit === undefined) return ""; + + let code = (() => { + switch (unit.quality) { + case sdk.items.quality.Set: + switch (unit.classid) { + case sdk.items.Sabre: + return "inv9sbu"; + case sdk.items.ShortWarBow: + return "invswbu"; + case sdk.items.Helm: + return "invhlmu"; + case sdk.items.LargeShield: + return "invlrgu"; + case sdk.items.LongSword: + case sdk.items.CrypticSword: + return "invlsdu"; + case sdk.items.SmallShield: + return "invsmlu"; + case sdk.items.Buckler: + return "invbucu"; + case sdk.items.Cap: + return "invcapu"; + case sdk.items.BroadSword: + return "invbsdu"; + case sdk.items.FullHelm: + return "invfhlu"; + case sdk.items.GothicShield: + return "invgtsu"; + case sdk.items.AncientArmor: + case sdk.items.SacredArmor: + return "invaaru"; + case sdk.items.KiteShield: + return "invkitu"; + case sdk.items.TowerShield: + return "invtowu"; + case sdk.items.FullPlateMail: + return "invfulu"; + case sdk.items.MilitaryPick: + return "invmpiu"; + case sdk.items.JaggedStar: + return "invmstu"; + case sdk.items.ColossusBlade: + return "invgsdu"; + case sdk.items.OrnatePlate: + return "invxaru"; + case sdk.items.Cuirass: + case sdk.items.ReinforcedMace: + case sdk.items.Ward: + case sdk.items.SpiredHelm: + return "inv" + unit.code + "s"; + case sdk.items.GrandCrown: + return "invxrnu"; + case sdk.items.ScissorsSuwayyah: + return "invskru"; + case sdk.items.GrimHelm: + case sdk.items.BoneVisage: + return "invbhmu"; + case sdk.items.ElderStaff: + return "invcstu"; + case sdk.items.RoundShield: + return "invxmlu"; + case sdk.items.BoneWand: + return "invbwnu"; + default: + return ""; + } + case sdk.items.quality.Unique: + for (let i = 0; i < 401; i += 1) { + if (unit.code === getBaseStat("uniqueitems", i, 4).trim() + && unit.fname.split("\n").reverse()[0].includes(getLocaleString(getBaseStat("uniqueitems", i, 2)))) { + return getBaseStat("uniqueitems", i, "invfile"); + } + } + return ""; + default: + return ""; + } + })(); + + if (!code) { + // Tiara/Diadem + code = ["ci2", "ci3"].includes(unit.code) + ? unit.code + : (getBaseStat("items", unit.classid, "normcode") || unit.code); + code = code.replace(" ", ""); + [ + sdk.items.type.Ring, sdk.items.type.Amulet, + sdk.items.type.Jewel, sdk.items.type.SmallCharm, + sdk.items.type.LargeCharm, sdk.items.type.GrandCharm + ].includes(unit.itemType) && (code += (unit.gfx + 1)); + } + + return code; + }, + + /** @param {ItemUnit} unit */ + getItemSockets: function (unit) { + let code; + let sockets = unit.sockets; + let subItems = unit.getItemsEx(); + /** @type {ItemUnit[]} */ + let tempArray = []; + + if (subItems.length) { + switch (unit.sizex) { + case 2: + switch (unit.sizey) { + case 3: // 2 x 3 + switch (sockets) { + case 4: + tempArray = [subItems[0], subItems[3], subItems[2], subItems[1]]; + + break; + case 5: + tempArray = [subItems[1], subItems[4], subItems[0], subItems[3], subItems[2]]; + + break; + case 6: + tempArray = [subItems[0], subItems[3], subItems[1], subItems[4], subItems[2], subItems[5]]; + + break; + } + + break; + case 4: // 2 x 4 + switch (sockets) { + case 5: + tempArray = [subItems[1], subItems[4], subItems[0], subItems[3], subItems[2]]; + + break; + case 6: + tempArray = [subItems[0], subItems[3], subItems[1], subItems[4], subItems[2], subItems[5]]; + + break; + } + + break; + } + + break; + } + + if (tempArray.length === 0 && subItems.length > 0) { + tempArray = subItems.slice(0); + } + } + + for (let i = 0; i < sockets; i += 1) { + if (tempArray[i]) { + code = tempArray[i].code; + + if ([ + sdk.items.type.Ring, sdk.items.type.Amulet, + sdk.items.type.Jewel, sdk.items.type.SmallCharm, + sdk.items.type.LargeCharm, sdk.items.type.GrandCharm + ].includes(tempArray[i].itemType)) { + code += (tempArray[i].gfx + 1); + } + } else { + code = "gemsocket"; + } + + tempArray[i] = code; + } + + return tempArray; + }, + + useItemLog: true, // Might be a bit dirty + + /** + * @param {string} action + * @param {ItemUnit} unit + * @param {string} text + */ + logger: function (action, unit, text) { + if (!Config.ItemInfo || !this.useItemLog) return false; + + let desc; + let date = new Date(); + let dateString = "[" + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, -5).replace(/-/g, "/").replace("T", " ") + "]"; + + switch (action) { + case "Sold": + if (Config.ItemInfoQuality.indexOf(unit.quality) === -1) { + return false; + } + + desc = this.getItemDesc(unit).split("\n").join(" | ").replace(/(\\xff|ÿ)c[0-9!"+<:;.*]/gi, "").trim(); + + break; + case "Kept": + case "Field Kept": + case "Runeword Kept": + case "Cubing Kept": + case "Shopped": + case "Gambled": + case "Dropped": + desc = this.getItemDesc(unit).split("\n").join(" | ").replace(/(\\xff|ÿ)c[0-9!"+<:;.*]|\/|\\/gi, "").trim(); + + break; + case "No room for": + desc = unit.name; + + break; + default: + desc = unit.fname.split("\n").reverse().join(" ").replace(/(\\xff|ÿ)c[0-9!"+<:;.*]|\/|\\/gi, "").trim(); + + break; + } + + return FileAction.append( + "logs/ItemLog.txt", + dateString + " <" + me.profile + "> <" + action + "> (" + Item.qualityToName(unit.quality) + ") " + + desc + (text ? " {" + text + "}" : "") + "\n" + ); + }, + + /** + * Log kept item stats in the manager. + * @param {string} action + * @param {ItemUnit} unit + * @param {string} keptLine + */ + logItem: function (action, unit, keptLine) { + if (!this.useItemLog) return false; + if (!Config.LogKeys && ["pk1", "pk2", "pk3"].includes(unit.code)) return false; + if (!Config.LogOrgans && ["dhn", "bey", "mbr"].includes(unit.code)) return false; + if (!Config.LogLowRunes && ["r01", "r02", "r03", "r04", "r05", "r06", "r07", "r08", "r09", "r10", "r11", "r12", "r13", "r14"].includes(unit.code)) return false; + if (!Config.LogMiddleRunes && ["r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23"].includes(unit.code)) return false; + if (!Config.LogHighRunes && ["r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", "r32", "r33"].includes(unit.code)) return false; + if (!Config.LogLowGems && ["gcv", "gcy", "gcb", "gcg", "gcr", "gcw", "skc", "gfv", "gfy", "gfb", "gfg", "gfr", "gfw", "skf", "gsv", "gsy", "gsb", "gsg", "gsr", "gsw", "sku"].includes(unit.code)) return false; + if (!Config.LogHighGems && ["gzv", "gly", "glb", "glg", "glr", "glw", "skl", "gpv", "gpy", "gpb", "gpg", "gpr", "gpw", "skz"].includes(unit.code)) return false; + + for (let skip of Config.SkipLogging) { + if (skip === unit.classid || skip === unit.code) return false; + } + + let lastArea; + const name = unit.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<:;.*]|\/|\\/g, "").trim(); + const color = (unit.getColor() || -1); + const code = this.getItemCode(unit); + const sock = unit.getItem(); + let desc = this.getItemDesc(unit); + + if (action.match("kept", "i")) { + lastArea = DataFile.getStats().lastArea; + lastArea && (desc += ("\n\\xffc0Area: " + lastArea)); + } + + if (sock) { + do { + if (sock.itemType === sdk.items.type.Jewel) { + desc += "\n\n"; + desc += this.getItemDesc(sock); + } + } while (sock.getNext()); + } + + keptLine && (desc += ("\n\\xffc0Line: " + keptLine)); + desc += "$" + (unit.ethereal ? ":eth" : ""); + const formattedDate = new Date().dateStamp().replace(/\//g, "-"); + + const itemObj = { + title: formattedDate + " " + action + " " + name, + description: desc, + image: code, + textColor: unit.quality, + itemColor: color, + header: "", + sockets: this.getItemSockets(unit) + }; + + D2Bot.printToItemLog(itemObj); + + return true; + }, + + /** + * skip low items: MuleLogger + * @param {number} id + */ + skipItem: function (id) { + return [ + sdk.items.HandAxe, sdk.items.Wand, sdk.items.Club, + sdk.items.ShortSword, sdk.items.Javelin, + sdk.items.ShortStaff, sdk.items.Katar, + sdk.items.Buckler, sdk.items.StaminaPotion, + sdk.items.AntidotePotion, sdk.items.RejuvenationPotion, + sdk.items.FullRejuvenationPotion, + sdk.items.ThawingPotion, sdk.items.TomeofTownPortal, + sdk.items.TomeofIdentify, sdk.items.ScrollofIdentify, + sdk.items.ScrollofTownPortal, sdk.items.Key, + sdk.items.MinorHealingPotion, sdk.items.LightHealingPotion, + sdk.items.HealingPotion, sdk.items.GreaterHealingPotion, + sdk.items.SuperHealingPotion, sdk.items.MinorManaPotion, + sdk.items.LightManaPotion, sdk.items.ManaPotion, + sdk.items.GreaterManaPotion, sdk.items.SuperManaPotion + ].includes(id); + }, +}; diff --git a/d2bs/kolbot/libs/core/Loader.js b/d2bs/kolbot/libs/core/Loader.js new file mode 100644 index 000000000..b5b19f1ad --- /dev/null +++ b/d2bs/kolbot/libs/core/Loader.js @@ -0,0 +1,462 @@ +/** +* @filename Loader.js +* @author kolton, theBGuy +* @desc script loader, based on mBot's Sequencer.js +* +*/ + + +/** + * @constructor + * @param {function(): boolean} action + * @param {RunnableOptions} [options] + */ +function Runnable (action, options = {}) { + this.action = action; + this.startArea = options.hasOwnProperty("startArea") ? options.startArea : null; + this.setup = options.hasOwnProperty("setup") ? options.setup : null; + this.preAction = options.hasOwnProperty("preAction") + ? options.preAction + : function chores () { + // TODO: We need to do a dry-run of chores to actually determine if we need it or not + if (getTickCount() - Town.lastChores > Time.minutes(1)) { + Town.doChores(); + } + }; + this.postAction = options.hasOwnProperty("postAction") ? options.postAction : null; + this.cleanup = options.hasOwnProperty("cleanup") ? options.cleanup : null; + this.forceTown = options.hasOwnProperty("forceTown") ? options.forceTown : false; + this.bossid = options.hasOwnProperty("bossid") ? options.bossid : null; +} + +const Loader = { + /** @type {string[]} */ + fileList: [], + /** @type {string[]} */ + scriptList: [], + scriptIndex: -1, + skipTown: ["Test", "Follower"], + firstScriptAct: -1, + /** @type {GlobalScript | Runnable | null} */ + currentScript: null, + /** @type {Runnable | null} */ + nextScript: null, + /** @type {Set} */ + doneScripts: new Set(), + + init: function () { + this.getScripts(); + this.loadScripts(); + }, + + getScripts: function () { + /** @type {string[]} */ + let fileList = dopen("libs/scripts/").getFiles(); + + for (let i = 0; i < fileList.length; i += 1) { + if (fileList[i].endsWith(".js")) { + this.fileList.push(fileList[i].substring(0, fileList[i].indexOf(".js"))); + } + } + }, + + /** @param {ScriptContext} ctx */ + _runCurrent: function (ctx) { + return this.currentScript instanceof Runnable + ? this.currentScript.action(ctx) + : this.currentScript(ctx); + }, + + /** + * @see http://stackoverflow.com/questions/728360/copying-an-object-in-javascript#answer-728694 + * @param {Date | Array | Object} obj + * @returns + */ + clone: function (obj) { + let copy; + + // Handle the 3 simple types, and null or undefined + if (null === obj || "object" !== typeof obj) { + return obj; + } + + // Handle Date + if (obj instanceof Date) { + copy = new Date(); + copy.setTime(obj.getTime()); + + return copy; + } + + // Handle Array + if (obj instanceof Array) { + copy = []; + + for (let i = 0; i < obj.length; i += 1) { + copy[i] = this.clone(obj[i]); + } + + return copy; + } + + // Handle Object + if (obj instanceof Object) { + copy = {}; + + for (let attr in obj) { + if (obj.hasOwnProperty(attr)) { + copy[attr] = this.clone(obj[attr]); + } + } + + return copy; + } + + throw new Error("Unable to copy obj! Its type isn't supported."); + }, + + copy: function (from, to) { + for (let i in from) { + if (from.hasOwnProperty(i)) { + to[i] = this.clone(from[i]); + } + } + }, + + loadScripts: function () { + let reconfiguration, unmodifiedConfig = {}; + + this.copy(Config, unmodifiedConfig); + + if (!this.fileList.length) { + showConsole(); + + throw new Error("You don't have any valid scripts in bots folder."); + } + + for (let s in Scripts) { + if (Scripts.hasOwnProperty(s) && Scripts[s]) { + Loader.scriptList.push(s); + } + } + + // handle getting cube here instead of from Cubing.doCubing + if (Config.Cubing && !me.getItem(sdk.quest.item.Cube) && me.accessToAct(2)) { + // we can actually get the cube - fixes bug causing level 1's to crash + Loader.runScript("GetCube"); + } + + for (Loader.scriptIndex = 0; Loader.scriptIndex < Loader.scriptList.length; Loader.scriptIndex++) { + const ctx = {}; + const script = this.scriptList[this.scriptIndex]; + + if (this.fileList.indexOf(script) === -1) { + if (FileTools.exists("scripts/" + script + ".js")) { + console.warn( + "ÿc1Something went wrong in loader, file exists in folder but didn't get included during init process. " + + "Lets ignore the error and continue to include the script by name instead" + ); + } else { + Misc.errorReport("ÿc1Script " + script + " doesn't exist."); + + continue; + } + } + + if (!include("scripts/" + script + ".js")) { + Misc.errorReport("Failed to include script: " + script); + continue; + } + + Loader.currentScript = global[script]; + + // Preload the next script + if (Loader.scriptIndex < Loader.scriptList.length - 1) { + let nextScript = this.scriptList[Loader.scriptIndex + 1]; + if (include("scripts/" + nextScript + ".js")) { + if (global[nextScript] instanceof Runnable && global[nextScript].startArea) { + Loader.nextScript = global[nextScript]; + } + } + } + + if (isIncluded("scripts/" + script + ".js")) { + try { + if (Loader.currentScript instanceof Runnable) { + const { startArea, bossid, preAction, setup } = Loader.currentScript; + + if (startArea && Loader.scriptIndex === 0) { + Loader.firstScriptAct = sdk.areas.actOf(startArea); + } + + if (bossid && Attack.haveKilled(bossid)) { + console.log("ÿc2Skipping script: ÿc9" + script + " ÿc2- Boss already killed."); + continue; + } + + if (setup && typeof setup === "function") { + setup(ctx); + } + + if (preAction && typeof preAction === "function") { + preAction(ctx); + } + + if (startArea && me.inArea(startArea)) { + this.skipTown.push(script); + } + } else if (typeof (Loader.currentScript) !== "function") { + throw new Error( + "Invalid script function name. " + + "Typeof: " + typeof (Loader.currentScript) + + " Name: " + script + ); + } + + + if (this.skipTown.includes(script) || Town.goToTown()) { + console.log("ÿc2Starting script: ÿc9" + script); + Messaging.sendToScript("threads/toolsthread.js", JSON.stringify({ currScript: script })); + reconfiguration = typeof Scripts[script] === "object"; + + if (reconfiguration) { + console.log("ÿc2Copying Config properties from " + script + " object."); + this.copy(Scripts[script], Config); + } + + let tick = getTickCount(); + let exp = me.getStat(sdk.stats.Experience); + + if (me.inTown) { + if (Config.StackThawingPots.enabled) { + Town.buyPots(Config.StackThawingPots.quantity, sdk.items.ThawingPotion, true); + } + if (Config.StackAntidotePots.enabled) { + Town.buyPots(Config.StackAntidotePots.quantity, sdk.items.AntidotePotion, true); + } + if (Config.StackStaminaPots.enabled) { + Town.buyPots(Config.StackStaminaPots.quantity, sdk.items.StaminaPotion, true); + } + } + + // kinda hacky, but faster for mfhelpers to stop + if (Config.MFLeader && Config.PublicMode && ["Diablo", "Baal"].includes(script)) { + say("nextup " + script); + } + + if (Loader._runCurrent(ctx)) { + let gain = Math.max(me.getStat(sdk.stats.Experience) - exp, 0); + let duration = Time.elapsed(tick); + console.log( + "ÿc7" + script + " :: ÿc0Complete\n" + + "ÿc2 Statistics:\n" + + "ÿc7 - Duration: ÿc0" + (Time.format(duration)) + "\n" + + "ÿc7 - Experience Gained: ÿc0" + gain + "\n" + + "ÿc7 - Exp/minute: ÿc0" + (gain / (duration / 60000)).toFixed(2) + ); + this.doneScripts.add(script); + + if (Loader.currentScript instanceof Runnable) { + const { postAction } = Loader.currentScript; + + if (postAction && typeof postAction === "function") { + postAction(ctx); + } + } + } + } + } catch (error) { + if (!(error instanceof ScriptError)) { + Misc.errorReport(error, script); + } else { + console.error(error); + } + } finally { + // Dont run for last script as that will clear everything anyway + if (this.scriptIndex < this.scriptList.length) { + // run cleanup if applicable + if (Loader.currentScript instanceof Runnable) { + if (Loader.currentScript.cleanup && typeof Loader.currentScript.cleanup === "function") { + Loader.currentScript.cleanup(ctx); + } + } + // remove script function from global scope, so it can be cleared by GC + delete global[script]; + Loader.currentScript = null; + Loader.nextScript = null; + } + + if (reconfiguration) { + console.log("ÿc2Reverting back unmodified config properties."); + this.copy(unmodifiedConfig, Config); + } + } + } + } + + // return to first script town + if (Loader.firstScriptAct > -1) { + let _act = [2, 3].includes(Loader.firstScriptAct) ? 1 : Loader.firstScriptAct; + Town.goToTown(_act); + } + }, + + /** @type {string[]} */ + tempList: [], + + /** + * @param {string} script + * @param {Partial | function(): any} configOverride + * @returns {boolean} + */ + runScript: function (script, configOverride) { + let reconfiguration, unmodifiedConfig = {}; + let failed = false; + let mainScript = this.scriptName(); + + function buildScriptMsg () { + let str = "ÿc9" + mainScript + " ÿc0:: "; + + if (Loader.tempList.length && Loader.tempList[0] !== mainScript) { + Loader.tempList.forEach(s => str += "ÿc9" + s + " ÿc0:: "); + } + + return str; + } + + this.copy(Config, unmodifiedConfig); + + if (!include("scripts/" + script + ".js")) { + Misc.errorReport("Failed to include script: " + script); + + return false; + } + + + if (isIncluded("scripts/" + script + ".js")) { + const ctx = { + _parent: Loader.currentScript + }; + Loader.currentScript = global[script]; + + try { + if (Loader.currentScript instanceof Runnable) { + const { startArea, bossid, preAction, setup } = Loader.currentScript; + + if (startArea && me.inArea(startArea)) { + Loader.skipTown.push(script); + } + + if (bossid && Attack.haveKilled(bossid)) { + console.log("ÿc2Skipping script: ÿc9" + script + " ÿc2- Boss already killed."); + return true; + } + + if (setup && typeof setup === "function") { + setup(ctx); + } + + if (preAction && typeof preAction === "function") { + preAction(ctx); + } + } else if (typeof (Loader.currentScript) !== "function") { + throw new Error("Invalid script function name"); + } + + if (this.skipTown.includes(script) || Town.goToTown()) { + let mainScriptStr = (mainScript !== script ? buildScriptMsg() : ""); + this.tempList.push(script); + console.log(mainScriptStr + "ÿc2Starting script: ÿc9" + script); + Messaging.sendToScript("threads/toolsthread.js", JSON.stringify({ currScript: script })); + + reconfiguration = typeof Scripts[script] === "object"; + + if (reconfiguration) { + console.log("ÿc2Copying Config properties from " + script + " object."); + this.copy(Scripts[script], Config); + } + + if (typeof configOverride === "function") { + reconfiguration = true; + configOverride(); + } else if (typeof configOverride === "object") { + reconfiguration = true; + this.copy(configOverride, Config); + } + + let tick = getTickCount(); + let exp = me.getStat(sdk.stats.Experience); + + if (Loader._runCurrent(ctx)) { + console.log( + mainScriptStr + "ÿc7" + script + + " :: ÿc0Complete ÿc0- ÿc7Duration: ÿc0" + (Time.format(getTickCount() - tick)) + ); + let gain = Math.max(me.getStat(sdk.stats.Experience) - exp, 0); + let duration = Time.elapsed(tick); + console.log( + mainScriptStr + "ÿc7" + script + " :: ÿc0Complete\n" + + "ÿc2 Statistics:\n" + + "ÿc7 - Duration: ÿc0" + (Time.format(duration)) + "\n" + + "ÿc7 - Experience Gained: ÿc0" + gain + "\n" + + "ÿc7 - Exp/minute: ÿc0" + (gain / (duration / 60000)).toFixed(2) + ); + this.doneScripts.add(script); + + if (Loader.currentScript instanceof Runnable) { + const { postAction } = Loader.currentScript; + + if (postAction && typeof postAction === "function") { + postAction(ctx); + } + } + } + } + } catch (error) { + if (!(error instanceof ScriptError)) { + Misc.errorReport(error, script); + } + failed = true; + } finally { + // Dont run for last script as that will clear everything anyway + if (this.scriptIndex < this.scriptList.length) { + // remove script function from global scope, so it can be cleared by GC + delete global[script]; + } else if (this.tempList.length) { + delete global[script]; + } + + // run cleanup if applicable + if (Loader.currentScript instanceof Runnable) { + if (Loader.currentScript.cleanup && typeof Loader.currentScript.cleanup === "function") { + Loader.currentScript.cleanup(ctx); + } + } + Loader.currentScript = ctx._parent; + Loader.tempList.pop(); + + if (reconfiguration) { + console.log("ÿc2Reverting back unmodified config properties."); + this.copy(unmodifiedConfig, Config); + } + } + } + + return !failed; + }, + + /** + * Get script name by index + * @param {number} [offset] + * @returns {string} + */ + scriptName: function (offset = 0) { + let index = this.scriptIndex + offset; + + if (index >= 0 && index < this.scriptList.length) { + return this.scriptList[index]; + } + + return ""; + } +}; diff --git a/d2bs/kolbot/libs/core/Me.js b/d2bs/kolbot/libs/core/Me.js new file mode 100644 index 000000000..71c04ae57 --- /dev/null +++ b/d2bs/kolbot/libs/core/Me.js @@ -0,0 +1,1413 @@ +/** +* @filename Me.js +* @author theBGuy +* @desc 'me' prototypes +* +*/ + +// Ensure these are in polyfill.js +!isIncluded("Polyfill.js") && include("Polyfill.js"); +/** @global */ +const Me = me; + +/** + * @desciption Set me.runwalk to 0 (walk) + * @returns {void} + */ +me.walk = function () { + me.runwalk = sdk.player.move.Walk; +}; + +/** + * @desciption Set me.runwalk to 1 (run) + * @returns {void} + */ +me.run = function () { + me.runwalk = sdk.player.move.Run; +}; + +/** + * @description Calling me.ping can bug sometimes so check if game is in ready state. + * - Single-Player returns static ping of 25. + * - Game not ready returns ping of 250 + * - ping < 10 returns 50 + * @returns {number} pingDelay + */ +me.getPingDelay = function () { + // single-player + if (!me.gameserverip) return 25; + let pingDelay = me.gameReady ? me.ping : 250; + pingDelay < 10 && (pingDelay = 50); + return pingDelay; +}; + +/** + * @description Find an item by classid, mode, loc, quality + * @param {number} id + * @param {number} [mode] + * @param {number} [loc] + * @param {number} [quality] + * @returns {ItemUnit | false} + */ +me.findItem = function (id = -1, mode = -1, loc = -1, quality = -1) { + let item = me.getItem(id, mode); + + if (item) { + do { + if ((loc === -1 || item.location === loc) && (quality === -1 || item.quality === quality)) { + return item; + } + } while (item.getNext()); + } + + return false; +}; + +me.findItems = function (id = -1, mode = -1, loc = false) { + let list = []; + let item = me.getItem(id, mode); + + if (item) { + do { + if (!loc || item.location === loc) { + list.push(copyUnit(item)); + } + } while (item.getNext()); + } + + return list; +}; + +me.cancelUIFlags = function () { + while (!me.gameReady) { + delay(3); + } + + const flags = [ + sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, + sdk.uiflags.SkillWindow, sdk.uiflags.NPCMenu, + sdk.uiflags.Waypoint, sdk.uiflags.Party, + sdk.uiflags.Shop, sdk.uiflags.Quest, + sdk.uiflags.Stash, sdk.uiflags.Cube, + sdk.uiflags.KeytotheCairnStonesScreen, sdk.uiflags.SubmitItem + ]; + + for (let i = 0; i < flags.length; i++) { + if (getUIFlag(flags[i]) && me.cancel()) { + delay(250); + i = 0; // Reset + } + } +}; + +/** + * @param {number} slot - 0 (Primary) or 1 (Secondary) + * @returns {boolean} + */ +me.switchWeapons = function (slot) { + if (this.gametype === sdk.game.gametype.Classic + || (slot !== undefined && this.weaponswitch === slot)) { + return true; + } + + while (!me.gameReady) { + delay(25); + } + + let originalSlot = this.weaponswitch; + let switched = false; + /** @param {number[]} bytes */ + const packetHandler = function (bytes) { + return bytes.length > 0 + && bytes[0] === sdk.packets.recv.WeaponSwitch && (switched = true) && false; // false to not block + }; + try { + addEventListener("gamepacket", packetHandler); + + for (let i = 0; i < 10; i += 1) { + for (let j = 10; --j && me.idle;) { + delay(3); + } + if (me.mode === sdk.player.mode.SkillActionSequence) { + while (me.mode === sdk.player.mode.SkillActionSequence) { + delay(3); + } + } + + i > 0 && delay(10); + !switched && sendPacket(1, sdk.packets.send.SwapWeapon); // Swap weapons + + let tick = getTickCount(); + while (getTickCount() - tick < 300) { + if (switched || originalSlot !== me.weaponswitch) { + delay(50); + return true; + } + + delay(3); + } + } + } finally { + removeEventListener("gamepacket", packetHandler); + } + + return false; +}; + +// Returns the number of frames needed to cast a given skill at a given FCR for a given char. +me.castingFrames = function (skillId, fcr, charClass) { + if (skillId === undefined) return 0; + + fcr === undefined && (fcr = me.FCR); + charClass === undefined && (charClass = this.classid); + + // https://diablo.fandom.com/wiki/Faster_Cast_Rate + let effectiveFCR = Math.min(75, Math.floor(fcr * 120 / (fcr + 120)) | 0); + let isLightning = skillId === sdk.skills.Lightning || skillId === sdk.skills.ChainLightning; + let baseCastRate = [20, isLightning ? 19 : 14, 16, 16, 14, 15, 17][charClass]; + let animationSpeed = { + normal: 256, + human: 208, + wolf: 229, + bear: 228 + }[charClass === sdk.player.class.Druid ? (me.getState(sdk.states.Wolf) || me.getState(sdk.states.Bear)) : "normal"]; + return Math.ceil( + 256 * baseCastRate / Math.floor(animationSpeed * (100 + effectiveFCR) / 100) - (isLightning ? 0 : 1) + ); +}; + +// Returns the duration in seconds needed to cast a given skill at a given FCR for a given char. +me.castingDuration = function (skillId, fcr = me.FCR, charClass = me.classid) { + return (me.castingFrames(skillId, fcr, charClass) / 25); +}; + +me.getWeaponQuantity = function (weaponLoc = sdk.body.RightArm) { + let currItem = me.getItemsEx(-1, sdk.items.mode.Equipped) + .filter(function (i) { + return i.bodylocation === weaponLoc; + }) + .first(); + return !!currItem ? currItem.getStat(sdk.stats.Quantity) : 0; +}; + +me.clearBelt = function () { + let item = me.getItem(-1, sdk.items.mode.inBelt); + let clearList = []; + + if (item) { + do { + switch (item.itemType) { + case sdk.items.type.HealingPotion: + if (Config.BeltColumn[item.x % 4] !== "hp") { + clearList.push(copyUnit(item)); + } + + break; + case sdk.items.type.ManaPotion: + if (Config.BeltColumn[item.x % 4] !== "mp") { + clearList.push(copyUnit(item)); + } + + break; + case sdk.items.type.RejuvPotion: + if (Config.BeltColumn[item.x % 4] !== "rv") { + clearList.push(copyUnit(item)); + } + + break; + case sdk.items.type.StaminaPotion: + case sdk.items.type.AntidotePotion: + case sdk.items.type.ThawingPotion: + clearList.push(copyUnit(item)); + } + } while (item.getNext()); + + if (clearList.length > 0 && me.inShop) { + console.debug("Currently inShop, canceling UI flags to clear belt"); + me.cancelUIFlags(); + } + + while (clearList.length > 0) { + let pot = clearList.shift(); + (Storage.Inventory.CanFit(pot) && Storage.Inventory.MoveTo(pot)) || pot.interact(); + delay(200); + } + } + + return true; +}; + +me.cleanUpInvoPotions = function (beltSize) { + beltSize === undefined && (beltSize = Storage.BeltSize()); + const beltMax = (beltSize * 4); + /** + * belt 4x4 locations + * 12 13 14 15 + * 8 9 10 11 + * 4 5 6 7 + * 0 1 2 3 + */ + const beltCapRef = [(0 + beltMax), (1 + beltMax), (2 + beltMax), (3 + beltMax)]; + // check if we have empty belt slots + const needCleanup = Storage.Belt.checkColumns(beltSize).some(slot => slot > 0); + + if (needCleanup) { + const potsInInventory = me.getItemsEx() + .filter(function (p) { + return p.isInInventory + && [ + sdk.items.type.HealingPotion, + sdk.items.type.ManaPotion, + sdk.items.type.RejuvPotion + ].includes(p.itemType); + }) + .sort(function (a, b) { + return a.itemType - b.itemType; + }); + + if (potsInInventory.length > 0 && Config.DebugMode.Town) { + console.debug("We have potions in our invo, put them in belt before we perform townchicken check"); + } + // Start interating over all the pots we have in our inventory + beltSize > 1 && potsInInventory.forEach(function (p) { + let moved = false; + // get free space in each slot of our belt + let freeSpace = Storage.Belt.checkColumns(beltSize); + for (let i = 0; i < 4 && !moved; i += 1) { + // checking that current potion matches what we want in our belt + if (freeSpace[i] > 0 && p.code && p.code.startsWith(Config.BeltColumn[i])) { + // Pick up the potion and put it in belt if the column is empty, and we don't have any other columns empty + // prevents shift-clicking potion into wrong column + if (freeSpace[i] === beltSize || freeSpace.some((spot) => spot === beltSize)) { + const x = freeSpace[i] === beltSize + ? i + : (beltCapRef[i] - (freeSpace[i] * 4)); + Packet.placeInBelt(p, x); + } else { + clickItemAndWait(sdk.clicktypes.click.item.ShiftLeft, p.x, p.y, p.location); + } + Misc.poll(function () { + return !me.itemoncursor; + }, 300, 30); + moved = Storage.Belt.checkColumns(beltSize)[i] === freeSpace[i] - 1; + } + Cubing.cursorCheck(); + } + }); + } + + return true; +}; + +me.needPotions = function () { + // we aren't using MinColumn if none of the values are set + if (!Config.MinColumn.some(el => el > 0)) return false; + // no hp pots or mp pots in Config.BeltColumn (who uses only rejuv pots?) + if (!Config.BeltColumn.some(el => ["hp", "mp"].includes(el))) return false; + + // Start + if (me.charlvl > 2 && me.gold > 1000) { + const pots = { + hp: [], + mp: [], + }; + me.getItemsEx(-1, sdk.items.mode.inBelt) + .filter(p => [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion].includes(p.itemType) && p.x < 4) + .forEach(p => { + if (p.itemType === sdk.items.type.HealingPotion) { + pots.hp.push(p); + } else if (p.itemType === sdk.items.type.ManaPotion) { + pots.mp.push(p); + } + }); + + // quick check + if ((Config.BeltColumn.includes("hp") && !pots.hp.length) + || (Config.BeltColumn.includes("mp") && !pots.mp.length)) { + return true; + } + + // if we have no belt what should qualify is to go to town at this point? + // we've confirmed having at least some potions in the above check + // if (!me.inTown && Storage.BeltSize() === 1) return false; + + // should we check the actual amount in the column? + // For now just keeping the way it was and checking if a column is empty + for (let i = 0; i < 4; i += 1) { + if (Config.MinColumn[i] <= 0) { + continue; + } + + switch (Config.BeltColumn[i]) { + case "hp": + if (!pots.hp.some(p => p.x === i)) { + console.debug("Column: " + (i + 1) + " needs hp pots"); + return true; + } + break; + case "mp": + if (!pots.mp.some(p => p.x === i)) { + console.debug("Column: " + (i + 1) + " needs mp pots"); + return true; + } + break; + } + } + } + + return false; +}; + +me.needBeltPots = function () { + // we aren't using MinColumn if none of the values are set + if (!Config.MinColumn.some(el => el > 0)) return false; + // no hp pots or mp pots in Config.BeltColumn (who uses only rejuv pots?) + if (!Config.BeltColumn.some(el => ["hp", "mp"].includes(el))) return false; + + // Start + if (me.charlvl > 2 && me.gold > 1000) { + let pots = { hp: [], mp: [], }; + const beltSize = Storage.BeltSize(); + + // only run this bit if we aren't wearing a belt for now + beltSize === 1 && me.cleanUpInvoPotions(beltSize); + // now check what's in our belt + me.getItemsEx(-1, sdk.items.mode.inBelt) + .filter(function (p) { + return p.x < 4 + && [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion].includes(p.itemType); + }) + .forEach(function (p) { + if (p.itemType === sdk.items.type.HealingPotion) { + pots.hp.push(copyUnit(p)); + } else if (p.itemType === sdk.items.type.ManaPotion) { + pots.mp.push(copyUnit(p)); + } + }); + + // quick check + if ((Config.BeltColumn.includes("hp") && !pots.hp.length) + || (Config.BeltColumn.includes("mp") && !pots.mp.length)) { + return true; + } + + // should we check the actual amount in the column? + // For now just keeping the way it was and checking if a column is empty + for (let i = 0; i < 4; i += 1) { + if (Config.MinColumn[i] <= 0) { + continue; + } + + switch (Config.BeltColumn[i]) { + case "hp": + if (!pots.hp.some(p => p.x === i)) { + console.debug("Column: " + (i + 1) + " needs hp pots"); + return true; + } + break; + case "mp": + if (!pots.mp.some(p => p.x === i)) { + console.debug("Column: " + (i + 1) + " needs mp pots"); + return true; + } + break; + } + } + } + + return false; +}; + +me.needBufferPots = function () { + // not using buffers + if (Config.HPBuffer < 0 && Config.MPBuffer < 0) return false; + + // Start + if (me.charlvl > 2 && me.gold > 1000) { + const pots = { hp: 0, mp: 0, }; + const beltSize = Storage.BeltSize(); + + // only run this bit if we aren't wearing a belt for now + beltSize === 1 && me.cleanUpInvoPotions(beltSize); + // now check what's in our belt + me.getItemsEx() + .filter(function (p) { + return p.isInInventory + && [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion].includes(p.itemType); + }) + .forEach(function (p) { + if (p.itemType === sdk.items.type.HealingPotion) { + pots.hp++; + } else if (p.itemType === sdk.items.type.ManaPotion) { + pots.mp++; + } + }); + + return (pots.mp < Config.MPBuffer || pots.hp < Config.HPBuffer); + } + + return false; +}; + +// me.needPotions = function () { +// return me.needBeltPots() || me.needBufferPots(); +// }; + +/** @returns {ItemUnit | null} */ +me.getTpTool = function () { + const items = me.getItemsEx(-1, sdk.items.mode.inStorage) + .filter(function (item) { + return item.isInInventory + && [ + sdk.items.ScrollofTownPortal, + sdk.items.TomeofTownPortal + ].includes(item.classid); + }); + if (!items.length) return null; + let tome = items.find(function (i) { + return i.classid === sdk.items.TomeofTownPortal && i.getStat(sdk.stats.Quantity) > 0; + }); + if (tome) return tome; + let scroll = items.find(function (i) { + return i.classid === sdk.items.ScrollofTownPortal; + }); + return scroll ? scroll : null; +}; + +/** @returns {ItemUnit | null} */ +me.getIdTool = function () { + const items = me.getItemsEx() + .filter(function (i) { + return i.isInInventory + && [ + sdk.items.ScrollofIdentify, + sdk.items.TomeofIdentify + ].includes(i.classid); + }); + if (!items.length) return null; + let tome = items.find(function (i) { + return i.classid === sdk.items.TomeofIdentify && i.getStat(sdk.stats.Quantity) > 0; + }); + if (tome) return tome; + let scroll = items.find(function (i) { + return i.classid === sdk.items.ScrollofIdentify; + }); + return scroll ? scroll : null; +}; + +/** @returns {boolean} */ +me.canTpToTown = function () { + // can't tp if dead + if (me.dead) return false; + let badAreas = [ + sdk.areas.RogueEncampment, sdk.areas.LutGholein, sdk.areas.KurastDocktown, + sdk.areas.PandemoniumFortress, sdk.areas.Harrogath, sdk.areas.ArreatSummit, sdk.areas.UberTristram + ]; + // can't tp from town or Uber Trist, and shouldn't tp from arreat summit + if (badAreas.includes(me.area)) return false; + // If we made it this far, we can only tp if we even have a tp + return !!me.getTpTool(); +}; + +/** + * @description Check if healing is needed, based on character config + * @returns {boolean} + */ +me.needHealing = function () { + if (me.hpPercent <= Config.HealHP || me.mpPercent <= Config.HealMP) return true; + if (!Config.HealStatus) return false; + // Status effects + return ([ + sdk.states.Poison, + sdk.states.AmplifyDamage, + sdk.states.Frozen, + sdk.states.Weaken, + sdk.states.Decrepify, + sdk.states.LowerResist + ].some(function (state) { + return me.getState(state); + })); +}; + +/** + * @description Check if stashing is needed, based on character config + * @returns {boolean} + */ +me.needStash = function () { + if (Config.StashGold + && me.getStat(sdk.stats.Gold) >= Config.StashGold + && me.getStat(sdk.stats.GoldBank) < 25e5) { + return true; + } + + return (Storage.Inventory.Compare(Config.Inventory) || []) + .some(function (item) { + return Storage.Stash.CanFit(item); + }); +}; + +/** + * @description Check if reviving merc is needed, based on character config + * @returns {boolean} + */ +me.needMerc = function () { + if (me.classic || !Config.UseMerc || me.gold < me.mercrevivecost || me.mercrevivecost === 0) { + return false; + } + + Misc.poll(function () { + return me.gameReady; + }, 1000, 100); + // me.getMerc() might return null if called right after taking a portal, that's why there's retry attempts + for (let i = 0; i < 3; i += 1) { + let merc = me.getMerc(); + + if (!!merc && !merc.dead) { + return false; + } + + delay(100); + } + + // In case we never had a merc and Config.UseMerc is still set to true for some odd reason + return true; +}; + +me.needRepair = function () { + const repairAction = []; + if (getInteractedNPC() && !getUIFlag(sdk.uiflags.Shop)) { + console.debug("Checking need repair: Currently at NPC"); + // fix crash with d2bs + me.cancel(); + } + const canAfford = me.gold >= me.getRepairCost(); + const quiverType = { bow: "aqv", crossbow: "cqv" }; + + // Arrow/Bolt check + const bowCheck = Attack.usingBow(); + + if (bowCheck) { + let quiver; + if (quiverType[bowCheck]) { + quiver = me.getItem(quiverType[bowCheck], sdk.items.mode.Equipped); + } + + if (!quiver) { // Out of arrows/bolts + repairAction.push("buyQuiver"); + } else { + let quantity = quiver.getStat(sdk.stats.Quantity); + + if (typeof quantity === "number" + && quantity * 100 / getBaseStat("items", quiver.classid, "maxstack") <= Config.RepairPercent) { + repairAction.push("buyQuiver"); + } + } + } + + // Repair durability/quantity/charges + if (canAfford) { + if (me.getItemsForRepair(Config.RepairPercent, true).length > 0) { + repairAction.push("repair"); + } + } else { + console.warn("Can't afford repairs."); + } + + return repairAction; +}; + +/** + * @description Check if buying keys is needed, based on character config + * @returns {boolean} + */ +me.needKeys = function () { + return me.checkKeys() <= 0; +}; + +/** + * @param {number} id + * @returns {ItemUnit | null} + */ +me.getTome = function (id) { + if (!id) return null; + let tome = me.findItem(id, sdk.items.mode.inStorage, sdk.storage.Inventory); + return tome ? tome : null; +}; + +me.getUnids = function () { + return me.getItemsEx(-1, sdk.items.mode.inStorage) + .filter(function (item) { + return item.isInInventory && !item.identified; + }); +}; + +/** + * @param {number} repairPercent + * @param {boolean} chargedItems + * @returns {ItemUnit[]} + */ +me.getItemsForRepair = function (repairPercent, chargedItems) { + let itemList = []; + let item = me.getItem(-1, sdk.items.mode.Equipped); + + if (item) { + do { + // Skip ethereal items + if (item.ethereal) continue; + // Skip indestructible items + if (!item.getStat(sdk.stats.Indestructible)) { + switch (item.itemType) { + // Quantity check + case sdk.items.type.ThrowingKnife: + case sdk.items.type.ThrowingAxe: + case sdk.items.type.Javelin: + case sdk.items.type.AmazonJavelin: + let quantity = item.getStat(sdk.stats.Quantity); + + // Stat 254 = increased stack size + if (typeof quantity === "number") { + let _maxStack = (getBaseStat("items", item.classid, "maxstack") + item.getStat(sdk.stats.ExtraStack)); + if (quantity * 100 / _maxStack <= repairPercent) { + itemList.push(copyUnit(item)); + } + } + + break; + // Durability check + default: + if (item.durabilityPercent <= repairPercent) { + itemList.push(copyUnit(item)); + } + + break; + } + } + + if (chargedItems) { + // Charged item check + let charge = item.getStat(-2)[sdk.stats.ChargedSkill]; + + if (typeof (charge) === "object") { + if (charge instanceof Array) { + for (let i = 0; i < charge.length; i += 1) { + if (charge[i] !== undefined && charge[i].hasOwnProperty("charges") + && charge[i].charges * 100 / charge[i].maxcharges <= repairPercent) { + itemList.push(copyUnit(item)); + } + } + } else if (charge.charges * 100 / charge.maxcharges <= repairPercent) { + itemList.push(copyUnit(item)); + } + } + } + } while (item.getNext()); + } + + return itemList; +}; + +/** + * @param {number} id + * @returns {number} quantity of scrolls in tome + */ +me.checkScrolls = function (id) { + let tome = me.getTome(id); + + if (!tome) { + switch (id) { + case sdk.items.TomeofIdentify: + case "ibk": + return Config.FieldID.Enabled ? 0 : 20; // Ignore missing ID tome if we aren't using field ID + case sdk.items.TomeofTownPortal: + case "tbk": + return 0; // Force TP tome check + } + } + + return tome.getStat(sdk.stats.Quantity); +}; + +me.checkKeys = function () { + if (!Config.OpenChests.Enabled) return 12; + // sins don't need keys + if (me.assassin) return 12; + // cam't afford key + if (me.gold < 540) return 12; + // no room for key + if (!me.getItem(sdk.items.Key) && !Storage.Inventory.CanFit({ sizex: 1, sizey: 1 })) { + return 12; + } + + return me.getItemsEx() + .filter(function (item) { + return item.classid === sdk.items.Key && item.isInInventory; + }) + .reduce(function (acc, curr) { + return acc + curr.getStat(sdk.stats.Quantity); + }, 0); +}; + +/** + * @todo Whats the point of this? + * @returns {boolean} + */ +me.checkShard = function () { + let shard; + let check = { left: false, right: false }; + let item = me.getItem("bld", sdk.items.mode.inStorage); + + if (item) { + do { + if (item.isInInventory && item.unique) { + shard = copyUnit(item); + + break; + } + } while (item.getNext()); + } + + if (!shard) return true; + + item = me.getItem(-1, sdk.items.mode.Equipped); + + if (item) { + do { + item.bodylocation === sdk.body.RightArm && (check.right = true); + item.bodylocation === sdk.body.LeftArm && (check.left = true); + } while (item.getNext()); + } + + if (!check.right) { + shard.toCursor(); + + while (me.itemoncursor) { + clickItem(sdk.clicktypes.click.item.Left, sdk.body.RightArm); + delay(500); + } + } else if (!check.left) { + shard.toCursor(); + + while (me.itemoncursor) { + clickItem(sdk.clicktypes.click.item.Left, sdk.body.LeftArm); + delay(500); + } + } + + return true; +}; + +// Identify items while in the field if we have a id tome +me.fieldID = function () { + let list = me.getUnids(); + if (!list.length) return false; + + let tome = me.getTome(sdk.items.TomeofIdentify); + if (!tome || tome.getStat(sdk.stats.Quantity) < list.length) return false; + + while (list.length > 0) { + let item = list.shift(); + let result = Pickit.checkItem(item); + + // unid item that should be identified + if (result.result === Pickit.Result.UNID) { + Town.identifyItem(item, tome, Config.FieldID.PacketID); + delay(me.ping + 1); + result = Pickit.checkItem(item); + + switch (result.result) { + case Pickit.Result.UNWANTED: + Item.logger("Dropped", item, "fieldID"); + + if (Config.DroppedItemsAnnounce.Enable && Config.DroppedItemsAnnounce.Quality.includes(item.quality)) { + say( + "Dropped: [" + Item.qualityToName(item.quality).capitalize() + "] " + + item.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "").trim() + ); + if (Config.DroppedItemsAnnounce.LogToOOG && Config.DroppedItemsAnnounce.OOGQuality.includes(item.quality)) { + Item.logItem("Field Dropped", item, result.line); + } + } + + item.drop(); + + break; + case Pickit.Result.WANTED: + Item.logger("Field Kept", item); + Item.logItem("Field Kept", item, result.line); + + break; + default: + break; + } + } + } + + delay(200); + me.cancel(); + + return true; +}; + +me.switchToPrimary = function () { + if (me.classic) return true; + return me.switchWeapons(Attack.getPrimarySlot()); +}; + +/** + * Get a list of items that match the given criteria. + * @param {ItemUnit | { + * itemType?: number, + * classid?: number, + * mode?: number, + * quality?: number, + * sockets?: number, + * location?: number, + * ethereal?: boolean, + * cb?: (item: ItemUnit) => boolean + * }} itemInfo + * @param {boolean} skipSame + * @returns {ItemUnit[]} + */ +me.getOwned = function (itemInfo = {}, skipSame = false) { + let itemList = []; + let item = me.getItem(); + + if (item) { + do { + if (itemInfo.itemType !== undefined && itemInfo.itemType !== item.itemType) continue; + if (itemInfo.classid !== undefined && itemInfo.classid !== item.classid) continue; + if (itemInfo.mode !== undefined && itemInfo.mode !== item.mode) continue; + if (itemInfo.quality !== undefined && itemInfo.quality !== item.quality) continue; + if (itemInfo.sockets !== undefined && itemInfo.sockets !== item.sockets) continue; + if (itemInfo.location !== undefined && itemInfo.location !== item.location) continue; + if (itemInfo.ethereal !== undefined && itemInfo.ethereal !== item.ethereal) continue; + if (typeof itemInfo.cb === "function" && !itemInfo.cb(item)) continue; + if (skipSame && itemInfo.gid !== undefined && itemInfo.gid !== item.gid) continue; + itemList.push(copyUnit(item)); + } while (item.getNext()); + } + + return itemList; +}; + +/** + * Misc functions, stats/modes/states/ etc + */ +Object.defineProperties(me, { + maxNearMonsters: { + get: function () { + return Math.floor((4 * (1 / me.hpmax * me.hp)) + 1); + }, + configurable: true + }, + maxgold: { + /** max capacity (cLvl * 10000) */ + get: function () { + return me.getStat(sdk.stats.Level) * 10000; + }, + }, + inShop: { + get: function () { + if (getUIFlag(sdk.uiflags.Shop)) return true; + if (!Config.PacketShopping) return false; + let npc = getInteractedNPC(); + return !!(npc && npc.itemcount > 0); + } + }, + walking: { + get: function () { + return me.runwalk === sdk.player.move.Walk; + } + }, + running: { + get: function () { + return me.runwalk === sdk.player.move.Run; + } + }, + deadOrInSequence: { + get: function () { + return me.dead || me.mode === sdk.player.mode.SkillActionSequence; + } + }, + moving: { + get: function () { + return [sdk.player.mode.Walking, sdk.player.mode.Running, sdk.player.mode.WalkingInTown].includes(me.mode); + } + }, + staminaPercent: { + get: function () { + return Math.round((me.stamina / me.staminamax) * 100); + } + }, + staminaDrainPerSec: { + get: function () { + let bonusReduction = me.getStat(sdk.stats.StaminaRecoveryBonus); + let armorMalusReduction = 0; // TODO + return 25 * Math.max(40 * (1 + armorMalusReduction / 10) * (100 - bonusReduction) / 100, 1) / 256; + } + }, + staminaTimeLeft: { + get: function () { + return me.stamina / me.staminaDrainPerSec; + } + }, + staminaMaxDuration: { + get: function () { + return me.staminamax / me.staminaDrainPerSec; + } + }, + FCR: { + get: function () { + return me.getStat(sdk.stats.FCR) - (!!Config ? Config.FCR : 0); + } + }, + FHR: { + get: function () { + return me.getStat(sdk.stats.FHR) - (!!Config ? Config.FHR : 0); + } + }, + FBR: { + get: function () { + return me.getStat(sdk.stats.FBR) - (!!Config ? Config.FBR : 0); + } + }, + IAS: { + get: function () { + return me.getStat(sdk.stats.IAS) - (!!Config ? Config.IAS : 0); + } + }, + shapeshifted: { + get: function () { + return me.getState(sdk.states.Wolf) || me.getState(sdk.states.Bear) || me.getState(sdk.states.Delerium); + } + }, + mpPercent: { + get: function () { + return Math.round(me.mp * 100 / me.mpmax); + } + }, + skillDelay: { + get: function () { + return me.getState(sdk.states.SkillDelay); + } + }, + _shitList: { + value: new Set(), + writable: true, + enumerable: true, + configurable: true + }, + shitList: { + get: function () { + return this._shitList; + }, + /** @param {Set} value */ + set: function (value) { + if (value instanceof Set) { + this._shitList = value; + } else { + throw new TypeError("shitList must be a Set"); + } + }, + enumerable: true, + configurable: true + } +}); + +/** + * Game type, difficulty, classtype, etc + */ +Object.defineProperties(me, { + classic: { + get: function () { + return me.gametype === sdk.game.gametype.Classic; + } + }, + expansion: { + get: function () { + return me.gametype === sdk.game.gametype.Expansion; + } + }, + softcore: { + get: function () { + return me.playertype === false; + } + }, + hardcore: { + get: function () { + return me.playertype === true; + } + }, + normal: { + get: function () { + return me.diff === sdk.difficulty.Normal; + } + }, + nightmare: { + get: function () { + return me.diff === sdk.difficulty.Nightmare; + } + }, + hell: { + get: function () { + return me.diff === sdk.difficulty.Hell; + } + }, + amazon: { + get: function () { + return me.classid === sdk.player.class.Amazon; + } + }, + sorceress: { + get: function () { + return me.classid === sdk.player.class.Sorceress; + } + }, + necromancer: { + get: function () { + return me.classid === sdk.player.class.Necromancer; + } + }, + paladin: { + get: function () { + return me.classid === sdk.player.class.Paladin; + } + }, + barbarian: { + get: function () { + return me.classid === sdk.player.class.Barbarian; + } + }, + druid: { + get: function () { + return me.classid === sdk.player.class.Druid; + } + }, + assassin: { + get: function () { + return me.classid === sdk.player.class.Assassin; + } + }, +}); + +/** + * Quest items + */ +Object.defineProperties(me, { + wirtsleg: { + get: function () { + return me.getItem(sdk.quest.item.WirtsLeg); + } + }, + cube: { + get: function () { + return me.getItem(sdk.quest.item.Cube); + } + }, + shaft: { + get: function () { + return me.getItem(sdk.quest.item.ShaftoftheHoradricStaff); + } + }, + amulet: { + get: function () { + return me.getItem(sdk.quest.item.ViperAmulet); + } + }, + staff: { + get: function () { + return me.getItem(sdk.quest.item.HoradricStaff); + } + }, + completestaff: { + get: function () { + return me.getItem(sdk.quest.item.HoradricStaff); + } + }, + eye: { + get: function () { + return me.getItem(sdk.items.quest.KhalimsEye); + } + }, + brain: { + get: function () { + return me.getItem(sdk.quest.item.KhalimsBrain); + } + }, + heart: { + get: function () { + return me.getItem(sdk.quest.item.KhalimsHeart); + } + }, + khalimswill: { + get: function () { + return me.getItem(sdk.quest.item.KhalimsWill); + } + }, + khalimsflail: { + get: function () { + return me.getItem(sdk.quest.item.KhalimsFlail); + } + }, + malahspotion: { + get: function () { + return me.getItem(sdk.quest.item.MalahsPotion); + } + }, + scrollofresistance: { + get: function () { + return me.getItem(sdk.quest.item.ScrollofResistance); + } + }, +}); + +/** + * Quests + AreaData + */ +(function () { + const QuestData = require("./GameData/QuestData"); + + /** + * @param {number} act + * @returns {boolean} + */ + me.accessToAct = function (act) { + if (act === 1) return true; + return me.highestAct >= act; + }; + + // const AMOUNT_OF_WAYPOINTS = 39; + const AMOUNT_OF_WAYPOINTS = [ + sdk.waypoints.Act1, + sdk.waypoints.Act2, + sdk.waypoints.Act3, + sdk.waypoints.Act4, + sdk.waypoints.Act5 + ].flat().length; + /** @type {boolean[]} */ + const _cachedWaypoints = new Array(AMOUNT_OF_WAYPOINTS).fill(false); + + Object.defineProperty(me, "waypoints", { + get: function () { + return _cachedWaypoints; + }, + /** @param {boolean[]} value */ + set: function (value) { + if (!Array.isArray(value)) return; + value.forEach(function (val, index) { + if (index < AMOUNT_OF_WAYPOINTS) { + _cachedWaypoints[index] = val; + } + }); + } + }); + + /** + * Easier way to check if you have a waypoint + * @param {number} area + * @returns {boolean} + */ + me.haveWaypoint = function (area) { + const areaIndex = Pather.wpAreas.indexOf(area); + if (areaIndex === -1) return false; + return getWaypoint(areaIndex); + }; + + me.checkQuest = function (questId, state) { + const quest = QuestData.get(questId); + if (!quest) return false; + return quest.checkState(state); + }; + + Object.defineProperties(me, { + highestAct: { + get: function () { + let acts = [true, + QuestData.get(sdk.quest.id.AbleToGotoActII).complete(), + QuestData.get(sdk.quest.id.AbleToGotoActIII).complete(), + QuestData.get(sdk.quest.id.AbleToGotoActIV).complete(), + QuestData.get(sdk.quest.id.AbleToGotoActV).complete()]; + let index = acts.findIndex(function (i) { + return !i; + }); // find first false, returns between 1 and 5 + return index === -1 ? 5 : index; + } + }, + highestQuestDone: { + get: function () { + for (let i = sdk.quest.id.Respec; i >= sdk.quest.id.SpokeToWarriv; i--) { + if (!QuestData.has(i)) continue; + if (QuestData.get(i).complete()) return i; + + // check if we've completed main part but not used our reward + if ([ + sdk.quest.id.RescueonMountArreat, + sdk.quest.id.SiegeOnHarrogath, + sdk.quest.id.ToolsoftheTrade, + sdk.quest.id.PrisonofIce, + ].includes(i) && QuestData.get(i).complete(true)) { + return i; + } + } + return undefined; + } + }, + den: { + get: function () { + return QuestData.get(sdk.quest.id.DenofEvil).complete(); + } + }, + bloodraven: { + get: function () { + return QuestData.get(sdk.quest.id.SistersBurialGrounds).complete(); + } + }, + smith: { + get: function () { + return QuestData.get(sdk.quest.id.ToolsoftheTrade).complete(); + } + }, + imbue: { + get: function () { + return QuestData.get(sdk.quest.id.ToolsoftheTrade).checkState(sdk.quest.states.ReqComplete, true); + } + }, + cain: { + get: function () { + return QuestData.get(sdk.quest.id.TheSearchForCain).complete(); + } + }, + tristram: { + get: function () { + // update where this is used and change the state to be portal opened and me.cain to be quest completed + return QuestData.get(sdk.quest.id.TheSearchForCain).complete(); + } + }, + countess: { + get: function () { + return QuestData.get(sdk.quest.id.ForgottenTower).complete(); + } + }, + andariel: { + get: function () { + return QuestData.get(sdk.quest.id.AbleToGotoActII).complete(); + } + }, + radament: { + get: function () { + return QuestData.get(sdk.quest.id.RadamentsLair).complete(); + } + }, + horadricstaff: { + get: function () { + return QuestData.get(sdk.quest.id.TheHoradricStaff).complete(); + } + }, + summoner: { + get: function () { + return QuestData.get(sdk.quest.id.TheSummoner).complete(); + } + }, + duriel: { + get: function () { + return QuestData.get(sdk.quest.id.AbleToGotoActIII).complete(); + } + }, + goldenbird: { + get: function () { + return QuestData.get(sdk.quest.id.TheGoldenBird).complete(); + } + }, + lamessen: { + get: function () { + return QuestData.get(sdk.quest.id.LamEsensTome).complete(); + } + }, + gidbinn: { + get: function () { + return QuestData.get(sdk.quest.id.BladeoftheOldReligion).complete(); + } + }, + travincal: { + get: function () { + return QuestData.get(sdk.quest.id.KhalimsWill).complete(); + } + }, + blackendTemple: { + get: function () { + return QuestData.get(sdk.quest.id.TheBlackenedTemple).complete(); + } + }, + mephisto: { + get: function () { + return QuestData.get(sdk.quest.id.AbleToGotoActIV).complete(); + } + }, + izual: { + get: function () { + return QuestData.get(sdk.quest.id.TheFallenAngel).complete(); + } + }, + hellforge: { + get: function () { + return QuestData.get(sdk.quest.id.HellsForge).complete(); + } + }, + diablo: { + get: function () { + return QuestData.get(sdk.quest.id.TerrorsEnd).complete(); + } + }, + shenk: { + get: function () { + return QuestData.get(sdk.quest.id.SiegeOnHarrogath).complete(true); + } + }, + larzuk: { + get: function () { + return QuestData.get(sdk.quest.id.SiegeOnHarrogath).checkState(sdk.quest.states.ReqComplete, true); + } + }, + savebarby: { + get: function () { + return QuestData.get(sdk.quest.id.RescueonMountArreat).complete(); + } + }, + barbrescue: { + get: function () { + return QuestData.get(sdk.quest.id.RescueonMountArreat).complete(); + } + }, + anya: { + get: function () { + return QuestData.get(sdk.quest.id.PrisonofIce).complete(); + } + }, + ancients: { + get: function () { + return QuestData.get(sdk.quest.id.RiteofPassage).complete(); + } + }, + baal: { + get: function () { + return QuestData.get(sdk.quest.id.EyeofDestruction).complete(); + } + }, + // Misc + cows: { + get: function () { + return me.getQuest(sdk.quest.id.TheSearchForCain, 10); + } + }, + respec: { + get: function () { + return QuestData.get(sdk.quest.id.Respec).complete(); + } + }, + diffCompleted: { + get: function () { + return !!((me.classic && me.diablo) || me.baal); + } + }, + }); +})(); diff --git a/d2bs/kolbot/libs/core/Misc.js b/d2bs/kolbot/libs/core/Misc.js new file mode 100644 index 000000000..42232117f --- /dev/null +++ b/d2bs/kolbot/libs/core/Misc.js @@ -0,0 +1,1164 @@ +/** +* @filename Misc.js +* @author kolton, theBGuy +* @desc Misc library for functions that don't fit neatly into another namespace +* contains clickhandling, shrine/chest/player locating, ect +* +*/ + +const Misc = (function () { + const ShrineData = require("./GameData/ShrineData"); + + return { + _diabloSpawned: false, + /** + * Click something + * @param {number} button + * @param {number} shift + * @param {number | Unit} [x] + * @param {number} [y] + * @returns {boolean} + */ + click: function (button, shift, x, y) { + if (arguments.length < 2) throw new Error("Misc.click: Needs at least 2 arguments."); + + while (!me.gameReady) { + delay(100); + } + + switch (arguments.length) { + case 2: + me.blockMouse = true; + clickMap(button, shift, me.x, me.y); + delay(20); + clickMap(button + 2, shift, me.x, me.y); + me.blockMouse = false; + + break; + case 3: + if (typeof (x) !== "object") throw new Error("Misc.click: Third arg must be a Unit."); + + me.blockMouse = true; + clickMap(button, shift, x); + delay(20); + clickMap(button + 2, shift, x); + me.blockMouse = false; + + break; + case 4: + me.blockMouse = true; + clickMap(button, shift, x, y); + delay(20); + clickMap(button + 2, shift, x, y); + me.blockMouse = false; + + break; + } + + return true; + }, + + /** + * Check if a player is in your party + * @param {string} name + * @returns {boolean} + */ + inMyParty: function (name) { + if (me.name === name) return true; + + while (!me.gameReady) { + delay(100); + } + + let player, myPartyId; + + try { + player = getParty(); + if (!player) return false; + + myPartyId = player.partyid; + player = getParty(name); // May throw an error + + if (player && player.partyid !== sdk.party.NoParty && player.partyid === myPartyId) { + return true; + } + } catch (e) { + player = getParty(); + + if (player) { + myPartyId = player.partyid; + + while (player.getNext()) { + if (player.partyid !== sdk.party.NoParty && player.partyid === myPartyId) { + return true; + } + } + } + } + + return false; + }, + + /** + * Find a player + * @param {string} name + * @returns {Party | false} + */ + findPlayer: function (name) { + let player = getParty(); + + if (player) { + do { + if (player.name !== me.name && player.name === name) { + return player; + } + } while (player.getNext()); + } + + return false; + }, + + /** + * Get player unit + * @param {string} name + * @returns {Player | false} + */ + getPlayerUnit: function (name) { + let player = Game.getPlayer(name); + + if (player) { + do { + if (!player.dead) { + return player; + } + } while (player.getNext()); + } + + return false; + }, + + /** + * Get the player act, accepts party unit or name + * @param {Party | string} player + * @returns {number | false} + */ + getPlayerAct: function (player) { + if (!player) return false; + + let unit = (typeof player === "object" ? player : this.findPlayer(player)); + + return unit ? sdk.areas.actOf(unit.area) : false; + }, + + /** + * Get number of players within getUnit distance - excludes self + * @returns {number} + */ + getNearbyPlayerCount: function () { + let count = 0; + let player = Game.getPlayer(); + + if (player) { + do { + if (player.name !== me.name && !player.dead) { + count += 1; + } + } while (player.getNext()); + } + + return count; + }, + + /** + * Get total number of players in game + * @returns {number} + */ + getPlayerCount: function () { + let count = 0; + let party = getParty(); + + if (party) { + do { + count += 1; + } while (party.getNext()); + } + + return count; + }, + + /** + * Get total number of players in game and in my party + * @returns {number} + */ + getPartyCount: function () { + let count = 0; + let party = getParty(); + + if (party) { + let myPartyId = party.partyid; + + do { + if (party.partyid !== sdk.party.NoParty + && party.partyid === myPartyId + && party.name !== me.name) { + count += 1; + } + } while (party.getNext()); + } + + return count; + }, + + /** + * Check if any member of our party meets a certain level req + * @param {number} levelCheck + * @param {string | string[]} exclude + * @returns {boolean} + */ + checkPartyLevel: function (levelCheck = 1, exclude = []) { + !Array.isArray(exclude) && (exclude = [exclude]); + let party = getParty(); + + if (party) { + let myPartyId = party.partyid; + + do { + if (party.partyid !== sdk.party.NoParty && party.partyid === myPartyId + && party.name !== me.name && !exclude.includes(party.name)) { + if (party.level >= levelCheck) { + return true; + } + } + } while (party.getNext()); + } + + return false; + }, + + /** + * @param {Player | string} player + * @returns {number | false} + */ + getPlayerArea: function (player) { + if (!player) return false; + + let unit = (typeof player === "object" ? player : this.findPlayer(player)); + + return !!unit ? unit.area : 0; + }, + + /** + * autoleader by Ethic - refactored by theBGuy + * Autodetect leader for leech scripts by looking to see who first enters a certain area + * @param {{ destination: number | number[], quitIf?: Function, timeout?: number }} givenSettings + * @returns + */ + autoLeaderDetect: function (givenSettings = {}) { + const settings = Object.assign({}, { + destination: -1, + quitIf: false, + timeout: Infinity + }, givenSettings); + + // make destination an array so it's easier to handle both cases + !Array.isArray(settings.destination) && (settings.destination = [settings.destination]); + + let leader; + let startTick = getTickCount(); + const check = typeof settings.quitIf === "function"; + do { + /** @type {Party[]} */ + let suspects = []; + let solofail = 0; + let suspect = getParty(); // get party object (players in game) + + do { + // player isn't alone + suspect.name !== me.name && (solofail += 1); + + if (check && settings.quitIf(suspect.area)) return false; + + // players not hostile found in destination area... + if (settings.destination.includes(suspect.area) + && !getPlayerFlag(me.gid, suspect.gid, sdk.player.flag.Hostile)) { + suspects.push(copyObj(suspect)); + console.log("ÿc4Autodetected ÿc0" + suspect.name + " (level " + suspect.level + ")"); + } + } while (suspect.getNext()); + + if (suspects.length > 1) { + // if we have more than one suspect, sort by level and they are generally the leaders + suspects.sort((a, b) => b.level - a.level); + + // look for tps from the suspect to the destination area. Sometimes we come in late, happens a lot with pubjoin + for (let suspect of suspects) { + let portal = Pather.getPortal(null, suspect.name); + if (!portal) continue; + + if (portal && settings.destination.includes(portal.objtype)) { + leader = suspect.name; + console.log("ÿc4Autodetect Selecting: ÿc0" + leader + " (Portal found)"); + return leader; + } + } + } + + if (suspects.length) { + leader = suspects[0].name; + console.log("ÿc4Autodetect Selecting: ÿc0" + leader); + + return leader; + } + + // empty game, nothing left to do. Or we exceeded our wait time + if (solofail === 0 || (getTickCount() - startTick > settings.timeout)) { + return false; + } + + delay(500); + } while (!leader); // repeat until leader is found (or until game is empty) + + return false; + }, + + /** + * @description Open a chest Unit (takes chestID or unit) + * @param {Unit | number} unit + * @returns {boolean} If we opened the chest + */ + openChest: function (unit) { + typeof unit === "number" && (unit = Game.getObject(unit)); + + // Skip invalid/open and Countess chests + if (!unit || unit.x === 12526 || unit.x === 12565 || unit.mode) return false; + // locked chest, no keys + if (!me.assassin && unit.islocked + && !me.findItem(sdk.items.Key, sdk.items.mode.inStorage, sdk.storage.Inventory)) { + return false; + } + + let specialChest = sdk.quest.chests.includes(unit.classid); + + for (let i = 0; i < 7; i++) { + // don't use tk if we are right next to it + let useTK = (unit.distance > 5 && Skill.useTK(unit) && i < 3); + if (useTK) { + unit.distance > 13 && Attack.getIntoPosition(unit, 13, sdk.collision.WallOrRanged); + if (!Packet.telekinesis(unit)) { + console.debug("Failed to tk: attempt: " + i); + continue; + } + } else { + [(unit.x + 1), (unit.y + 2)].distance > 5 && Pather.moveTo(unit.x + 1, unit.y + 2, 3); + (specialChest || i > 2) ? Misc.click(0, 0, unit) : Packet.entityInteract(unit); + } + + if (Misc.poll(() => unit.mode, 1000, 50)) { + return true; + } + Packet.flash(me.gid); + } + + // Click to stop walking in case we got stuck + !me.idle && Misc.click(0, 0, me.x, me.y); + + return false; + }, + + /** + * Open all chests that have preset units in an area + * @param {number} area + * @param {number[]} chestIds + * @returns {boolean} + */ + openChestsInArea: function (area, chestIds = []) { + !area && (area = me.area); + area !== me.area && Pather.journeyTo(area); + + let presetUnits = Game.getPresetObjects(area); + if (!presetUnits) return false; + + if (!chestIds.length) { + chestIds = [ + 5, 6, 87, 104, 105, 106, 107, 143, 140, 141, 144, 146, 147, 148, 176, 177, 181, 183, 198, 240, 241, + 242, 243, 329, 330, 331, 332, 333, 334, 335, 336, 354, 355, 356, 371, 387, 389, 390, 391, 397, 405, + 406, 407, 413, 420, 424, 425, 430, 431, 432, 433, 454, 455, 501, 502, 504, 505, 580, 581 + ]; + } + + let coords = []; + + while (presetUnits.length > 0) { + if (chestIds.includes(presetUnits[0].id)) { + coords.push({ + x: presetUnits[0].roomx * 5 + presetUnits[0].x, + y: presetUnits[0].roomy * 5 + presetUnits[0].y + }); + } + + presetUnits.shift(); + } + + while (coords.length) { + coords.sort(Sort.units); + Pather.moveToUnit(coords[0], 1, 2); + this.openChests(20); + + for (let i = 0; i < coords.length; i += 1) { + if (getDistance(coords[i].x, coords[i].y, coords[0].x, coords[0].y) < 20) { + coords.shift(); + } + } + } + + return true; + }, + + /** + * @param {number} range + * @returns {boolean} + */ + openChests: function (range = 15) { + if (!Config.OpenChests.Enabled) return true; + + let containers = []; + + // Testing all container code + if (Config.OpenChests.Types.some((el) => el.toLowerCase() === "all")) { + containers = [ + "chest", "loose rock", "hidden stash", "loose boulder", "corpseonstick", + "casket", "armorstand", "weaponrack", "barrel", "holeanim", "tomb2", + "tomb3", "roguecorpse", "ratnest", "corpse", "goo pile", "largeurn", + "urn", "chest3", "jug", "skeleton", "guardcorpse", "sarcophagus", "object2", + "cocoon", "basket", "stash", "hollow log", "hungskeleton", "pillar", + "skullpile", "skull pile", "jar3", "jar2", "jar1", "bonechest", "woodchestl", + "woodchestr", "barrel wilderness", "burialchestr", "burialchestl", "explodingchest", + "chestl", "chestr", "groundtomb", "icecavejar1", "icecavejar2", + "icecavejar3", "icecavejar4", "deadperson", "deadperson2", "evilurn", "tomb1l", "tomb3l", "groundtombl" + ]; + } else { + containers = Config.OpenChests.Types; + } + + /** @type {Set} */ + const seenGids = new Set(); + + /** + * @param {number} range + * @returns {ObjectUnit[]} + */ + const buildChestList = function (range) { + let unitList = []; + let unit = Game.getObject(); + + if (unit) { + do { + if (unit.name && unit.mode === sdk.objects.mode.Inactive + && !seenGids.has(unit.gid) + && getDistance(me.x, me.y, unit.x, unit.y) <= range + && containers.includes(unit.name.toLowerCase())) { + seenGids.add(unit.gid); + unitList.push(copyUnit(unit)); + } + } while (unit.getNext()); + } + return unitList; + }; + + const startPos = new PathNode(me.x, me.y); + let unitList = buildChestList(range); + + while (unitList.length > 0) { + unitList.sort(Sort.units); + let unit = unitList.shift(); + + if (unit) { + const chest = Game.getObject(-1, -1, unit.gid); + if (chest && (Pather.useTeleport() || !checkCollision(me, chest, sdk.collision.WallOrRanged)) + && this.openChest(chest)) { + Pickit.pickItems(); + } + } + + if (startPos.distance > 5) { + // rebuid chest list every 5 chests in case we've moved and add any new chests to our list + let _unitList = buildChestList(Math.round(range / 2)); + // console.debug("Rescanning for chests: " + _unitList.length + " chests found."); + unitList = unitList.concat(_unitList); + } + } + + return true; + }, + + /** @type {number[] | null} */ + shrineStates: null, + + lastShrine: new function () { + this.tick = 0; + this.duration = 0; + this.type = -1; + this.state = 0; + + /** @param {ObjectUnit} unit */ + this.update = function (unit) { + if (!unit || !unit.hasOwnProperty("objtype")) return; + // we only care about tracking shrines with states + if (!ShrineData.getState(unit.objtype)) return; + this.tick = getTickCount(); + this.type = unit.objtype; + this.duration = ShrineData.getDuration(unit.objtype); + this.state = ShrineData.getState(unit.objtype); + }; + + this.remaining = function () { + return this.duration - (getTickCount() - this.tick); + }; + + this.isMyCurrentState = function () { + if (this.state <= 0) return false; + return me.getState(this.state); + }; + }, + + /** @type {Set} */ + _shrinerIgnore: new Set(), + + shriner: function () { + if (!Config.AutoShriner) return false; + + let shrineList = []; + let shrine = Game.getObject(); + + /** + * TODO: Handle stateful shrines + * TODO: Track last shrine used - should tier based on shrine type + * @param {ObjectUnit} shrine + * @returns {boolean} + */ + const wantShrine = function (shrine) { + if (ShrineData.getState(shrine.objtype) + && Misc.lastShrine.type === shrine.objtype + && Misc.lastShrine.isMyCurrentState() + && Misc.lastShrine.remaining() > Time.seconds(30)) { + return false; + } + switch (shrine.objtype) { + case sdk.shrines.Health: + // we only want if its dire or its close to us if we can't teleport + if (!Pather.useTeleport() && Pather.getWalkDistance(shrine.x, shrine.y) > 10) { + return me.hpPercent <= 50; + } + return me.hpPercent < 80; + case sdk.shrines.Mana: + // we only want if its dire or its close to us if we can't teleport + if (!Pather.useTeleport() && Pather.getWalkDistance(shrine.x, shrine.y) > 10) { + return me.mpPercent <= 50; + } + return me.mpPercent < 80; + case sdk.shrines.Refilling: + // we only want if its dire or its close to us if we can't teleport + if (!Pather.useTeleport() && Pather.getWalkDistance(shrine.x, shrine.y) > 10) { + return me.hpPercent <= 50 || me.mpPercent <= 50; + } + return me.hpPercent < 85 || me.mpPercent < 85; + case sdk.shrines.Experience: + return me.charlvl < 99; + case sdk.shrines.Skill: + return !me.getState(sdk.states.ShrineExperience); + case sdk.shrines.ManaRecharge: + case sdk.shrines.Stamina: + // we only want if its close to us if we can't teleport + if (!Pather.useTeleport() && Pather.getWalkDistance(shrine.x, shrine.y) > 15) { + return false; + } + // for now, only grab if we have nothing else active + return !Misc.lastShrine.state || !me.getState(Misc.lastShrine.state); + case sdk.shrines.ResistFire: + case sdk.shrines.ResistCold: + case sdk.shrines.ResistLightning: + case sdk.shrines.ResistPoison: + { + /** @type {Record} */ + let resistances = {}; + resistances[sdk.shrines.ResistFire] = me.fireRes; + resistances[sdk.shrines.ResistCold] = me.coldRes; + resistances[sdk.shrines.ResistLightning] = me.lightRes; + resistances[sdk.shrines.ResistPoison] = me.poisonRes; + + // we only want if its dire or its close to us if we can't teleport + if (!Pather.useTeleport() && Pather.getWalkDistance(shrine.x, shrine.y) > 15) { + return resistances[shrine.objtype] <= 0; + } + + if (!Misc.lastShrine.state || !me.getState(Misc.lastShrine.state)) { + return true; + } + + // first check if the lasts shrine was a resist shrine + if (resistances[Misc.lastShrine.type] === undefined) { + // evaluate whether we should overwrite the last shrine + if (Misc.lastShrine.type === sdk.shrines.Experience) { + // never overwrite experience shrine + return false; + } + + if (Misc.lastShrine.type === sdk.shrines.Skill) { + if (resistances[shrine.objtype] <= 25) { + // makes sense if we have a low resistance + return true; + } + + return false; + } + } + + // check that the current shrine benefits our lowest resistance better than the last shrine + if (resistances[shrine.objtype] < resistances[Misc.lastShrine.type]) { + // how much better? If it's at least a 5% difference, we should take it + // otherwise only do it if the distance is convenient + + return true; + } + break; + } + // TODO: handle armor and combat shrines + case sdk.shrines.Armor: + case sdk.shrines.Combat: + // we only want if its close to us if we can't teleport + if (!Pather.useTeleport() && Pather.getWalkDistance(shrine.x, shrine.y) > 15) { + return false; + } + + if (!Misc.lastShrine.state || !me.getState(Misc.lastShrine.state)) { + return true; + } + return false; + case sdk.shrines.Monster: + // we only want if its close to us if we can't teleport + if (!Pather.useTeleport() && Pather.getWalkDistance(shrine.x, shrine.y) > 15) { + return false; + } + + return true; // why not? + case sdk.shrines.Gem: + // for now we ignore if we are gem hunting later on + // TODO: add gem hunting logic, get gem from stash if we have one + return !Scripts.GemHunter; + } + return false; + }; + + const wantWell = function () { + if (me.hpPercent < 75) return true; + if (me.mpPercent < 75) return true; + if (me.staminaPercent < 50) return true; + return [ + sdk.states.Frozen, + sdk.states.Poison, + sdk.states.AmplifyDamage, + sdk.states.Decrepify + ].some(function (state) { + return me.getState(state); + }); + }; + + if (shrine) { + do { + if (!shrine.name) continue; + // don't leave our area to grab shrines + // TODO: better fix for this as it'd be okay for small detours but orginally found this at halls of vaught stairs attempting to get shrine that was on next level + if (!me.inArea(shrine.area)) continue; + let _name = shrine.name.toLowerCase(); + if ((_name.includes("shrine") && ShrineData.has(shrine.objtype) || (_name.includes("well"))) + && ShrineData.has(shrine.objtype) + && shrine.mode === sdk.objects.mode.Inactive) { + shrineList.push(copyUnit(shrine)); + } + } while (shrine.getNext()); + } + + while (shrineList.length > 0) { + shrineList.sort(Sort.units); + shrine = shrineList.shift(); + + if (shrine) { + if (me.inArea(sdk.areas.ChaosSanctuary)) { + // stateful shrines are pointless in CS unless we are running wakka or diablo has spawned so no more oblivion knights + if (!this._diabloSpawned && Loader.scriptName() !== "Wakka" && ShrineData.getState(shrine.objtype)) { + continue; + } + } + + if (ShrineData.has(shrine.objtype) ? wantShrine(shrine) : wantWell()) { + // need to take distance into account. + // How far away is this shrine? + // Can we teleport to it? + // Is it closer to our path at a later point? + // Is it a really good shrine or just a meh one? So we know if we should go out of our way for it. + if (shrine.distance > Skill.haveTK ? 20 : 10) { + if (Pather.currentWalkingPath.some((point) => getDistance(point.x, point.y, shrine.x, shrine.y) < 10)) { + if (Config.DebugMode.Path) { + new Line(shrine.x - 3, shrine.y, shrine.x + 3, shrine.y, 0x9B, true); + new Line(shrine.x, shrine.y - 3, shrine.x, shrine.y + 3, 0x9B, true); + } + continue; + } + } + this.getShrine(shrine); + } + } + } + + return true; + }, + + /** + * @param {number} range + * @param {number[]} ignore + * @returns {boolean} + */ + scanShrines: function (range, ignore) { + if (Config.AutoShriner) { + return this.shriner(); + } + if (!Config.ScanShrines.length) return false; + + !range && (range = Pather.useTeleport() ? 25 : 15); + !Array.isArray(ignore) && (ignore = [ignore]); + + /** @type {ObjectUnit[]} */ + let shrineList = []; + + // Initiate shrine states + if (!this.shrineStates) { + Misc.shrineStates = []; + + for (let i = 0; i < Config.ScanShrines.length; i += 1) { + this.shrineStates[i] = ShrineData.getState(Config.ScanShrines[i]); + } + } + + const needWell = function () { + if (me.hpPercent < Config.UseWells.HpPercent) return true; + if (me.mpPercent < Config.UseWells.MpPercent) return true; + if (me.staminaPercent < Config.UseWells.StaminaPercent) return true; + if (Config.UseWells.StatusEffects) { + return [ + sdk.states.Frozen, + sdk.states.Poison, + sdk.states.AmplifyDamage, + sdk.states.Decrepify + ].some(function (state) { + return me.getState(state); + }); + } + return false; + }; + + /** + * Fix for a3/a5 shrines + */ + let shrine = Game.getObject(); + + if (shrine) { + let index = -1; + + // Build a list of nearby shrines + do { + if (!shrine.name) continue; + let _name = shrine.name.toLowerCase(); + if ((_name.includes("shrine") && ShrineData.has(shrine.objtype) || (_name.includes("well"))) + && shrine.mode === sdk.objects.mode.Inactive + && !ignore.includes(shrine.objtype) + && getDistance(me.x, me.y, shrine.x, shrine.y) <= range) { + shrineList.push(copyUnit(shrine)); + } + } while (shrine.getNext()); + if (!shrineList.length) return false; + + // Check if we have a shrine state, store its index if yes + for (let i = 0; i < this.shrineStates.length; i += 1) { + if (me.getState(this.shrineStates[i])) { + index = i; + + break; + } + } + + for (let i = 0; i < Config.ScanShrines.length; i += 1) { + for (let shrine of shrineList) { + // Get the shrine if we have no active state or to refresh current state or if the shrine has no state + // Don't override shrine state with a lesser priority shrine + // todo - check to make sure we can actually get the shrine for ones without states + // can't grab a health shrine if we are in perfect health, can't grab mana shrine if our mana is maxed + if (index === -1 || i <= index || this.shrineStates[i] === 0) { + if (( + shrine.objtype === Config.ScanShrines[i] + || (Config.ScanShrines[i] === "well" && shrine.name.toLowerCase().includes("well") && needWell()) + ) && (Pather.useTeleport() || !checkCollision(me, shrine, sdk.collision.WallOrRanged))) { + this.getShrine(shrine); + + // Gem shrine - pick gem + if (Config.ScanShrines[i] === sdk.shrines.Gem) { + Pickit.pickItems(); + } + } + } + } + } + } + + return true; + }, + + /** + * Use a shrine Unit + * @param {ObjectUnit} unit + * @returns {boolean} + */ + getShrine: function (unit) { + if (unit.mode === sdk.objects.mode.Active) return false; + + for (let i = 0; i < 3; i++) { + if (Skill.useTK(unit) && i < 2) { + unit.distance > 21 && Pather.moveNearUnit(unit, 20); + if (!Packet.telekinesis(unit)) { + Attack.getIntoPosition(unit, 20, sdk.collision.WallOrRanged); + } + } else { + if (getDistance(me, unit) < 4 || Pather.moveToUnit(unit, 3, 0)) { + Misc.click(0, 0, unit); + } + } + + if (Misc.poll(() => unit.mode, 1000, 40)) { + Misc.lastShrine.update(unit); + return true; + } + } + + return false; + }, + + /** + * Check all shrines in area and get the first one of specified type + * @param {number} area + * @param {number} type + * @param {boolean} use + * @returns {boolean} Sucesfully found shrine(s) + * @todo + * - Sometimes it seems like calling getPresetObjects to quickly after taking an exit causes a crash, only anecdotal evidence though. Test delays + * - Add the rest of the preset shrine id's to look for + */ + getShrinesInArea: function (area, type, use) { + let shrineLocs = []; + let shrineIds = [2, 81, 83]; + let unit = Game.getPresetObjects(area); + let result = false; + + if (unit) { + for (let i = 0; i < unit.length; i += 1) { + if (shrineIds.includes(unit[i].id)) { + shrineLocs.push([unit[i].roomx * 5 + unit[i].x, unit[i].roomy * 5 + unit[i].y]); + } + } + } + + try { + NodeAction.shrinesToIgnore.push(type); + + while (shrineLocs.length > 0) { + shrineLocs.sort(Sort.points); + let coords = shrineLocs.shift(); + + // Skill.haveTK ? Pather.moveNear(coords[0], coords[1], 20) : Pather.moveTo(coords[0], coords[1], 2); + Pather.moveToEx(coords[0], coords[1], { minDist: Skill.haveTK ? 20 : 5, callback: () => { + let shrine = Game.getObject("shrine"); + return !!shrine && shrine.x === coords[0] && shrine.y === coords[1]; + } }); + + let shrine = Game.getObject("shrine"); + + if (shrine) { + do { + if (shrine.objtype === type && shrine.mode === sdk.objects.mode.Inactive) { + (!Skill.haveTK || !use) && Pather.moveTo(shrine.x - 2, shrine.y - 2); + + if (!use || this.getShrine(shrine)) { + result = true; + + if (type === sdk.shrines.Gem) { + Pickit.pickItems(); + } + return true; + } + } + } while (shrine.getNext()); + } + } + } finally { + NodeAction.shrinesToIgnore.remove(type); + } + + return result; + }, + + /** + * Go to town when low on hp/mp or when out of potions. can be upgraded to check for curses etc. + * @deprecated - will be removed in future versions + * @returns {boolean} + */ + townCheck: function () { + if (!me.canTpToTown()) return false; + + let tTick = getTickCount(); + let check = false; + + if (Config.TownCheck && !me.inTown) { + try { + if (me.needPotions() || (Config.OpenChests.Enabled && me.needKeys())) { + check = true; + } + } catch (e) { + return false; + } + + if (check) { + // check that townchicken is running - so we don't spam needing potions if it isn't + let townChick = getScript("threads/TownChicken.js"); + if (!townChick || townChick && !townChick.running) { + return false; + } + + townChick.send("townCheck"); + console.log("townCheck check Duration: " + (getTickCount() - tTick)); + + return true; + } + } + + return false; + }, + + /** + * Log someone's gear + * @param {string} name + * @returns {boolean} + */ + spy: function (name) { + let unit = getUnit(-1, name); + + if (!unit) { + console.warn("player not found"); + return false; + } + + let item = unit.getItem(); + + if (item) { + do { + this.logItem(unit.name, item); + } while (item.getNext()); + } + + return true; + }, + + errorConsolePrint: true, + screenshotErrors: true, + + /** + * Report script errors to logs/ScriptErrorLog.txt + * @param {Error | string} error + * @param {string} [script] + */ + errorReport: function (error, script) { + let msg, oogmsg, filemsg, source, stack; + let stackLog = ""; + + let date = new Date(); + const dateString = "[" + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)) + .toISOString().slice(0, -5).replace(/-/g, "/").replace("T", " ") + "]"; + + if (typeof error === "string") { + msg = error; + oogmsg = error.replace(/ÿc[0-9!"+<:;.*]/gi, ""); + filemsg = dateString + " <" + me.profile + "> " + error.replace(/ÿc[0-9!"+<:;.*]/gi, "") + "\n"; + } else { + source = error.fileName.substring(error.fileName.lastIndexOf("\\") + 1, error.fileName.length); + msg = "ÿc1Error in ÿc0" + script + " ÿc1(" + source + " line ÿc1" + error.lineNumber + "): ÿc1" + error.message; + oogmsg = ( + "Error in " + script + " (" + source + " #" + error.lineNumber + ") " + error.message + + " (Area: " + me.area + ", Ping:" + me.ping + ", Game: " + me.gamename + ")" + ); + filemsg = dateString + " <" + me.profile + "> " + msg.replace(/ÿc[0-9!"+<:;.*]/gi, "") + "\n"; + + if (error.hasOwnProperty("stack")) { + stack = error.stack; + + if (stack) { + stack = stack.split("\n"); + + if (stack && typeof stack === "object") { + stack.reverse(); + } + + for (let i = 0; i < stack.length; i += 1) { + if (stack[i]) { + stackLog += stack[i].substr( + 0, + stack[i].indexOf("@") + 1) + stack[i].substr(stack[i].lastIndexOf("\\") + 1, stack[i].length - 1 + ); + + if (i < stack.length - 1) { + stackLog += ", "; + } + } + } + } + } + + stackLog && (filemsg += "Stack: " + stackLog + "\n"); + } + + this.errorConsolePrint && D2Bot.printToConsole(oogmsg, sdk.colors.D2Bot.Gray); + showConsole(); + console.log(msg); + FileAction.append("logs/ScriptErrorLog.txt", filemsg); + + if (this.screenshotErrors) { + takeScreenshot(); + delay(500); + } + }, + + /** + * @param {string} msg + * @returns {void} + */ + debugLog: function (msg) { + if (!Config.Debug) return; + debugLog(me.profile + ": " + msg); + }, + + /** + * Use a NPC menu. Experimental function, subject to change + * @param {number} id - string number (with exception of Ressurect merc). + * @returns {boolean} + */ + useMenu: function (id) { + //console.log("useMenu " + getLocaleString(id)); + + let npc; + + switch (id) { + case sdk.menu.RessurectMerc: // (non-English dialog) + case sdk.menu.Trade: // (crash dialog) + npc = getInteractedNPC(); + + if (npc) { + npc.useMenu(id); + delay(750); + + return true; + } + + break; + } + + let lines = getDialogLines(); + if (!lines) return false; + + for (let i = 0; i < lines.length; i += 1) { + if (lines[i].selectable && lines[i].text.includes(getLocaleString(id))) { + getDialogLines()[i].handler(); + delay(750); + + return true; + } + } + + return false; + }, + + /** + * @template T + * @param {function(): T} check + * @param {number} [timeout=6000] + * @param {number} [sleep=40] + * @returns {T | false} + */ + poll: function (check, timeout = 6000, sleep = 40) { + let ret, start = getTickCount(); + + while (getTickCount() - start <= timeout) { + if ((ret = check())) { + return ret; + } + + delay(sleep); + } + + return false; + }, + + /** + * @param {number[]} excluded + * @returns {number[] | null} array of UI flags that are set, or null if none are set + */ + getUIFlags: function (excluded = []) { + if (!me.gameReady) return null; + + const MAX_FLAG = 37; // anything over 37 crashes + let flags = []; + + if (typeof excluded !== "object" || excluded.length === undefined) { + // not an array-like object, make it an array + excluded = [excluded]; + } + + for (let c = 1; c <= MAX_FLAG; c++) { + // 0x23 is always set in-game + if (c !== 0x23 && excluded.indexOf(c) === -1 && getUIFlag(c)) { + flags.push(c); + } + } + + return flags.length ? flags : null; + }, + + /** + * @param {number} id + * @param {number} state + * @returns {0 | 1} + */ + checkQuest: function (id, state) { + Packet.questRefresh(); + delay(500); + return me.getQuest(id, state); + }, + + /** + * @param {number} questID + * @returns {number[]} List of set quest states + */ + getQuestStates: function (questID) { + if (!me.gameReady) return []; + Packet.questRefresh(); + delay(500); + const MAX_STATE = 16; + let questStates = []; + + for (let i = 0; i < MAX_STATE; i++) { + if (me.getQuest(questID, i)) { + questStates.push(i); + } + + delay(50); + } + + return questStates; + } + }; +})(); diff --git a/d2bs/kolbot/libs/core/NPC.js b/d2bs/kolbot/libs/core/NPC.js new file mode 100644 index 000000000..b7a6c8301 --- /dev/null +++ b/d2bs/kolbot/libs/core/NPC.js @@ -0,0 +1,67 @@ +/** +* @filename NPC.js +* @author kolton, theBGuy +* @desc Handle NPC object +* +*/ + +const NPC = (new function NPC () { + this.Akara = getLocaleString(sdk.locale.npcs.Akara).toLowerCase(); + this.Gheed = getLocaleString(sdk.locale.npcs.Gheed).toLowerCase(); + this.Charsi = getLocaleString(sdk.locale.npcs.Charsi).toLowerCase(); + this.Kashya = getLocaleString(sdk.locale.npcs.Kashya).toLowerCase(); + this.Warriv = getLocaleString(sdk.locale.npcs.Warriv).toLowerCase(); + + this.Fara = getLocaleString(sdk.locale.npcs.Fara).toLowerCase(); + this.Drognan = getLocaleString(sdk.locale.npcs.Drognan).toLowerCase(); + this.Elzix = getLocaleString(sdk.locale.npcs.Elzix).toLowerCase(); + this.Greiz = getLocaleString(sdk.locale.npcs.Greiz).toLowerCase(); + this.Lysander = getLocaleString(sdk.locale.npcs.Lysander).toLowerCase(); + this.Jerhyn = getLocaleString(sdk.locale.npcs.Jerhyn).toLowerCase(); + this.Meshif = getLocaleString(sdk.locale.npcs.Meshif).toLowerCase(); + this.Atma = getLocaleString(sdk.locale.npcs.Atma).toLowerCase(); + + this.Ormus = getLocaleString(sdk.locale.npcs.Ormus).toLowerCase(); + this.Alkor = getLocaleString(sdk.locale.npcs.Alkor).toLowerCase(); + this.Hratli = getLocaleString(sdk.locale.npcs.Hratli).toLowerCase(); + this.Asheara = getLocaleString(sdk.locale.npcs.Asheara).toLowerCase(); + + this.Jamella = getLocaleString(sdk.locale.npcs.Jamella).toLowerCase(); + this.Halbu = getLocaleString(sdk.locale.npcs.Halbu).toLowerCase(); + this.Tyrael = getLocaleString(sdk.locale.npcs.Tyrael).toLowerCase(); + + this.Malah = getLocaleString(sdk.locale.npcs.Malah).toLowerCase(); + this.Anya = getLocaleString(sdk.locale.npcs.Anya).toLowerCase(); + this.Larzuk = getLocaleString(sdk.locale.npcs.Larzuk).toLowerCase(); + this.Qual_Kehk = getLocaleString(sdk.locale.npcs.QualKehk).toLowerCase(); + this.Nihlathak = getLocaleString(sdk.locale.npcs.Nihlathak2).toLowerCase(); + + this.Cain = getLocaleString(sdk.locale.npcs.DeckardCain).toLowerCase(); + + /** + * Returns the act(s) where the given NPC can be found. + * @param {string} name - The name of the NPC. + * @returns {Array} An array of act numbers where the NPC can be found. + */ + this.getAct = function (name) { + if (name === NPC.Cain) return [me.act]; + if (name === NPC.Warriv) return [1, 2]; + if (name === NPC.Meshif) return [2, 3]; + switch (true) { + case [NPC.Akara, NPC.Gheed, NPC.Charsi, NPC.Kashya, NPC.Warriv].includes(name): + return [1]; + case [NPC.Fara, NPC.Drognan, NPC.Elzix, NPC.Greiz, NPC.Lysander, NPC.Jerhyn, NPC.Atma].includes(name): + return [2]; + case [NPC.Ormus, NPC.Alkor, NPC.Hratli, NPC.Asheara].includes(name): + return [3]; + case [NPC.Jamella, NPC.Halbu, NPC.Tyrael].includes(name): + return [4]; + case [NPC.Malah, NPC.Anya, NPC.Larzuk, NPC.Qual_Kehk, NPC.Nihlathak].includes(name): + return [5]; + } + return []; + }; + Object.defineProperty(this, "getAct", { + enumerable: false, + }); +}); diff --git a/d2bs/kolbot/libs/core/NTItemParser.js b/d2bs/kolbot/libs/core/NTItemParser.js new file mode 100644 index 000000000..8234bfd03 --- /dev/null +++ b/d2bs/kolbot/libs/core/NTItemParser.js @@ -0,0 +1,653 @@ +/* eslint-disable max-len */ +/** +* @filename NTItemParser.js +* @author kolton, jaenster +* @credit d2nt +* @desc nip file parser for kolbots pickit system +* +* +* @Item-parser Syntax Information +* 1. [Keyword] separates into two groups +* - [Property Keywords] : [Type], [Name], [Class], [Quality], [Flag], [Level], [Prefix], [Suffix] +* - [Stat Keywords] : [Number or Alias] +* 2. [Keyword] must be surrounded by '[' and ']' +* 3. [Property Keywords] must be placed first +* 4. Insert '#' symbol between [Property Keywords] and [Stat Keywords] +* 5. Use '+', '-', '*', '/', '(', ')', '&&', '||', '>', '>=', '<', '<=', '==', '!=' symbols for comparison +* 6. Use '//' symbol for comment +* +* @example: [name] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 // Perfect Raven Frost +* +*/ + +includeIfNotIncluded("core/Prototypes.js"); +includeIfNotIncluded("core/GameData/NTItemAlias.js"); + +/** + * @todo clean up this file + */ + +const NTIP = {}; +const NTIP_CheckList = []; +const NTIP_CheckListNoTier = []; +let stringArray = []; + +NTIP.addLine = function (itemString, filename = "kolbot") { + const tierdItem = itemString.toLowerCase().includes("tier"); + const info = { + line: NTIP_CheckList.length + 1, + file: filename, + string: itemString + }; + + const line = NTIP.ParseLineInt(itemString, info); + + if (line) { + NTIP_CheckList.push(line); + + if (!tierdItem) { + NTIP_CheckListNoTier.push(line); + } + + stringArray.push(info); + } + + return true; +}; + +NTIP.OpenFile = function (filepath, notify) { + if (!FileTools.exists(filepath)) { + if (notify) { + Misc.errorReport("ÿc1NIP file doesn't exist: ÿc0" + filepath); + } + + return false; + } + + let nipfile; + let tick = getTickCount(); + let entries = 0; + const filename = filepath.substring(filepath.lastIndexOf("/") + 1, filepath.length); + + try { + nipfile = File.open(filepath, 0); + } catch (fileError) { + if (notify) { + Misc.errorReport("ÿc1Failed to load NIP: ÿc0" + filename); + } + } + + if (!nipfile) { + return false; + } + + let lines = nipfile.readAllLines(); + nipfile.close(); + + for (let i = 0; i < lines.length; i++) { + const info = { + line: i + 1, + file: filename, + string: lines[i] + }; + + let line = NTIP.ParseLineInt(lines[i], info); + + if (line) { + entries++; + + NTIP_CheckList.push(line); + + if (!lines[i].toLowerCase().match("tier")) { + NTIP_CheckListNoTier.push(line); + } + + stringArray.push(info); + } + } + + if (notify) { + console.log( + "ÿc4Loaded NIP: ÿc2" + filename + "ÿc4. Lines: ÿc2" + lines.length + + "ÿc4. Valid entries: ÿc2" + entries + + ". ÿc4Time: ÿc2" + (getTickCount() - tick) + " ms" + ); + } + + return true; +}; + +NTIP.CheckQuantityOwned = function (item_type, item_stats) { + let num = 0; + let items = me.getItemsEx(); + + if (!items.length) { + print("I can't find my items!"); + + return 0; + } + + for (let item of items) { + if (item.mode === sdk.items.mode.inStorage + && item.location === sdk.storage.Stash) { + if ((item_type !== null && typeof item_type === "function" && item_type(item)) || item_type === null) { + if ((item_stats !== null && typeof item_stats === "function" && item_stats(item)) || item_stats === null) { + num += 1; + } + } + } else if (item.mode === sdk.items.mode.inStorage + && item.location === sdk.storage.Inventory) { // inv check + if ((item_type !== null && typeof item_type === "function" && item_type(item)) || item_type === null) { + if ((item_stats !== null && typeof item_stats === "function" && item_stats(item)) || item_stats === null) { + num += 1; + } + } + } + } + + //print("I have "+num+" of these."); + + return num; +}; + +NTIP.Clear = function () { + NTIP_CheckList.length = 0; + NTIP_CheckListNoTier.length = 0; + stringArray = []; +}; + +/** + * @param {string} tierType + * @returns {(item: ItemUnit) => number} + */ +NTIP.generateTierFunc = function (tierType) { + return function (item) { + let tier = -1; + + const updateTier = (wanted) => { + const tmpTier = wanted[tierType](item); + + if (tier < tmpTier) { + tier = tmpTier; + } + }; + + // Go through ALL lines that describe the item + for (let i = 0; i < NTIP_CheckList.length; i += 1) { + if (NTIP_CheckList[i].length !== 3) { + continue; + } + + let [type, stat, wanted] = NTIP_CheckList[i]; + + // If the line doesnt have a tier of this type, we dont need to call it + if (typeof wanted === "object" && wanted && typeof wanted[tierType] === "function") { + try { + if (typeof type === "function") { + if (type(item)) { + if (typeof stat === "function") { + if (stat(item)) { + updateTier(wanted); + } + } else { + updateTier(wanted); + } + } + } else if (typeof stat === "function") { + if (stat(item)) { + updateTier(wanted); + } + } + } catch (e) { + const info = stringArray[i]; + Misc.errorReport("ÿc1Pickit Tier (" + tierType + ") error! Line # ÿc2" + info.line + " ÿc1Entry: ÿc0" + info.string + " (" + info.file + ") Error message: " + e.message); + } + } + } + + return tier; + }; +}; + +/** + * @function + * @param {ItemUnit} item + * @returns {number} + */ +NTIP.GetTier = NTIP.generateTierFunc("Tier"); + +/** + * @function + * @param {ItemUnit} item + * @returns {number} + */ +NTIP.GetMercTier = NTIP.generateTierFunc("Merctier"); + +/** + * @param {ItemUnit} item + * @param {[(item) => Boolean, (item) => Boolean, (item) => Boolean]} entryList + * @param {boolean} verbose + */ +NTIP.CheckItem = function (item, entryList, verbose) { + let i, num; + let rval = {}; + let result = 0; + + const list = entryList + ? entryList + : NTIP_CheckList; + const identified = item.getFlag(sdk.items.flags.Identified); + + for (i = 0; i < list.length; i++) { + try { + // Get the values in separated variables (its faster) + const [type, stat, wanted] = list[i]; + + if (typeof type === "function") { + if (type(item)) { + if (typeof stat === "function") { + if (stat(item)) { + if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) { + num = NTIP.CheckQuantityOwned(type, stat); + + if (num < wanted.MaxQuantity) { + result = 1; + + break; + } else { + // attempt at inv fix for maxquantity + if (item.getParent() && item.getParent().name === me.name && item.isInStorage && num === wanted.MaxQuantity) { + result = 1; + + break; + } + } + } else { + result = 1; + + break; + } + } else if (!identified && result === 0) { + result = -1; + verbose && (rval.line = stringArray[i].file + " #" + stringArray[i].line); + } + } else { + if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) { + num = NTIP.CheckQuantityOwned(type, null); + + if (num < wanted.MaxQuantity) { + result = 1; + + break; + } else { + // attempt at inv fix for maxquantity + if (item.getParent() && item.getParent().name === me.name && item.isInStorage && num === wanted.MaxQuantity) { + result = 1; + + break; + } + } + } else { + result = 1; + + break; + } + } + } + } else if (typeof stat === "function") { + if (stat(item)) { + if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) { + num = NTIP.CheckQuantityOwned(null, stat); + + if (num < wanted.MaxQuantity) { + result = 1; + + break; + } else { + // attempt at inv fix for maxquantity + if (item.getParent() && item.getParent().name === me.name && item.isInStorage && num === wanted.MaxQuantity) { + result = 1; + + break; + } + } + } else { + result = 1; + + break; + } + } else if (!identified && result === 0) { + result = -1; + verbose && (rval.line = stringArray[i].file + " #" + stringArray[i].line); + } + } + } catch (pickError) { + showConsole(); + + if (!entryList) { + Misc.errorReport("ÿc1Pickit error! Line # ÿc2" + stringArray[i].line + " ÿc1Entry: ÿc0" + stringArray[i].string + " (" + stringArray[i].file + ") Error message: " + pickError.message + " Trigger item: " + item.fname.split("\n").reverse().join(" ")); + + NTIP_CheckList.splice(i, 1); // Remove the element from the list + } else { + Misc.errorReport("ÿc1Pickit error in runeword config!"); + } + + result = 0; + } + } + + if (verbose) { + switch (result) { + case -1: + break; + case 1: + rval.line = stringArray[i].file + " #" + stringArray[i].line; + + break; + default: + rval.line = null; + + break; + } + + rval.result = result; + + return rval; + } + + return result; +}; + +/** @param {string} ch */ +NTIP.IsSyntaxInt = function (ch) { + return ( + ch === "!" + || ch === "%" + || ch === "&" + || (ch >= "(" && ch <= "+") + || ch === "-" + || ch === "/" + || (ch >= ":" && ch <= "?") + || ch === "|" + ); +}; + +NTIP.parseAliasIn = { + in: "\[([^\]]+)\]in\(", + notin: "\[([^\]]+)\]notin\(", + /** @private */ + _regex: new RegExp(/\[([^\]]+)\](in|notin)\(/gi), + /** + * @param {string} input + * @returns {boolean} + */ + test: function (input) { + this._regex.lastIndex = 0; + return this._regex.test(input); + }, + /** + * @param {string} input + * @returns {string} + */ + convert: function (input) { + const regex = new RegExp(/\[([^\]]+)\](in|notin)\(([^)]+)\)/g); + let match; + let result = input; + while ((match = regex.exec(input)) !== null) { + if (match.index === regex.lastIndex) { + regex.lastIndex++; + } + const [_full, property, type, values] = match; + if (!property || !values) throw new Error("Invalid syntax"); + const alias = "(" + values.split(",") + .filter(function (el) { + return el.trim().length > 0; + }) + .map(function (el) { + return "[" + property + "]" + (type === "in" ? "==" : "!=") + el.trim(); + }) + .join(type === "in" ? "||" : "&&") + + ")"; + result = result.replace(match[0], alias); + } + return result; + } +}; + +NTIP._props = new Map([ + ["classid", "item.classid"], + ["name", "item.classid"], + ["type", "item.itemType"], + ["class", "item.itemclass"], + ["quality", "item.quality"], + ["charlvl", "me.charlvl"], + ["level", "item.ilvl"], + ["flag", "item.getFlag("], + ["wsm", 'getBaseStat("items", item.classid, "speed")'], + ["weaponspeed", 'getBaseStat("items", item.classid, "speed")'], + ["minimumsockets", 'getBaseStat("items", item.classid, "gemsockets")'], + ["strreq", "item.strreq"], + ["dexreq", "item.dexreq"], + ["2handed", 'getBaseStat("items", item.classid, "2handed")'], + ["color", "item.getColor()"], + ["europe", '("' + me.realm.toLowerCase() + '"===" europe")'], + ["uswest", '("' + me.realm.toLowerCase() + '"===" uswest")'], + ["useast", '("' + me.realm.toLowerCase() + '"===" useast")'], + ["asia", '("' + me.realm.toLowerCase() + '"===" asia")'], + ["ladder", "me.ladder"], + ["hardcore", "(!!me.playertype)"], + ["classic", "(!me.gametype)"], + ["distance", "(item.onGroundOrDropping && item.distance || Infinity)"], + ["prefix", "item.getPrefix("], + ["suffix", "item.getSuffix("] +]); + +NTIP._aliases = new Map([ + ["n", "name"], + ["id", "classid"], + ["t", "type"], + ["q", "quality"], + ["lvl", "level"], + ["ilvl", "level"], + ["f", "flag"], + ["hc", "hardcore"], + ["cl", "classic"], + ["clvl", "charlvl"], +]); + +NTIP._lists = new Map([ + ["color", NTIPAliasColor], + ["type", NTIPAliasType], + ["name", NTIPAliasClassID], + ["classid", NTIPAliasClassID], + ["class", NTIPAliasClass], + ["quality", NTIPAliasQuality], + ["flag", NTIPAliasFlag], + ["stat", NTIPAliasStat], +]); + +NTIP.ParseLineInt = function (input, info) { + let i, property, p_start, p_end, p_section, p_keyword, p_result, value; + + p_end = input.indexOf("//"); + + if (p_end !== -1) { + input = input.substring(0, p_end); + } + + input = input.replace(/\s+/g, "").toLowerCase(); + + if (input.length < 5) { + return null; + } + + p_result = input.split("#"); + + try { + if (p_result[0] && p_result[0].length > 4) { + if (NTIP.parseAliasIn.test(p_result[0])) { + p_result[0] = NTIP.parseAliasIn.convert(p_result[0]); + } + p_section = p_result[0].split("["); + p_result[0] = p_section[0]; + + for (i = 1; i < p_section.length; i += 1) { + p_end = p_section[i].indexOf("]") + 1; + property = p_section[i].substring(0, p_end - 1); + + if (NTIP._aliases.has(property)) { + property = NTIP._aliases.get(property); + } + + switch (property) { + case "flag": + case "prefix": + case "suffix": + if (p_section[i][p_end] === "!") { + p_result[0] += "!" + NTIP._props.get(property); + } else { + p_result[0] += NTIP._props.get(property); + } + + p_end += 2; + + break; + default: + if (!NTIP._props.has(property)) { + throw new Error("Unknown property: " + property + " File: " + info.file + " Line: " + info.line); + } + p_result[0] += NTIP._props.get(property); + } + + for (p_start = p_end; p_end < p_section[i].length; p_end += 1) { + if (!NTIP.IsSyntaxInt(p_section[i][p_end])) { + break; + } + } + + p_result[0] += p_section[i].substring(p_start, p_end); + + if (p_section[i].substring(p_start, p_end) === "=") { + throw new Error("Unexpected = at line " + info.line + " in " + info.file); + } + + for (p_start = p_end; p_end < p_section[i].length; p_end += 1) { + if (NTIP.IsSyntaxInt(p_section[i][p_end])) { + break; + } + } + + p_keyword = p_section[i].substring(p_start, p_end); + + if (isNaN(p_keyword)) { + switch (property) { + case "prefix": + case "suffix": + p_result[0] += "\"" + p_keyword + "\")"; + + break; + default: + if (!NTIP._lists.has(property)) { + throw new Error("Unknown property: " + property + " File: " + info.file + " Line: " + info.line); + } else if (NTIP._lists.get(property)[p_keyword] === undefined) { + throw new Error("Unknown " + property + ": " + p_keyword + " File: " + info.file + " Line: " + info.line); + } + p_result[0] += NTIP._lists.get(property)[p_keyword]; + property === "flag" && (p_result[0] += ")"); + + break; + } + } else { + if (property === "flag" || property === "prefix" || property === "suffix") { + p_result[0] += p_keyword + ")"; + } else { + p_result[0] += p_keyword; + } + } + + p_result[0] += p_section[i].substring(p_end); + } + } else { + p_result[0] = ""; + } + + if (p_result[1] && p_result[1].length > 4) { + p_section = p_result[1].split("["); + p_result[1] = p_section[0]; + + for (i = 1; i < p_section.length; i += 1) { + p_end = p_section[i].indexOf("]"); + p_keyword = p_section[i].substring(0, p_end); + + if (isNaN(p_keyword)) { + if (NTIPAliasStat[p_keyword] === undefined) { + throw new Error("Unknown stat: " + p_keyword + " File: " + info.file + " Line: " + info.line); + } + + p_result[1] += "item.getStatEx(" + NTIPAliasStat[p_keyword] + ")"; + } else { + p_result[1] += "item.getStatEx(" + p_keyword + ")"; + } + + p_result[1] += p_section[i].substring(p_end + 1); + } + } else { + p_result[1] = ""; + } + + if (p_result[2] && p_result[2].length > 0) { + p_section = p_result[2].split("["); + p_result[2] = {}; + + for (i = 1; i < p_section.length; i += 1) { + p_end = p_section[i].indexOf("]"); + p_keyword = p_section[i].substring(0, p_end); + + let keyword = p_keyword.toLowerCase(); + switch (keyword) { + case "mq": + case "maxquantity": + value = Number(p_section[i].split("==")[1].match(/\d+/g)); + + if (!isNaN(value)) { + p_result[2].MaxQuantity = value; + } + + break; + case "merctier": + case "tier": + try { + // p_result[2].Tier = function(item) { return value }; + p_result[2][keyword.charAt(0).toUpperCase() + keyword.slice(1)] = (new Function("return function(item) { return " + p_section[i].split("==")[1] + ";}")).call(null); // generate function out of + } catch (e) { + throw new Error("ÿc1Pickit Tier (" + keyword + ") error! Line # ÿc2" + info.line + " ÿc1Entry: ÿc0" + info.string + " (" + info.file + ") Error message: " + e.message); + } + break; + default: + throw new Error("Unknown 3rd part keyword: " + p_keyword.toLowerCase() + " File: " + info.file + " Line: " + info.line); + } + } + } + } catch (e) { + Misc.errorReport(e); + + return false; + } + + // Compile the line, to 1) remove the eval lines, and 2) increase the speed + for (let i = 0; i < 2; i++) { + if (p_result[i].length) { + try { + p_result[i] = (new Function("return function(item) { return " + p_result[i] + ";}")).call(null); // generate function out of + } catch (e) { + Misc.errorReport("ÿc1Pickit error! Line # ÿc2" + info.line + " ÿc1Entry: ÿc0" + info.string + " (" + info.file + ") Error message: " + e.message); + + return null; // failed load this line so return false + } + } else { + p_result[i] = undefined; + } + + } + return p_result; +}; diff --git a/d2bs/kolbot/libs/core/Packet.js b/d2bs/kolbot/libs/core/Packet.js new file mode 100644 index 000000000..39d3425a1 --- /dev/null +++ b/d2bs/kolbot/libs/core/Packet.js @@ -0,0 +1,615 @@ +/** +* @filename Packet.js +* @author kolton, theBGuy +* @desc handle packet based functions +* +*/ + +const Packet = { + /** + * Interact and open the menu of an NPC + * @deprecated there was only one line difference between this and Unit.openMenu + * added the line to Unit.openMenu to save defining this function + * @param {NPCUnit} unit + * @returns {boolean} + */ + openMenu: function (unit) { + if (unit.type !== sdk.unittype.NPC) throw new Error("openMenu: Must be used on NPCs."); + if (getUIFlag(sdk.uiflags.NPCMenu)) return true; + let pingDelay = (me.gameReady ? me.ping : 125); + + for (let i = 0; i < 5; i += 1) { + if (getDistance(me, unit) > 4) { + Pather.moveNearUnit(unit, 4); + } + Packet.entityInteract(unit); + let tick = getTickCount(); + + while (getTickCount() - tick < 5000) { + if (getUIFlag(sdk.uiflags.NPCMenu)) { + delay(Math.max(500, pingDelay * 2)); + + return true; + } + + if ((getTickCount() - tick > 1000 + && getInteractedNPC()) || (getTickCount() - tick > 500 && getIsTalkingNPC())) { + me.cancel(); + } + + delay(100); + } + + new PacketBuilder() + .byte(sdk.packets.send.NPCInit) + .dword(1) + .dword(unit.gid) + .send(); + delay(pingDelay + 1 * 2); + Packet.cancelNPC(unit); + delay(pingDelay + 1 * 2); + this.flash(me.gid); + } + + return false; + }, + + /** + * Start a trade action with an NPC + * @param {NPCUnit} unit + * @param {number} mode + * @returns {boolean} + */ + startTrade: function (unit, mode) { + if (unit.type !== sdk.unittype.NPC) throw new Error("Unit.startTrade: Must be used on NPCs."); + if (getUIFlag(sdk.uiflags.Shop)) return true; + + const gamble = mode === "Gamble"; + console.info(true, mode + " at " + unit.name); + + if (unit.openMenu()) { + for (let i = 0; i < 10; i += 1) { + delay(200); + + if (i % 2 === 0) { + new PacketBuilder() + .byte(sdk.packets.send.EntityAction) + .dword(gamble ? 2 : 1) + .dword(unit.gid) + .dword(0) + .send(); + } + + if (unit.itemcount > 0) { + delay(200); + console.info(false, "Successfully started " + mode + " at " + unit.name); + return true; + } + } + } + + return false; + }, + + /** + * Buy an item from an interacted NPC + * @param {NPCUnit} unit + * @param {boolean} shiftBuy + * @param {boolean} gamble + * @returns {boolean} + */ + buyItem: function (unit, shiftBuy, gamble) { + const oldGold = me.gold; + const itemCount = me.itemcount; + let npc = getInteractedNPC(); + + try { + if (!npc) throw new Error("buyItem: No NPC menu open."); + + // Can we afford the item? + if (oldGold < unit.getItemCost(sdk.items.cost.ToBuy)) return false; + + for (let i = 0; i < 3; i += 1) { + new PacketBuilder() + .byte(sdk.packets.send.NPCBuy) + .dword(npc.gid) + .dword(unit.gid) + .dword(shiftBuy ? 0x80000000 : gamble ? 0x2 : 0x0) + .dword(0) + .send(); + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(2000, me.ping * 2 + 500)) { + if (shiftBuy && me.gold < oldGold) return true; + if (itemCount !== me.itemcount) return true; + + delay(10); + } + } + } catch (e) { + console.error(e); + } + + return false; + }, + + /** + * Buy scrolls from an interacted NPC, we need this as a seperate check because itemcount doesn't change + * if the scroll goes into the tome automatically. + * @param {NPCUnit} unit + * @param {ItemUnit} [tome] + * @param {boolean} [shiftBuy] + * @returns {boolean} + */ + buyScroll: function (unit, tome, shiftBuy) { + let oldGold = me.gold; + let itemCount = me.itemcount; + let npc = getInteractedNPC(); + tome === undefined && (tome = me.findItem( + (unit.classid === sdk.items.ScrollofTownPortal ? sdk.items.TomeofTownPortal : sdk.items.TomeofIdentify), + sdk.items.mode.inStorage, sdk.storage.Inventory + )); + let preCount = !!tome ? tome.getStat(sdk.stats.Quantity) : 0; + + try { + if (!npc) throw new Error("buyItem: No NPC menu open."); + + // Can we afford the item? + if (oldGold < unit.getItemCost(sdk.items.cost.ToBuy)) return false; + + for (let i = 0; i < 3; i += 1) { + new PacketBuilder() + .byte(sdk.packets.send.NPCBuy) + .dword(npc.gid) + .dword(unit.gid) + .dword(shiftBuy ? 0x80000000 : 0x0) + .dword(0) + .send(); + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(2000, me.ping * 2 + 500)) { + if (shiftBuy && me.gold < oldGold) return true; + if (itemCount !== me.itemcount) return true; + if (tome && tome.getStat(sdk.stats.Quantity) > preCount) return true; + delay(10); + } + } + } catch (e) { + console.error(e); + } + + return false; + }, + + /** + * Sell a item to a NPC + * @param {ItemUnit} unit + * @returns {boolean} + */ + sellItem: function (unit) { + // Check if it's an item we want to buy + if (unit.type !== sdk.unittype.Item) throw new Error("Unit.sell: Must be used on items."); + if (!unit.sellable) { + console.error((new Error("Item is unsellable"))); + return false; + } + + let itemCount = me.itemcount; + let npc = getInteractedNPC(); + if (!npc) return false; + let _npcs = Town.tasks.get(me.act); + if (![_npcs.Shop, _npcs.Gamble, _npcs.Repair, _npcs.Key].includes(npc.name.toLowerCase())) { + console.warn("Unit.sell: NPC is not a shop, gamble, repair or key NPC."); + return false; + } + + for (let i = 0; i < 5; i += 1) { + new PacketBuilder() + .byte(sdk.packets.send.NPCSell) + .dword(npc.gid) + .dword(unit.gid) + .dword(0) + .dword(0) + .send(); + let tick = getTickCount(); + + while (getTickCount() - tick < 2000) { + if (me.itemcount !== itemCount) return true; + delay(10); + } + } + + return false; + }, + + /** + * @param {ItemUnit} unit + * @param {ItemUnit} tome + * @returns {boolean} + */ + identifyItem: function (unit, tome) { + if (!unit || unit.identified) return false; + const identify = function () { + new PacketBuilder() + .byte(sdk.packets.send.IndentifyItem) + .dword(unit.gid) + .dword(tome.gid) + .send(); + }; + const idOnCursor = function () { + return getCursorType() === sdk.cursortype.Identify; + }; + const unitIdentified = function () { + return unit.identified; + }; + + for (let i = 0; i < 3; i += 1) { + identify(); + if (Misc.poll(idOnCursor, 2000, 10)) { + break; + } + } + + if (!idOnCursor()) return false; + + for (let i = 0; i < 3; i += 1) { + idOnCursor() && identify(); + if (Misc.poll(unitIdentified, 2000, 10)) { + delay(25); + + return true; + } + } + + return false; + }, + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + itemToCursor: function (item) { + // Something already on cursor + if (me.itemoncursor) { + let cursorItem = Game.getCursorUnit(); + // Return true if the item is already on cursor + if (cursorItem.gid === item.gid) { + return true; + } + this.dropItem(cursorItem); // If another item is on cursor, drop it + } + + for (let i = 0; i < 15; i += 1) { + // equipped + item.isEquipped + ? sendPacket(1, sdk.packets.send.PickupBodyItem, 2, item.bodylocation) + : item.isInBelt + ? new PacketBuilder().byte(sdk.packets.send.RemoveBeltItem).dword(item.gid).send() + : sendPacket(1, sdk.packets.send.PickupBufferItem, 4, item.gid); + + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(500, me.ping * 2 + 200)) { + if (me.itemoncursor) return true; + delay(10); + } + } + + return false; + }, + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + dropItem: function (item) { + if (!this.itemToCursor(item)) return false; + + for (let i = 0; i < 15; i += 1) { + new PacketBuilder() + .byte(sdk.packets.send.DropItem) + .dword(item.gid) + .send(); + const tick = getTickCount(); + + while (getTickCount() - tick < Math.max(500, me.ping * 2 + 200)) { + if (!me.itemoncursor) return true; + delay(10); + } + } + + return false; + }, + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + givePotToMerc: function (item) { + if (!item) return false; + if (![ + sdk.items.type.HealingPotion, sdk.items.type.RejuvPotion, + sdk.items.type.ThawingPotion, sdk.items.type.AntidotePotion + ].includes(item.itemType)) { + return false; + } + if (item.isInBelt) return this.useBeltItemForMerc(item); + if (item.isInInventory && this.itemToCursor(item)) { + new PacketBuilder() + .byte(sdk.packets.send.MercItem) + .word(0) + .send(); + return true; + } + return false; + }, + + /** + * @param {ItemUnit} item + * @param {number} xLoc + * @returns {boolean} + */ + placeInBelt: function (item, xLoc) { + if (item.toCursor(true)) { + new PacketBuilder() + .byte(sdk.packets.send.ItemToBelt) + .dword(item.gid) + .dword(xLoc) + .send(); + } + return Misc.poll(function () { + return item.isInBelt; + }, 500, 100); + }, + + /** + * @param {ItemUnit} who + * @param {boolean} toCursor + * @returns {boolean} + */ + click: function (who, toCursor = false) { + if (!who || !copyUnit(who).x) return false; + new PacketBuilder() + .byte(sdk.packets.send.PickupItem) + .dword(sdk.unittype.Item) + .dword(who.gid) + .dword(toCursor ? 1 : 0) + .send(); + return true; + }, + + /** + * @param {Unit} who + * @returns {boolean} + */ + entityInteract: function (who) { + if (!who || !copyUnit(who).x) return false; + new PacketBuilder() + .byte(sdk.packets.send.InteractWithEntity) + .dword(who.type) + .dword(who.gid) + .send(); + return true; + }, + + /** + * @param {NPCUnit} who + * @returns {boolean} + */ + initNPC: function (who) { + if (!who || !copyUnit(who).x) return false; + new PacketBuilder() + .byte(sdk.packets.send.NPCInit) + .dword(1) // action type + .dword(who.gid) + .send(); + return true; + }, + + /** + * @param {NPCUnit} who + * @returns {boolean} + */ + cancelNPC: function (who) { + if (!who || !copyUnit(who).x) return false; + new PacketBuilder() + .byte(sdk.packets.send.NPCCancel) + .dword(who.type) + .dword(who.gid) + .send(); + return true; + }, + + /** + * @param {ItemUnit} pot + * @returns {boolean} + */ + useBeltItemForMerc: function (pot) { + if (!pot) return false; + new PacketBuilder() + .byte(sdk.packets.send.UseBeltItem) + .dword(pot.gid) + .dword(1) + .dword(0) + .send(); + return true; + }, + + castSkill: function (hand, wX, wY) { + hand = (hand === sdk.skills.hand.Right) + ? sdk.packets.send.RightSkillOnLocation + : sdk.packets.send.LeftSkillOnLocation; + new PacketBuilder() + .byte(hand) + .word(wX) + .word(wY) + .send(); + }, + + castAndHoldSkill: function (hand, wX, wY, duration = 1000) { + /** @param {number} byte */ + const cast = function (byte) { + new PacketBuilder() + .byte(byte) + .word(wX) + .word(wY) + .send(); + }; + const nHand = (hand === sdk.skills.hand.Right) + ? sdk.packets.send.RightSkillOnLocation + : sdk.packets.send.LeftSkillOnLocation; + hand = (hand === sdk.skills.hand.Right) + ? sdk.packets.send.RightSkillOnLocationEx + : sdk.packets.send.LeftSkillOnLocationEx; + + const endTime = getTickCount() + duration; + // has to be cast normally first with a click before held packet is sent + cast(nHand); + while (getTickCount() < endTime) { + cast(hand); + delay(25); + } + }, + + /** + * @param {number} hand + * @param {Monster | ItemUnit | ObjectUnit} who + * @returns {boolean} + */ + unitCast: function (hand, who) { + hand = (hand === sdk.skills.hand.Right) + ? sdk.packets.send.RightSkillOnEntityEx3 + : sdk.packets.send.LeftSkillOnEntityEx3; + new PacketBuilder() + .byte(hand) + .dword(who.type) + .dword(who.gid) + .send(); + }, + + /** + * @param {Monster | ItemUnit | ObjectUnit} who + * @returns {boolean} + */ + telekinesis: function (who) { + if (!who || !Skill.setSkill(sdk.skills.Telekinesis, sdk.skills.hand.Right)) return false; + if (Skill.getManaCost(sdk.skills.Telekinesis) > me.mp) return false; + if (me.shapeshifted) return false; + new PacketBuilder() + .byte(sdk.packets.send.RightSkillOnEntityEx3) + .dword(who.type) + .dword(who.gid) + .send(); + return true; + }, + + /** + * @param {Player | Monster | MercUnit} who + * @returns {boolean} + */ + enchant: function (who) { + if (!who || !Skill.setSkill(sdk.skills.Enchant, sdk.skills.hand.Right)) return false; + new PacketBuilder() + .byte(sdk.packets.send.RightSkillOnEntityEx3) + .dword(who.type) + .dword(who.gid) + .send(); + return true; + }, + + /** + * @param {number} wX + * @param {number} wY + * @returns {boolean} + */ + teleport: function (wX, wY) { + if (![wX, wY].every(n => typeof n === "number")) return false; + if (!Skill.setSkill(sdk.skills.Teleport, sdk.skills.hand.Right)) return false; + new PacketBuilder() + .byte(sdk.packets.send.RightSkillOnLocation) + .word(wX) + .word(wY) + .send(); + return true; + }, + + // moveNPC: function (npc, dwX, dwY) { // commented the patched packet + // //sendPacket(1, sdk.packets.send.MakeEntityMove, 4, npc.type, 4, npc.gid, 4, dwX, 4, dwY); + // }, + + /** + * @deprecated + * @param {number} x + * @param {number} y + * @param {number} maxDist + * @returns {boolean} + */ + teleWalk: function (x, y, maxDist = 5) { + !Packet.telewalkTick && (Packet.telewalkTick = 0); + + if (getDistance(me, x, y) > 10 && getTickCount() - this.telewalkTick > 3000 && Attack.validSpot(x, y)) { + for (let i = 0; i < 5; i += 1) { + sendPacket(1, sdk.packets.send.UpdatePlayerPos, 2, x + rand(-1, 1), 2, y + rand(-1, 1)); + delay(me.ping + 1); + sendPacket(1, sdk.packets.send.RequestEntityUpdate, 4, me.type, 4, me.gid); + delay(me.ping + 1); + + if (getDistance(me, x, y) < maxDist) { + delay(200); + + return true; + } + } + + Packet.telewalkTick = getTickCount(); + } + + return false; + }, + + questRefresh: function () { + sendPacket(1, sdk.packets.send.UpdateQuests); + }, + + /** + * Request entity update + * @param {number} gid + * @param {number} wait + */ + flash: function (gid, wait = 0) { + wait === 0 && (wait = 300 + (me.gameReady ? 2 * me.ping : 300)); + sendPacket(1, sdk.packets.send.RequestEntityUpdate, 4, 0, 4, gid); + + if (wait > 0) { + delay(wait); + } + }, + + /** + * @deprecated + * @param {number} stat + * @param {number} value + */ + changeStat: function (stat, value) { + if (value > 0) { + getPacket(1, 0x1d, 1, stat, 1, value); + } + }, + + // specialized wrapper for addEventListener + addListener: function (packetType, callback) { + if (typeof packetType === "number") { + packetType = [packetType]; + } + + if (typeof packetType === "object" && packetType.length) { + addEventListener("gamepacket", packet => (packetType.indexOf(packet[0]) > -1 ? callback(packet) : false)); + + return callback; + } + + return null; + }, + + removeListener: callback => removeEventListener("gamepacket", callback), // just a wrapper +}; diff --git a/d2bs/kolbot/libs/core/Pather.js b/d2bs/kolbot/libs/core/Pather.js new file mode 100644 index 000000000..00487788b --- /dev/null +++ b/d2bs/kolbot/libs/core/Pather.js @@ -0,0 +1,2255 @@ +/** +* @filename Pather.js +* @author kolton, theBGuy +* @desc handle player movement +* +*/ + +/** + * @constructor + * @param {number} x + * @param {number} y + */ +function PathNode (x, y) { + this.x = x; + this.y = y; +} + +/** + * Perform certain actions after moving to each node + * @todo this needs to be re-worked + */ +const NodeAction = { + /** @type {number[]} */ + shrinesToIgnore: [], + enabled: true, + + /** + * Run all the functions within NodeAction (except for itself) + * @param {clearSettings} arg + */ + go: function (arg) { + if (!this.enabled) return; + for (let i in this) { + if (this.hasOwnProperty(i) && typeof this[i] === "function" && i !== "go") { + this[i](arg); + } + } + }, + + /** + * Kill monsters while pathing + * @param {clearSettings} arg + * @returns {void} + */ + killMonsters: function (arg = {}) { + if (arg.hasOwnProperty("allowClearing") && !arg.allowClearing) return; + + const killSettings = Object.assign({}, { + clearPath: false, + specType: sdk.monsters.spectype.All, + range: 10, + overrideConfig: false, + }, arg); + + if (Config.Countess.KillGhosts && me.act === 1) { + if (me.area >= sdk.areas.TowerCellarLvl1 && me.area <= sdk.areas.TowerCellarLvl5) { + let monList = (Attack.getMob(sdk.monsters.Ghost1, sdk.monsters.spectype.All, 30) || []); + monList.length > 0 && Attack.clearList(monList); + } + } + + if ((typeof Config.ClearPath === "number" || typeof Config.ClearPath === "object") + && killSettings.clearPath === false && !killSettings.overrideConfig) { + switch (typeof Config.ClearPath) { + case "number": + Attack.clear(30, Config.ClearPath); + + break; + case "object": + if (!Config.ClearPath.hasOwnProperty("Areas") + || !Config.ClearPath.Areas.length + || Config.ClearPath.Areas.includes(me.area)) { + Attack.clear(Config.ClearPath.Range, Config.ClearPath.Spectype); + } + + break; + } + + return; + } + + if (killSettings.clearPath !== false) { + Attack.clear(killSettings.range, killSettings.specType); + } + }, + + /** + * Open chests while pathing + */ + popChests: function () { + // fastPick check? should only open chests if surrounding monsters have been cleared or if fastPick is active + // note: clear of surrounding monsters of the spectype we are set to clear + Config.OpenChests.Enabled && Misc.openChests(Config.OpenChests.Range); + }, + + /** + * Scan shrines while pathing + */ + getShrines: function () { + if (Config.AutoShriner) { + Misc.shriner(); + } else if (Config.ScanShrines.length > 0) { + Misc.scanShrines(null, this.shrinesToIgnore); + } + } +}; + +const PathDebug = { + /** @type {Line[]} */ + hooks: [], + enableHooks: false, + + /** + * Draw our path on the screen + * @param {PathNode[]} path + * @returns {void} + */ + drawPath: function (path) { + if (!this.enableHooks) return; + + this.removeHooks(); + + if (path.length < 2) return; + + for (let i = 0; i < path.length - 1; i += 1) { + this.hooks.push(new Line(path[i].x, path[i].y, path[i + 1].x, path[i + 1].y, 0x84, true)); + } + }, + + removeHooks: function () { + for (let i = 0; i < this.hooks.length; i += 1) { + PathDebug.hooks[i].remove(); + } + + PathDebug.hooks = []; + }, + + /** + * Check if a set of coords are a set path + * @param {PathNode[]} path + * @param {number} x + * @param {number} y + * @returns {boolean} + */ + coordsInPath: function (path, x, y) { + for (let i = 0; i < path.length; i += 1) { + if (getDistance(x, y, path[i].x, path[i].y) < 5) { + return true; + } + } + + return false; + } +}; + +// todo - test path generating in a dedicated thread to prevent lagging main thread +const Pather = { + initialized: false, + teleport: true, + recursion: true, + walkDistance: 5, + teleDistance: 40, + lastPortalTick: 0, + cancelFlags: [ + sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, + sdk.uiflags.SkillWindow, sdk.uiflags.NPCMenu, + sdk.uiflags.Waypoint, sdk.uiflags.Party, + sdk.uiflags.Shop, sdk.uiflags.Quest, + sdk.uiflags.TradePrompt, sdk.uiflags.Stash, sdk.uiflags.Cube + ], + wpAreas: [ + sdk.areas.RogueEncampment, sdk.areas.ColdPlains, sdk.areas.StonyField, + sdk.areas.DarkWood, sdk.areas.BlackMarsh, sdk.areas.OuterCloister, + sdk.areas.JailLvl1, sdk.areas.InnerCloister, sdk.areas.CatacombsLvl2, + sdk.areas.LutGholein, sdk.areas.A2SewersLvl2, sdk.areas.DryHills, + sdk.areas.HallsoftheDeadLvl2, sdk.areas.FarOasis, sdk.areas.LostCity, + sdk.areas.PalaceCellarLvl1, sdk.areas.ArcaneSanctuary, sdk.areas.CanyonofMagic, + sdk.areas.KurastDocktown, sdk.areas.SpiderForest, sdk.areas.GreatMarsh, + sdk.areas.FlayerJungle, sdk.areas.LowerKurast, sdk.areas.KurastBazaar, + sdk.areas.UpperKurast, sdk.areas.Travincal, sdk.areas.DuranceofHateLvl2, + sdk.areas.PandemoniumFortress, sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, + sdk.areas.Harrogath, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, + sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, sdk.areas.HallsofPain, + sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.WorldstoneLvl2 + ], + nonTownWpAreas: [ + sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.DarkWood, + sdk.areas.BlackMarsh, sdk.areas.OuterCloister, + sdk.areas.JailLvl1, sdk.areas.InnerCloister, sdk.areas.CatacombsLvl2, + sdk.areas.A2SewersLvl2, sdk.areas.DryHills, + sdk.areas.HallsoftheDeadLvl2, sdk.areas.FarOasis, sdk.areas.LostCity, + sdk.areas.PalaceCellarLvl1, sdk.areas.ArcaneSanctuary, sdk.areas.CanyonofMagic, + sdk.areas.SpiderForest, sdk.areas.GreatMarsh, sdk.areas.FlayerJungle, + sdk.areas.LowerKurast, sdk.areas.KurastBazaar, + sdk.areas.UpperKurast, sdk.areas.Travincal, sdk.areas.DuranceofHateLvl2, + sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, + sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.CrystalizedPassage, + sdk.areas.GlacialTrail, sdk.areas.HallsofPain, + sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.WorldstoneLvl2 + ], + nextAreas: {}, + + /** @param {{ type: string, data: number[] }} msg */ + cacheListener: function (msg) { + if (typeof msg !== "object" || !msg /*null*/) return; + if (typeof msg.type === "undefined") return; + if (msg.type !== "wp-cache") return; + if (typeof msg.data !== "object") return; + if (!Array.isArray(msg.data)) return; + if (msg.data.length !== Pather.wpAreas.length) return; + + me.waypoints = msg.data; + + // Waypoint data is set + Pather.initialized = true; + }, + + init: function () { + if (!this.initialized) { + addEventListener("scriptmsg", Pather.cacheListener); + me.classic && (Pather.nonTownWpAreas = this.nonTownWpAreas.filter((wp) => wp < sdk.areas.Harrogath)); + + scriptBroadcast("get-cached-waypoints"); + delay(500); + + if (!Config.WaypointMenu && !Pather.initialized) { + !getWaypoint(1) && this.getWP(me.area); + me.cancelUIFlags(); + Pather.initialized = true; + } + + removeEventListener("scriptmsg", Pather.cacheListener); + } + }, + + /** + * @todo Handle rare bug where teleport skill dissapears from enigma + */ + canTeleport: function () { + return this.teleport && (Skill.canUse(sdk.skills.Teleport) || me.getStat(sdk.stats.OSkill, sdk.skills.Teleport)); + }, + + useTeleport: function () { + let manaTP = Skill.getManaCost(sdk.skills.Teleport); + let numberOfTeleport = ~~(me.mpmax / manaTP); + return !me.inTown && !Config.NoTele && !me.shapeshifted && this.canTeleport() && numberOfTeleport > 2; + }, + + /** + * @typedef {object} spotOnDistanceSettings + * @property {number} [area] + * @property {number} [reductionType] + * @property {number} [coll] + * @property {boolean} [returnSpotOnError] + * + * @param {PathNode} spot + * @param {number} distance + * @param {spotOnDistanceSettings} givenSettings + * @returns {PathNode} + */ + spotOnDistance: function (spot, distance, givenSettings = {}) { + const spotSettings = Object.assign({}, { + area: me.area, + reductionType: 2, + coll: (sdk.collision.BlockWalk), + returnSpotOnError: true + }, givenSettings); + + let nodes = (getPath(spotSettings.area, me.x, me.y, spot.x, spot.y, spotSettings.reductionType, 4) || []); + + if (!nodes.length) { + if (spotSettings.reductionType === 2) { + // try again with walking reduction + nodes = getPath(spotSettings.area, me.x, me.y, spot.x, spot.y, 0, 4); + } + if (!nodes.length) return (spotSettings.returnSpotOnError ? spot : { x: me.x, y: me.y }); + } + + return (nodes.find((node) => getDistance(spot.x, spot.y, node.x, node.y) < distance + && Pather.checkSpot(node.x, node.y, spotSettings.coll)) + || (spotSettings.returnSpotOnError ? spot : { x: me.x, y: me.y })); + }, + + /** + * @typedef {object} pathSettings + * @property {boolean} [allowNodeActions] + * @property {boolean} [allowTeleport] + * @property {boolean} [allowClearing] + * @property {boolean} [allowTown] + * @property {boolean} [allowPicking] + * @property {number} [minDist] + * @property {number} [retry] + * @property {boolean} [pop] + * @property {boolean} [returnSpotOnError] + * @property {Function} [callback] + * @property {clearSettings} [clearSettings] + * + * @typedef {object} clearSettings + * @property {boolean} [clearSettings.clearPath] + * @property {number} [clearSettings.range] + * @property {number} [clearSettings.specType] + * @property {Function} [clearSettings.sort] + * + * @param {PathNode | Unit | PresetUnit} target + * @param {pathSettings} givenSettings + * @returns {boolean} + */ + move: function (target, givenSettings = {}) { + // Abort if dead + if (me.dead) return false; + /** + * assign settings + * @type {pathSettings} + */ + const settings = Object.assign({}, { + clearSettings: { + }, + allowNodeActions: true, + allowTeleport: true, + allowClearing: true, + allowTown: true, + allowPicking: true, + minDist: 3, + retry: 5, + pop: false, + returnSpotOnError: true, + callback: null, + }, givenSettings); + // assign clear settings becasue object.assign was removing the default properties of settings.clearSettings + const clearSettings = Object.assign({ + clearPath: false, + range: 10, + specType: 0, + sort: Attack.sortMonsters, + }, settings.clearSettings); + // set settings.clearSettings equal to the now properly asssigned clearSettings + settings.clearSettings = clearSettings; + + if (!settings.allowClearing && settings.allowClearing !== undefined) { + settings.clearSettings.allowClearing = false; + } + (target instanceof PresetUnit) && (target = target.realCoords()); + + if (settings.minDist > 3) { + target = this.spotOnDistance( + target, + settings.minDist, + { returnSpotOnError: settings.returnSpotOnError, reductionType: (me.inTown ? 0 : 2) } + ); + } + + /** @constructor */ + function PathAction () { + this.at = 0; + /** @type {PathNode} */ + this.node = { x: null, y: null }; + } + + /** @param {PathNode} node */ + PathAction.prototype.update = function (node) { + this.at = getTickCount(); + this.node.x = node.x; + this.node.y = node.y; + }; + + let fail = 0; + let invalidCheck = false; + let node = { x: target.x, y: target.y }; + const leaped = new PathAction(); + const whirled = new PathAction(); + const cleared = new PathAction(); + const primarySlot = Attack.getPrimarySlot(); // for tele-switch + + for (let i = 0; i < this.cancelFlags.length; i += 1) { + getUIFlag(this.cancelFlags[i]) && me.cancel(); + } + + if (typeof target.x !== "number" || typeof target.y !== "number") { + throw new Error("move: Coords must be numbers"); + } + if (getDistance(me, target) < 2 && !CollMap.checkColl(me, target, sdk.collision.BlockMissile, 5)) { + return true; + } + + let useTeleport = settings.allowTeleport && this.useTeleport(); + const tpMana = useTeleport ? Skill.getManaCost(sdk.skills.Teleport) : Infinity; + const annoyingArea = [ + sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3 + ].includes(me.area); + let path = getPath( + me.area, + target.x, target.y, + me.x, me.y, + useTeleport ? 1 : 0, + useTeleport ? (annoyingArea ? 30 : this.teleDistance) : this.walkDistance + ); + if (!path) throw new Error("move: Failed to generate path."); + + if (settings.retry <= 3 && target.distance > useTeleport ? 120 : 60) { + settings.retry = 10; + } + + path.reverse(); + settings.pop && path.pop(); + PathDebug.drawPath(path); + useTeleport && Config.TeleSwitch && path.length > 5 && me.switchWeapons(primarySlot ^ 1); + + while (path.length > 0) { + // Abort if dead + if (me.dead) return false; + // main path + Pather.recursion && (Pather.currentWalkingPath = path); + + for (let i = 0; i < this.cancelFlags.length; i += 1) { + if (getUIFlag(this.cancelFlags[i])) me.cancel(); + } + + node = path.shift(); + + if (typeof settings.callback === "function" && settings.callback()) { + console.debug("Callback function passed. Ending path."); + useTeleport && Config.TeleSwitch && me.switchWeapons(primarySlot); + PathDebug.removeHooks(); + return true; + } + + if (getDistance(me, node) > 2) { + // Make life in Maggot Lair easier + fail >= 3 && fail % 3 === 0 && !Attack.validSpot(node.x, node.y) && (invalidCheck = true); + // Make life in Maggot Lair easier - should this include arcane as well? + if (annoyingArea || invalidCheck) { + let adjustedNode = this.getNearestWalkable(node.x, node.y, 15, 3, sdk.collision.BlockWalk); + + if (adjustedNode) { + [node.x, node.y] = adjustedNode; + invalidCheck && (invalidCheck = false); + } + + annoyingArea && ([settings.clearSettings.overrideConfig, settings.clearSettings.range] = [true, 5]); + settings.retry <= 3 && !useTeleport && (settings.retry = 15); + } + + if (useTeleport && tpMana <= me.mp + ? this.teleportTo(node.x, node.y) + : this.walkTo(node.x, node.y, (fail > 0 || me.inTown) ? 2 : 4)) { + if (settings.allowNodeActions && !me.inTown) { + if (Pather.recursion) { + Pather.recursion = false; + try { + NodeAction.go(settings.clearSettings); + node.distance > 5 && this.move(node, settings); + } finally { + Pather.recursion = true; + } + } + } + } else { + if (!me.inTown) { + if (!useTeleport && (this.kickBarrels(node.x, node.y) || this.openDoors(node.x, node.y))) { + continue; + } + + if (/* fail > 0 && */(!useTeleport || tpMana > me.mp)) { + // if we are allowed to clear + if (settings.allowClearing) { + // Don't go berserk on longer paths - also check that there are even mobs blocking us + if (cleared.at === 0 || getTickCount() - cleared.at > Time.seconds(3) + && cleared.node.distance > 5 && me.checkForMobs({ range: 10 })) { + // only set that we cleared if we actually killed at least 1 mob + if (Attack.clear(10, null, null, null, settings.allowPicking) === Attack.Result.SUCCESS) { + cleared.update(node); + } + } + } + + // Leap can be helpful on long paths but make sure we don't spam it + if (Skill.canUse(sdk.skills.LeapAttack)) { + // we can use leapAttack, now lets see if we should - either haven't used it yet or it's been long enough since last time + if (leaped.at === 0 || getTickCount() - leaped.at > Time.seconds(3) + || leaped.node.distance > 5 || me.checkForMobs({ range: 6 })) { + // alright now if we have actually casted it set the values so we know + if (Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, node.x, node.y)) { + leaped.update(node); + } + } + } + + /** + * whirlwind can be useful as well, implement it. + * Things to consider: + * 1) Can we cast whirlwind on the node? Is it blocked by something other than monsters. + * 2) If we can't cast on that node, is there another node between us and it that would work? + */ + if (Skill.canUse(sdk.skills.Whirlwind)) { + // we can use whirlwind, now lets see if we should - either haven't used it yet or it's been long enough since last time + if (whirled.at === 0 || getTickCount() - whirled.at > Time.seconds(3) + || whirled.node.distance > 5 || me.checkForMobs({ range: 6 })) { + // alright now if we have actually casted it set the values so we know + if (Skill.cast(sdk.skills.Whirlwind, sdk.skills.hand.Right, node.x, node.y)) { + whirled.update(node); + } + } + } + } + } else if (fail > 0 && me.inArea(sdk.areas.LutGholein)) { + // dislike have this here but handle atma blocking us from inside the tavern + if (me.x > 5122 && me.y <= 5049) { + let atma = Game.getNPC(NPC.Atma); + if (atma && (atma.x === 5136 || atma.x === 5137) + && (atma.y >= 5048 && atma.y <= 5051)) { + // yup dumb lady is blocking the door, take side door + [[5140, 5038], [5148, 5031], [5154, 5025], [5161, 5030]].forEach(function (node) { + Pather.walkTo(node[0], node[1]); + }); + } + } else if (me.x >= 5051 && me.x <= 5068 && me.y <= 5145) { + // we might not be able to get past the guards - rare but can happen to rushers + Pather.moveToExit(sdk.areas.HaremLvl1, true); + Pather.makePortal(true); + } + } + + // Reduce node distance in new path + path = getPath( + me.area, + target.x, target.y, + me.x, me.y, + useTeleport ? 1 : 0, + useTeleport ? rand(25, 35) : rand(10, 15) + ); + if (!path) throw new Error("move: Failed to generate path."); + + path.reverse(); + PathDebug.drawPath(path); + settings.pop && path.pop(); + + if (fail > 0) { + console.debug("move retry " + fail); + Packet.flash(me.gid); + + if (fail >= settings.retry) { + console.log("Failed move: Retry = " + settings.retry); + break; + } + } + fail++; + } + } + + delay(5); + } + + useTeleport && Config.TeleSwitch && me.switchWeapons(primarySlot); + PathDebug.removeHooks(); + + return getDistance(me, node.x, node.y) < 5; + }, + + /** + * @param {number} x + * @param {number} y + * @param {number} minDist + * @param {pathSettings} givenSettings + * @returns {boolean} + */ + moveNear: function (x, y, minDist, givenSettings = {}) { + return Pather.move({ x: x, y: y }, Object.assign({ minDist: minDist }, givenSettings)); + }, + + /** + * @param {number} x - the x coord to move to + * @param {number} y - the y coord to move to + * @param {number} retry - number of attempts before aborting + * @param {boolean} clearPath - kill monsters while moving + * @param {boolean} pop - remove last node + * @returns {boolean} + */ + moveTo: function (x, y, retry, clearPath, pop) { + return Pather.move({ x: x, y: y }, { retry: retry, pop: pop, allowClearing: clearPath }); + }, + + /** + * + * @param {number} x + * @param {number} y + * @param {pathSettings} givenSettings + * @returns + */ + moveToEx: function (x, y, givenSettings = {}) { + return Pather.move({ x: x, y: y }, givenSettings); + }, + + /** + * @param {number} x - the x coord to teleport to + * @param {number} y - the y coord to teleport to + * @param {number} [maxRange] - max acceptable distance from node + * @returns {boolean} + * @todo does this need a validLocation check? - maybe if we fail once check the spot + */ + teleportTo: function (x, y, maxRange = 5) { + for (let i = 0; i < 3; i += 1) { + Config.PacketCasting > 0 + ? Packet.teleport(x, y) + : Skill.cast(sdk.skills.Teleport, sdk.skills.hand.Right, x, y); + let tick = getTickCount(); + let pingDelay = i === 0 ? 150 : me.getPingDelay(); + + while (getTickCount() - tick < Math.max(500, pingDelay * 2 + 200)) { + if ([x, y].distance < maxRange) { + return true; + } + + delay(10); + } + } + + return false; + }, + + /** + * @param {number} x - the x coord to teleport to + * @param {number} y - the y coord to teleport to + * @param {number} [minDist] - minimal distance from x/y before returning true + * @returns {boolean} - sucessfully moved within minDist + */ + walkTo: function (x, y, minDist) { + while (!me.gameReady) { + delay(100); + } + + if (x === undefined || y === undefined || me.dead) return false; + minDist === undefined && (minDist = me.inTown ? 2 : 4); + + let nTimer; + let [nFail, attemptCount] = [0, 0]; + + /** + * @todo add cleansing/meditation here as well + */ + // credit @Jaenster + // Stamina handler and Charge + if (!me.inTown) { + // Check if I have a stamina potion and use it if I do + if (me.staminaPercent <= 20) { + let stam = me.getItemsEx(-1, sdk.items.mode.inStorage) + .filter(function (i) { + return i.classid === sdk.items.StaminaPotion && i.isInInventory; + }) + .first(); + !!stam && !me.deadOrInSequence && stam.use(); + } + (me.running && me.staminaPercent <= 15) && me.walk(); + // the less stamina you have, the more you wait to recover + let recover = me.staminaMaxDuration < 30 ? 80 : 50; + (me.walking && me.staminaPercent >= recover) && me.run(); + if (Skill.canUse(sdk.skills.Charge) && me.paladin + && me.mp >= 9 && [x, y].distance > 8 + && Skill.setSkill(sdk.skills.Charge, sdk.skills.hand.Left)) { + if (Skill.canUse(sdk.skills.Vigor)) { + Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right); + } else if (Skill.isAura(Config.RunningAura) && Skill.canUse(Config.RunningAura)) { + Skill.setSkill(Config.RunningAura, sdk.skills.hand.Right); + } else if (!Config.Vigor && !Attack.auradin && Skill.canUse(sdk.skills.HolyFreeze)) { + // Useful in classic to keep mobs cold while you rush them + Skill.setSkill(sdk.skills.HolyFreeze, sdk.skills.hand.Right); + } + Misc.click(0, 1, x, y); + while (!me.idle) { + delay(40); + } + } + + if (Precast.enabled && Skill.canUse(sdk.skills.Blaze) + && me.mp > (Skill.getManaCost(sdk.skills.Blaze) * 2) + && !me.getState(sdk.states.Blaze)) { + Skill.cast(sdk.skills.Blaze); + } + } else { + me.walking && me.run(); + Skill.canUse(sdk.skills.Vigor) && Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right); + } + + MainLoop: + while (getDistance(me.x, me.y, x, y) > minDist && !me.dead) { + if (me.paladin && !me.inTown) { + Skill.canUse(sdk.skills.Vigor) + ? Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right) + : Skill.isAura(Config.RunningAura) && Skill.canUse(Config.RunningAura) + ? Skill.setSkill(Config.RunningAura, sdk.skills.hand.Right) + : Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); + } + + if (this.openDoors(x, y) && getDistance(me.x, me.y, x, y) <= minDist) { + return true; + } + + if (attemptCount > 1 + && CollMap.checkColl(me, { x: x, y: y }, sdk.collision.BlockWall | sdk.collision.ClosedDoor)) { + this.openDoors(me.x, me.y); + } + + Misc.click(0, 0, x, y); + + attemptCount += 1; + nTimer = getTickCount(); + + while (!me.moving) { + if (me.dead) return false; + + if ((getTickCount() - nTimer) > 500) { + if (nFail >= 3) { + break MainLoop; + } + + nFail += 1; + let angle = Math.atan2(me.y - y, me.x - x); + let angles = [Math.PI / 2, -Math.PI / 2]; + + for (let i = 0; i < angles.length; i += 1) { + // TODO: might need rework into getnearestwalkable + let whereToClick = { + x: Math.round(Math.cos(angle + angles[i]) * 5 + me.x), + y: Math.round(Math.sin(angle + angles[i]) * 5 + me.y) + }; + + if (Attack.validSpot(whereToClick.x, whereToClick.y)) { + Misc.click(0, 0, whereToClick.x, whereToClick.y); + + let tick = getTickCount(); + + while (getDistance(me, whereToClick) > 2 && getTickCount() - tick < 1000) { + delay(40); + } + + break; + } + } + + break; + } + + delay(10); + } + + attemptCount > 1 && this.kickBarrels(x, y); + + // Wait until we're done walking - idle or dead + while (getDistance(me.x, me.y, x, y) > minDist && !me.idle) { + delay(10); + } + + if (attemptCount >= 3) { + break; + } + } + return (!me.dead && getDistance(me.x, me.y, x, y) <= minDist); + }, + + /** + * If there is a door in our path, open it so we can continue moving + * @param {number} x - the x coord of the node close to the door + * @param {number} y - the y coord of the node close to the door + * @returns {boolean} true if we opened any doors that were in our way + */ + openDoors: function (x, y) { + if (me.inTown && me.act !== 5) return false; + + (typeof x !== "number" || typeof y !== "number") && ({ x, y } = me); + + // Regular doors + let door = Game.getObject("door", sdk.objects.mode.Inactive); + + if (door) { + do { + if ((getDistance(door, x, y) < 4 && door.distance < 9) || door.distance < 4) { + for (let i = 0; i < 3; i++) { + Misc.click(0, 0, door); + + if (Misc.poll(() => door.mode === sdk.objects.mode.Active, 1000, 30)) { + return true; + } + + i === 2 && Packet.flash(me.gid); + } + } + } while (door.getNext()); + } + + // handle act 5 gate + if ([sdk.areas.Harrogath, sdk.areas.BloodyFoothills].includes(me.area)) { + let gate = Game.getObject("gate", sdk.objects.mode.Inactive); + + if (gate) { + if ((getDistance(gate, x, y) < 4 && gate.distance < 9) || gate.distance < 4) { + for (let i = 0; i < 3; i++) { + Misc.click(0, 0, gate); + + if (Misc.poll(() => gate.mode, 1000, 50)) { + return true; + } + + i === 2 && Packet.flash(me.gid); + } + } + } + } + + // Monsta doors (Barricaded) - not sure if this is really needed anymore + let monstadoor = Game.getMonster("barricaded door"); + + if (monstadoor) { + do { + if (monstadoor.hp > 0 && (getDistance(monstadoor, x, y) < 4 + && monstadoor.distance < 9) || monstadoor.distance < 4) { + for (let p = 0; p < 20 && monstadoor.hp; p++) { + Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), monstadoor); + } + } + } while (monstadoor.getNext()); + } + + let monstawall = Game.getMonster("barricade"); + + if (monstawall) { + do { + if (monstawall.hp > 0 && (getDistance(monstawall, x, y) < 4 + && monstawall.distance < 9) || monstawall.distance < 4) { + for (let p = 0; p < 20 && monstawall.hp; p++) { + Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), monstawall); + } + } + } while (monstawall.getNext()); + } + + return false; + }, + + /** + * Small and annoying things like barrels can block our path, open them if they are near us + * @param {number} x - the x coord of the node close to the barrel + * @param {number} y - the y coord of the node close to the barrel + * @returns {boolean} true if we kicked any barrels that were in our way + */ + kickBarrels: function (x, y) { + if (me.inTown) return false; + + (typeof x !== "number" || typeof y !== "number") && ({ x, y } = me); + + // anything small and annoying really + let _things = [ + "ratnest", "goo pile", "barrel", "basket", + "largeurn", "jar3", "jar2", "jar1", + "urn", "jug", "barrel wilderness", "cocoon" + ]; + let barrels = getUnits(sdk.unittype.Object) + .filter(function (el) { + return (el.name && el.mode === sdk.objects.mode.Inactive + && _things.includes(el.name.toLowerCase()) + && ((getDistance(el, x, y) < 4 && el.distance < 9) || el.distance < 4)); + }); + let brokeABarrel = false; + + while (barrels.length > 0) { + barrels.sort(Sort.units); + let unit = barrels.shift(); + + if (unit && !checkCollision(me, unit, sdk.collision.WallOrRanged)) { + try { + for (let i = 0; i < 5; i++) { + i < 3 ? Packet.entityInteract(unit) : Misc.click(0, 0, unit); + + if (unit.mode) { + brokeABarrel = true; + break; + } + } + } catch (e) { + continue; + } + } + } + + return brokeABarrel; + }, + + /** + * Move to unit + * @param {Unit} unit - unit to move to + * @param {number} [offX] - offset from unit's x coord + * @param {number} [offY] - offset from unit's x coord + * @param {boolean} [clearPath] - kill monsters while moving + * @param {boolean} [pop] - remove last node + * @returns {boolean} Sucessfully moved to unit + */ + moveToUnit: function (unit, offX, offY, clearPath, pop) { + const useTeleport = this.useTeleport(); + + offX === undefined && (offX = 0); + offY === undefined && (offY = 0); + clearPath === undefined && (clearPath = false); + pop === undefined && (pop = false); + + if (!unit || !unit.hasOwnProperty("x") || !unit.hasOwnProperty("y")) { + throw new Error("moveToUnit: Invalid unit."); + } + (unit instanceof PresetUnit) && (unit = { x: unit.roomx * 5 + unit.x, y: unit.roomy * 5 + unit.y }); + + let [x, y] = [unit.x + offX, unit.y + offY]; + + if (!useTeleport) { + // The unit will most likely be moving so call the first walk with 'pop' parameter + this.moveTo(x, y, 0, clearPath, true); + } + + return this.moveTo(x, y, useTeleport && unit.type && unit.isMonster ? 3 : 0, clearPath, pop); + }, + + /** + * Move near unit + * @param {Unit} unit - unit to move near + * @param {boolean} [clearPath] - kill monsters while moving + * @param {boolean} [pop] - remove last node + * @returns {boolean} Sucessfully moved near unit + */ + moveNearUnit: function (unit, minDist, clearPath, pop = false) { + const useTeleport = this.useTeleport(); + minDist === undefined && (minDist = me.inTown ? 2 : 5); + + if (!unit || !unit.hasOwnProperty("x") || !unit.hasOwnProperty("y")) throw new Error("moveNearUnit: Invalid unit."); + + (unit instanceof PresetUnit) && (unit = { x: unit.roomx * 5 + unit.x, y: unit.roomy * 5 + unit.y }); + + if (!useTeleport) { + // The unit will most likely be moving so call the first walk with 'pop' parameter + this.moveNear(unit.x, unit.y, minDist, { clearSettings: { clearPath: clearPath }, pop: true }); + } + + return this.moveNear(unit.x, unit.y, minDist, { clearSettings: { clearPath: clearPath }, pop: pop }); + }, + + /** + * Move near preset unit + * @param {number} area - area of the preset unit + * @param {number} unitType - type of the preset unit + * @param {number} unitId - preset unit id + * @param {number} [minDist] - minimum distance from unit + * @param {boolean} [clearPath] - kill monsters while moving + * @param {boolean} [pop] - remove last node + * @returns {boolean} Sucessfully moved near unit + */ + moveNearPreset: function (area, unitType, unitId, minDist, clearPath = false, pop = false) { + if (area === undefined || unitType === undefined || unitId === undefined) { + throw new Error("moveNearPreset: Invalid parameters."); + } + + me.area !== area && Pather.journeyTo(area); + let presetUnit = getPresetUnit(area, unitType, unitId); + + if (!presetUnit) { + throw new Error( + "moveNearPreset: Couldn't find preset unit - id: " + unitId + + " unitType: " + unitType + " in area: " + getAreaName(area) + ); + } + + delay(40); + Misc.poll(() => me.gameReady, 500, 100); + + let unit = presetUnit.realCoords(); + + return this.moveNear(unit.x, unit.y, minDist, { clearSettings: { clearPath: clearPath }, pop: pop }); + }, + + /** + * Move to preset unit + * @param {number} area - area of the preset unit + * @param {number} unitType - type of the preset unit + * @param {number} unitId - preset unit id + * @param {number} [offX] - offset from unit's x coord + * @param {number} [offY] - offset from unit's x coord + * @param {boolean} [clearPath] - kill monsters while moving + * @param {boolean} [pop] - remove last node + * @returns {boolean} Sucessfully moved to unit + */ + moveToPreset: function (area, unitType, unitId, offX, offY, clearPath, pop) { + if (area === undefined || unitType === undefined || unitId === undefined) { + throw new Error("moveToPreset: Invalid parameters."); + } + + offX === undefined && (offX = 0); + offY === undefined && (offY = 0); + clearPath === undefined && (clearPath = false); + pop === undefined && (pop = false); + + me.area !== area && Pather.journeyTo(area); + let presetUnit = getPresetUnit(area, unitType, unitId); + + if (!presetUnit) { + throw new Error( + "moveToPreset: Couldn't find preset unit - id: " + unitId + + " unitType: " + unitType + " in area: " + getAreaName(area) + ); + } + + delay(40); + Misc.poll(() => me.gameReady, 500, 100); + let { x, y } = presetUnit.realCoords(); + + return this.moveTo(x + offX, y + offY, 3, clearPath, pop); + }, + + /** + * @todo + * moveTo/NearPresetTile + */ + + /** + * + * @param {number} area + * @param {number} unitId + * @param {pathSettings} givenSettings + */ + moveToPresetObject: function (area, unitId, givenSettings = {}) { + if (area === undefined || unitId === undefined) { + throw new Error("moveToPreset: Invalid parameters."); + } + + let offX = givenSettings.hasOwnProperty("offX") ? givenSettings.offX : 0; + let offY = givenSettings.hasOwnProperty("offY") ? givenSettings.offY : 0; + + me.area !== area && Pather.journeyTo(area); + let presetUnit = Game.getPresetObject(area, unitId); + + if (!presetUnit) { + throw new Error( + "moveToPresetObject: Couldn't find preset unit - id: " + unitId + + " in area: " + getAreaName(area) + ); + } + + delay(40); + Misc.poll(() => me.gameReady, 500, 100); + let { x, y } = presetUnit.realCoords(); + + return this.moveToEx(x + offX, y + offY, givenSettings); + }, + + /** + * + * @param {number} area + * @param {number} unitId + * @param {pathSettings} givenSettings + */ + moveToPresetMonster: function (area, unitId, givenSettings = {}) { + if (area === undefined || unitId === undefined) { + throw new Error("moveToPreset: Invalid parameters."); + } + + let offX = givenSettings.hasOwnProperty("offX") ? givenSettings.offX : 0; + let offY = givenSettings.hasOwnProperty("offY") ? givenSettings.offY : 0; + + me.area !== area && Pather.journeyTo(area); + let presetUnit = Game.getPresetMonster(area, unitId); + + if (!presetUnit) { + throw new Error( + "moveToPresetMonster: Couldn't find preset unit - id: " + unitId + + " in area: " + getAreaName(area) + ); + } + + delay(40); + Misc.poll(() => me.gameReady, 500, 100); + let { x, y } = presetUnit.realCoords(); + + return this.moveToEx(x + offX, y + offY, givenSettings); + }, + + /** + * @param {number} targetArea - area id or array of area ids to move to + * @param {boolean} [use] - enter target area or last area in the array + * @param {pathSettings} givenSettings + */ + moveToExit: function (targetArea, use, givenSettings = {}) { + if (targetArea === undefined) return false; + + const areas = Array.isArray(targetArea) + ? targetArea + : [targetArea]; + const finalDest = areas.last(); + const finalDestName = getAreaName(finalDest); + console.info(true, "ÿc7MyArea: ÿc0" + getAreaName(me.area) + " ÿc7TargetArea: ÿc0" + finalDestName, "moveToExit"); + + me.inArea(areas.first()) && areas.shift(); + + for (let currTarget of areas) { + console.info(null, getAreaName(me.area) + "ÿc8 --> ÿc0" + getAreaName(currTarget)); + + const area = Misc.poll(() => getArea(me.area)); + if (!area) throw new Error("moveToExit: error in getArea()"); + + /** @type {Array} */ + const exits = (area.exits || []); + if (!exits.length) return false; + + let checkExits = []; + for (let exit of exits) { + if (!exit.hasOwnProperty("target") || exit.target !== currTarget) continue; + checkExits.push(exit); + } + + if (checkExits.length > 0) { + // if there are multiple exits to the same location find the closest one + let currExit = checkExits.length > 1 + ? (() => { + let useExit = checkExits.shift(); // assign the first exit as a possible result + let dist = getDistance(me.x, me.y, useExit.x, useExit.y); + while (checkExits.length > 0) { + let exitDist = getDistance(me.x, me.y, checkExits[0].x, checkExits[0].y); + if (exitDist < dist) { + useExit = checkExits[0]; + dist = exitDist; + } + checkExits.shift(); + } + return useExit; + })() + : checkExits[0]; + let dest = this.getNearestWalkable(currExit.x, currExit.y, 5, 1); + if (!dest) return false; + + for (let retry = 0; retry < 3; retry++) { + if (this.moveToEx(dest[0], dest[1], givenSettings)) { + break; + } + + delay(200); + console.log("ÿc7(moveToExit) :: ÿc0Retry: " + (retry + 1)); + Misc.poll(() => me.gameReady, 1000, 200); + } + + if (use || currTarget !== finalDest) { + switch (currExit.type) { + case 1: // walk through + let targetRoom = this.getNearestRoom(currTarget); + // might need adjustments + if (!targetRoom) return false; + this.moveToEx(targetRoom[0], targetRoom[1], givenSettings); + + break; + case 2: // stairs + if (!this.openExit(currTarget) && !this.useUnit(sdk.unittype.Stairs, currExit.tileid, currTarget)) { + return false; + } + + break; + } + } + } + } + + console.info(false, "ÿc7targetArea: ÿc0" + finalDestName + " ÿc7myArea: ÿc0" + getAreaName(me.area), "moveToExit"); + delay(300); + + return (use && finalDest ? me.area === finalDest : true); + }, + + /** + * @param {number} area + * @param {number} exit + * @returns {number} + */ + getDistanceToExit: function (area, exit) { + area === undefined && (area = me.area); + exit === undefined && (exit = me.area + 1); + let areaToCheck = Misc.poll(() => getArea(area)); + if (!areaToCheck) throw new Error("Couldn't get area info for " + getAreaName(area)); + let exits = areaToCheck.exits; + if (!exits.length) throw new Error("Failed to find exits"); + let loc = exits.find(a => a.target === exit); + console.debug(area, exit, loc); + return loc ? [loc.x, loc.y].distance : Infinity; + }, + + /** + * @param {number} area + * @param {number} exit + * @returns {PathNode | false} + */ + getExitCoords: function (area, exit) { + area === undefined && (area = me.area); + exit === undefined && (exit = me.area + 1); + let areaToCheck = Misc.poll(() => getArea(area)); + if (!areaToCheck) throw new Error("Couldn't get area info for " + getAreaName(area)); + let exits = areaToCheck.exits; + if (!exits.length) throw new Error("Failed to find exits"); + let loc = exits.find(a => a.target === exit); + console.debug(area, exit, loc); + return loc ? { x: loc.x, y: loc.y } : false; + }, + + + /** + * @param {number} area - the id of area to search for the room nearest to the player character + * @returns {[number, number] | false} + */ + getNearestRoom: function (area) { + let x, y, minDist = 10000; + + let room = Misc.poll(() => getRoom(area), 1000, 200); + if (!room) return false; + + do { + let dist = getDistance(me, room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2); + + if (dist < minDist) { + x = room.x * 5 + room.xsize / 2; + y = room.y * 5 + room.ysize / 2; + minDist = dist; + } + } while (room.getNext()); + + room = getRoom(area, x, y); + !!Config.DebugMode.Path && console.log(room); + + if (room) { + CollMap.addRoom(room); + + return this.getNearestWalkable(x, y, 20, 4); + } + + return [x, y]; + }, + + /** + * @param {number} targetArea - area id of where the unit leads to + * @returns {boolean} + */ + openExit: function (targetArea) { + switch (true) { + case targetArea === sdk.areas.AncientTunnels: + case targetArea === sdk.areas.A2SewersLvl1 && !(me.inArea(sdk.areas.LutGholein) && [5218, 5180].distance < 20): + return this.useUnit(sdk.unittype.Object, sdk.objects.TrapDoorA2, targetArea); + case targetArea === sdk.areas.A3SewersLvl2: + return this.useUnit(sdk.unittype.Object, sdk.objects.SewerStairsA3, targetArea); + case targetArea === sdk.areas.RuinedTemple: + case targetArea === sdk.areas.DisusedFane: + case targetArea === sdk.areas.ForgottenReliquary: + case targetArea === sdk.areas.ForgottenTemple: + case targetArea === sdk.areas.RuinedFane: + case targetArea === sdk.areas.DisusedReliquary: + return this.useUnit(sdk.unittype.Object, "stair", targetArea); + case targetArea === sdk.areas.DuranceOfHateLvl1 && me.inArea(sdk.areas.Travincal): + return this.useUnit(sdk.unittype.Object, sdk.objects.DuranceEntryStairs, targetArea); + case targetArea === sdk.areas.WorldstoneLvl1 && me.inArea(sdk.areas.ArreatSummit): + return this.useUnit(sdk.unittype.Object, sdk.objects.AncientsDoor, targetArea); + } + + return false; + }, + + /** + * @param {UnitType} type - type of the unit to open + * @param {number} id - id of the unit to open + * @returns {boolean} + */ + openUnit: function (type, id) { + /** @type {ObjectUnit | Tile} */ + let unit = Misc.poll(() => getUnit(type, id), 1000, 200); + if (!unit) throw new Error("openUnit: Unit not found. ID: " + unit); + if (unit.mode !== sdk.objects.mode.Inactive) return true; + + return unit.openUnit(); + }, + + /** + * @param {UnitType} type - type of the unit to use + * @param {number} id - id of the unit to use + * @param {number} targetArea - area id of where the unit leads to + * @returns {boolean} + * @todo should use an object as param, or be changed to able to take an already found unit as a param + */ + useUnit: function (type, id, targetArea) { + /** @type {ObjectUnit | Tile} */ + let unit = Misc.poll(() => getUnit(type, id), 2000, 200); + if (!unit) { + throw new Error( + "useUnit: Unit not found. TYPE: " + type + " ID: " + id + + " MyArea: " + getAreaName(me.area) + + (!!targetArea ? " TargetArea: " + getAreaName(targetArea) : "") + ); + } + + return unit.useUnit(targetArea); + }, + + allowBroadcast: true, + /** + * Meant for use as a MfLeader to let MfHelpers know where to go next + * @param {number} targetArea - area id + */ + broadcastIntent: function broadcastIntent (targetArea) { + if (Config.MFLeader + && Pather.allowBroadcast + // mfhelper is disabled for these scripts so announcing is pointless + && !Loader.scriptName(0).toLowerCase().includes("diablo") + && !Loader.scriptName(0).toLowerCase().includes("baal")) { + let targetAct = sdk.areas.actOf(targetArea); + me.act !== targetAct && say("goto A" + targetAct); + } + }, + + /** + * @param {number} targetArea - id of the area to enter + * @param {boolean} check - force the waypoint menu + * @returns {boolean} + */ + useWaypoint: function useWaypoint (targetArea, check = false) { + switch (targetArea) { + case undefined: + throw new Error("useWaypoint: Invalid targetArea parameter: " + targetArea); + case null: + case "random": + check = true; + + break; + default: + if (typeof targetArea !== "number") throw new Error("useWaypoint: Invalid targetArea parameter"); + if (this.wpAreas.indexOf(targetArea) < 0) throw new Error("useWaypoint: Invalid area"); + + break; + } + + const destName = targetArea ? getAreaName(targetArea) : targetArea; + Pather.broadcastIntent(targetArea); + console.info( + true, + "ÿc7targetArea: ÿc0" + destName + " ÿc7myArea: ÿc0" + getAreaName(me.area), + "useWaypoint" + ); + + MainLoop: + for (let i = 0; i < 12; i += 1) { + if (me.area === targetArea || me.dead) { + break; + } + + if (me.inTown) { + if (me.inArea(sdk.areas.LutGholein)) { + let npc = Game.getNPC(NPC.Warriv); + + if (!!npc && npc.distance < 50) { + if (npc && npc.openMenu()) { + Misc.useMenu(sdk.menu.GoWest); + + if (!Misc.poll(() => me.gameReady && me.inArea(sdk.areas.RogueEncampment), 2000, 100)) { + throw new Error("Failed to go to act 1 using Warriv"); + } + if (me.inArea(targetArea)) { + break; + } + } + } + } + + /** + * @todo If we start in a3 and want to go to a2, use Meshif + * somehow need to take into account reason for wanting to change act though, e.g. If we were + * going to a2 to revive a merc then running to the a3 waypoint takes us close to our goal. + * On the other hand though if we are going to a2 to take a portal then using meshif makes sense + * extending so far as not just starting next to him but finishing chores anywhere around ormus/wp + * if we are all the way at Alkor then it wouldn't make sense + */ + + if (!getUIFlag(sdk.uiflags.Waypoint) && Town.getDistance("waypoint") > (Skill.haveTK ? 20 : 5)) { + Town.move("waypoint"); + } + } + + let wp = Game.getObject("waypoint"); + + if (!!wp && wp.area === me.area) { + let useTK = (Skill.useTK(wp) && i < 3); + let pingDelay = me.getPingDelay(); + + if (useTK && !getUIFlag(sdk.uiflags.Waypoint)) { + wp.distance > 21 && Pather.moveNearUnit(wp, 20); + Packet.telekinesis(wp); + } else if (!me.inTown && wp.distance > 7) { + this.moveToUnit(wp); + } + + if (check || Config.WaypointMenu || !this.initialized) { + if (!useTK && (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint))) { + this.moveToUnit(wp) && Misc.click(0, 0, wp); + } + + // handle getUnit bug + if (me.inTown && !getUIFlag(sdk.uiflags.Waypoint) && wp.name.toLowerCase() === "dummy") { + Town.getDistance("waypoint") > 5 && Town.move("waypoint"); + Misc.click(0, 0, wp); + } + + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), pingDelay * 2)) { + // Waypoint screen is open + if (getUIFlag(sdk.uiflags.Waypoint)) { + delay(500); + !Pather.initialized && (Pather.initialized = true); + + switch (targetArea) { + case "random": + let validWps = this.nonTownWpAreas + .filter(area => getWaypoint(this.wpAreas.indexOf(area))); + if (!validWps.length) { + if (me.inTown && Pather.moveToExit(me.area + 1, true)) { + break; + } + throw new Error("Pather.useWaypoint: Failed to go to waypoint " + targetArea); + } + targetArea = validWps.random(); + + break; + case null: + me.cancel(); + + return true; + } + + if (!getWaypoint(this.wpAreas.indexOf(targetArea))) { + me.cancel(); + console.log("Trying to get the waypoint: " + getAreaName(targetArea)); + me.overhead("Trying to get the waypoint"); + if (this.getWP(targetArea)) return true; + + throw new Error("Pather.useWaypoint: Failed to go to waypoint " + getAreaName(targetArea)); + } + + break; + } + + delay(10); + } + + if (!getUIFlag(sdk.uiflags.Waypoint)) { + console.warn("waypoint retry " + (i + 1)); + let retry = Math.min(i + 1, 5); + let coord = CollMap.getRandCoordinate(me.x, -5 * retry, 5 * retry, me.y, -5 * retry, 5 * retry); + !!coord && this.moveTo(coord.x, coord.y); + delay(200); + Packet.flash(me.gid, pingDelay); + + continue; + } + } + + if (!check || getUIFlag(sdk.uiflags.Waypoint)) { + delay(200); + wp.interact(targetArea); + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), pingDelay * 2)) { + if (me.area === targetArea) { + delay(1500); + + break MainLoop; + } + + delay(20); + } + + while (!me.gameReady) { + delay(1000); + } + + // In case lag causes the wp menu to stay open + Misc.poll(() => me.gameReady, 2000, 100) && getUIFlag(sdk.uiflags.Waypoint) && me.cancelUIFlags(); + } + + Packet.flash(me.gid, pingDelay); + // Activate check if we fail direct interact twice + i > 1 && (check = true); + } else { + Packet.flash(me.gid); + } + + // We can't seem to get the wp maybe attempt portal to town instead and try to use that wp + i >= 10 && !me.inTown && Town.goToTown(); + delay(200); + } + + if (me.area === targetArea) { + // delay to allow act to init - helps with crashes + delay(500); + console.info( + false, + "ÿc7targetArea: ÿc0" + getAreaName(targetArea) + " ÿc7myArea: ÿc0" + getAreaName(me.area), + "useWaypoint" + ); + return true; + } + + throw new Error("useWaypoint: Failed to use waypoint"); + }, + + /** + * @param {boolean} use - use the portal that was made + * @returns {Unit | boolean} + */ + makePortal: function (use = false) { + if (me.inTown) return true; + + let oldGid; + + for (let i = 0; i < 5; i += 1) { + if (me.dead) return false; + + const tpTool = me.getTpTool(); + const pingDelay = i === 0 ? 100 : me.gameReady ? (me.ping + 25) : 350; + if (!tpTool) return false; + + let oldPortal = getUnits(sdk.unittype.Object, "portal") + .filter(function (p) { + return p.getParent() === me.name; + }) + .first(); + + !!oldPortal && (oldGid = oldPortal.gid); + + if (tpTool.use() || Game.getObject("portal")) { + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(500 + i * 100, pingDelay * 2 + 100)) { + let portal = getUnits(sdk.unittype.Object, "portal") + .filter(function (p) { + return p.getParent() === me.name && p.gid !== oldGid; + }) + .first(); + + if (portal) { + // getUnits returns a copied version, get the actual portal so we don't copy a copy + const realPortal = Game.getObject(-1, -1, portal.gid); + if (realPortal) { + if (use) { + if (this.usePortal(null, null, copyUnit(realPortal))) { + return true; + } + break; // don't spam usePortal + } else { + return copyUnit(realPortal); + } + } + } + + delay(10); + } + } else { + console.log("Failed to use tp tool"); + Packet.flash(me.gid, pingDelay); + delay(200); + } + + delay(40); + } + + return false; + }, + + /** + * @param {number} [targetArea] - id of the area the portal leads to + * @param {string} [owner] - name of the portal's owner + * @param {ObjectUnit} [unit] - use existing portal unit + * @returns {boolean} + */ + usePortal: function (targetArea, owner, unit) { + if (targetArea && me.area === targetArea) return true; + + me.cancelUIFlags(); + + const preArea = me.area; + const changedArea = function () { + return me.area !== preArea; + }; + + for (let i = 0; i < 10; i += 1) { + if (me.dead) return false; + i > 0 && me.inTown && Town.move("portalspot"); + + const portal = unit + ? copyUnit(unit) + : this.getPortal(targetArea, owner); + + if (portal) { + if (portal.objtype === sdk.areas.DuranceofHateLvl3 && portal.getParent() !== me.name + && !Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)) { + throw new Error("Cannot access meph through someone elses portal without first completing Travincal"); + } + let redPortal = portal.classid === sdk.objects.RedPortal; + + if (portal.area === me.area) { + if (Skill.useTK(portal) && i < 3) { + if (portal.distance > 21) { + me.inArea(sdk.areas.Harrogath) + ? Town.move("portalspot") + : Pather.moveNearUnit(portal, 20); + } + if (Packet.telekinesis(portal)) { + if (Misc.poll(changedArea, 500, 50)) { + Pather.lastPortalTick = getTickCount(); + delay(100); + return true; + } + } + } else { + portal.distance > 5 && this.moveToUnit(portal); + + if (getTickCount() - this.lastPortalTick > 2500) { + i < 2 ? Packet.entityInteract(portal) : Misc.click(0, 0, portal); + !!redPortal && delay(150); + } else { + let timeTillNextPortal = Math.max(3, Math.round(2500 - (getTickCount() - this.lastPortalTick))); + delay(timeTillNextPortal); + + continue; + } + } + } + + // Portal to/from Arcane + if (portal.classid === sdk.objects.ArcaneSanctuaryPortal && portal.mode !== sdk.objects.mode.Active) { + Misc.click(0, 0, portal); + let tick = getTickCount(); + + while (getTickCount() - tick < 2000) { + if (portal.mode === sdk.objects.mode.Active || me.inArea(sdk.areas.ArcaneSanctuary)) { + break; + } + + delay(10); + } + } + + if (Misc.poll(changedArea, 500, 3)) { + Pather.lastPortalTick = getTickCount(); + delay(100); + + break; + } + + i > 1 && Packet.flash(me.gid); + } else { + Packet.flash(me.gid); + } + + delay(250); + } + + return (targetArea ? me.area === targetArea : me.area !== preArea); + }, + + /** + * @param {number} targetArea - id of the area the portal leads to + * @param {string} owner - name of the portal's owner + * @returns {ObjectUnit | false} + */ + getPortal: function (targetArea, owner) { + let portal = Game.getObject("portal"); + + if (portal) { + do { + if (typeof targetArea !== "number" || portal.objtype === targetArea) { + switch (owner) { + case undefined: // Pather.usePortal(area) - red portal + if (!portal.getParent() || portal.getParent() === me.name) { + return copyUnit(portal); + } + + break; + case null: // Pather.usePortal(area, null) - any blue portal leading to area + if (portal.getParent() === me.name || Misc.inMyParty(portal.getParent())) { + return copyUnit(portal); + } + + break; + default: // Pather.usePortal(null, owner) - any blue portal belonging to owner OR Pather.usePortal(area, owner) - blue portal matching area and owner + if (portal.getParent() === owner && (owner === me.name || Misc.inMyParty(owner))) { + return copyUnit(portal); + } + + break; + } + } + } while (portal.getNext()); + } + + return false; + }, + + /** + * @param {number} x - the starting x coord + * @param {number} y - the starting y coord + * @param {number} range - maximum allowed range from the starting coords + * @param {number} step - distance between each checked dot on the grid + * @param {number} coll - collision flag to avoid + * @param {number} size + * @returns {[number, number] | false} + */ + getNearestWalkable: function (x, y, range, step, coll, size) { + !step && (step = 1); + coll === undefined && (coll = sdk.collision.BlockWall); + + let distance = 1; + let result = false; + + // Check if the original spot is valid + if (this.checkSpot(x, y, coll, false, size)) { + result = [x, y]; + } + + MainLoop: + while (!result && distance < range) { + for (let i = -distance; i <= distance; i += 1) { + for (let j = -distance; j <= distance; j += 1) { + // Check outer layer only (skip previously checked) + if (Math.abs(i) >= Math.abs(distance) || Math.abs(j) >= Math.abs(distance)) { + if (this.checkSpot(x + i, y + j, coll, false, size)) { + result = [x + i, y + j]; + + break MainLoop; + } + } + } + } + + distance += step; + } + + CollMap.reset(); + + return result; + }, + + /** + * @param {number} x - the x coord to check + * @param {number} y - the y coord to check + * @param {number} coll - collision flag to search for + * @param {boolean} cacheOnly - use only cached room data + * @param {number} size + * @returns {boolean} + */ + checkSpot: function (x, y, coll, cacheOnly, size) { + coll === undefined && (coll = sdk.collision.BlockWall); + !size && (size = 1); + + for (let dx = -size; dx <= size; dx += 1) { + for (let dy = -size; dy <= size; dy += 1) { + if (Math.abs(dx) !== Math.abs(dy)) { + let value = CollMap.getColl(x + dx, y + dy, cacheOnly); + + if (value & coll) { + return false; + } + } + } + } + + return true; + }, + + /** + * @deprecated use `me.accessToAct(act)` instead + * @param {number} act - the act number to check for access + * @returns {boolean} + */ + accessToAct: function (act) { + return me.accessToAct(act); + }, + + /** + * @param {number} area - the id of area to get the waypoint in + * @param {boolean} [clearPath] + * @returns {boolean} + */ + getWP: function (area, clearPath) { + area !== me.area && this.journeyTo(area); + + for (let i = 0; i < sdk.waypoints.Ids.length; i++) { + let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); + + if (preset) { + Skill.haveTK + ? Pather.moveNearUnit(preset, 20, clearPath) + : Pather.moveToUnit(preset, 0, 0, clearPath); + + let wp = Game.getObject("waypoint"); + + if (wp) { + for (let j = 0; j < 10; j++) { + if (!getUIFlag(sdk.uiflags.Waypoint)) { + if (wp.distance > 5 && Skill.useTK(wp) && j < 3) { + wp.distance > 21 && Attack.getIntoPosition(wp, 20, sdk.collision.Ranged); + Packet.telekinesis(wp); + } else if (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint)) { + this.moveToUnit(wp) && Misc.click(0, 0, wp); + } + } + + if (Misc.poll(() => me.gameReady && getUIFlag(sdk.uiflags.Waypoint), 1000, 150)) { + delay(500); + !Pather.initialized && (Pather.initialized = true); + me.cancelUIFlags(); + + return true; + } + + // handle getUnit bug + if (!getUIFlag(sdk.uiflags.Waypoint) && me.inTown && wp.name.toLowerCase() === "dummy") { + Town.getDistance("waypoint") > 5 && Town.move("waypoint"); + Misc.click(0, 0, wp); + } + + delay(500); + } + } + } + } + + return false; + }, + + /** + * @param {number} area - the id of area to move to + * @returns {boolean} + * @todo refactor this, it's rather messy + */ + journeyTo: function (area) { + if (area === undefined) return false; + let target, retry = 0; + + if (area !== sdk.areas.DurielsLair) { + target = this.plotCourse(area, me.area); + } else { + target = { course: [sdk.areas.CanyonofMagic, sdk.areas.DurielsLair], useWP: false }; + this.wpAreas.indexOf(me.area) === -1 && (target.useWP = true); + } + + console.info(true, "Course :: " + target.course, "journeyTo"); + if (area === sdk.areas.PandemoniumFortress && me.inArea(sdk.areas.DuranceofHateLvl3)) { + target.useWP = false; + } + target.useWP && Town.goToTown(); + + // handle variable flayer jungle entrances + if (target.course.includes(sdk.areas.FlayerJungle)) { + Town.goToTown(3); // without initiated act, getArea().exits will crash + let special = getArea(sdk.areas.FlayerJungle); + + if (special) { + special = special.exits; + + for (let i = 0; i < special.length; i += 1) { + if (special[i].target === sdk.areas.GreatMarsh) { + // add great marsh if needed + target.course.splice(target.course.indexOf(sdk.areas.FlayerJungle), 0, sdk.areas.GreatMarsh); + + break; + } + } + } + } + + while (target.course.length) { + const currArea = me.area; + const targetArea = target.course[0]; + let unit; + + if (currArea === targetArea && target.course.shift()) { + continue; + } + + console.info(null, "ÿc0Moving from: " + getAreaName(currArea) + " to " + getAreaName(targetArea)); + + if (!me.inTown) { + Precast.doPrecast(false); + + if (this.wpAreas.includes(currArea) + && !getWaypoint(this.wpAreas.indexOf(currArea))) { + this.getWP(currArea); + } + } + + if (me.inTown && this.nextAreas[currArea] !== targetArea + && this.wpAreas.includes(targetArea) && getWaypoint(this.wpAreas.indexOf(targetArea))) { + this.useWaypoint(targetArea, !Pather.initialized); + Precast.doPrecast(false); + } else if (currArea === sdk.areas.StonyField && targetArea === sdk.areas.Tristram) { + // Stony Field -> Tristram + this.moveToPreset(currArea, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 0, 0, false, true); + Misc.poll(() => this.usePortal(sdk.areas.Tristram), 5000, 1000); + } else if (currArea === sdk.areas.LutGholein && targetArea === sdk.areas.A2SewersLvl1) { + // Lut Gholein -> Sewers Level 1 (use Trapdoor) + this.moveToPreset(currArea, sdk.unittype.Stairs, sdk.exits.preset.A2SewersTrapDoor); + this.useUnit(sdk.unittype.Object, sdk.objects.TrapDoorA2, sdk.areas.A2SewersLvl1); + } else if (currArea === sdk.areas.A2SewersLvl2 && targetArea === sdk.areas.A2SewersLvl1) { + // Sewers Level 2 -> Sewers Level 1 + Pather.moveToExit(targetArea, false); + this.useUnit(sdk.unittype.Stairs, sdk.objects.A2UndergroundUpStairs, sdk.areas.A2SewersLvl1); + } else if (currArea === sdk.areas.PalaceCellarLvl3 && targetArea === sdk.areas.ArcaneSanctuary) { + // Palace -> Arcane + this.moveTo(10073, 8670); + this.usePortal(null); + } else if (currArea === sdk.areas.ArcaneSanctuary && targetArea === sdk.areas.PalaceCellarLvl3) { + // Arcane Sanctuary -> Palace Cellar 3 + this.moveNearPreset( + currArea, + sdk.unittype.Object, + sdk.objects.ArcaneSanctuaryPortal, + (Skill.haveTK ? 20 : 5) + ); + unit = Misc.poll(() => Game.getObject(sdk.objects.ArcaneSanctuaryPortal)); + unit && Pather.useUnit(sdk.unittype.Object, sdk.objects.ArcaneSanctuaryPortal, sdk.areas.PalaceCellarLvl3); + } else if (currArea === sdk.areas.ArcaneSanctuary && targetArea === sdk.areas.CanyonofMagic) { + // Arcane Sanctuary -> Canyon of the Magic + this.moveToPreset(currArea, sdk.unittype.Object, sdk.objects.Journal); + unit = Game.getObject(sdk.objects.RedPortal); + + if (!unit || !this.usePortal(null, null, unit)) { + for (let i = 0; i < 5; i++) { + unit = Game.getObject(sdk.objects.Journal); + + // couldnt find journal? Move to it's preset + if (!unit) { + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.Journal); + continue; + } else if (unit && unit.distance > 20) { + Pather.moveNearUnit(unit, 13); + } + + Packet.entityInteract(unit); + Misc.poll(() => getIsTalkingNPC(), 1000, 50); + me.cancel(); + + if (this.usePortal(sdk.areas.CanyonofMagic)) { + break; + } + } + } + } else if (currArea === sdk.areas.CanyonofMagic && targetArea === sdk.areas.DurielsLair) { + // Canyon -> Duriels Lair + this.moveToExit(getRoom().correcttomb, true); + this.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.HoradricStaffHolder); + unit = Misc.poll(() => Game.getObject(sdk.objects.PortaltoDurielsLair)); + unit && Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair); + } else if (currArea === sdk.areas.Travincal && targetArea === sdk.areas.DuranceofHateLvl1) { + // Trav -> Durance Lvl 1 + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.DuranceEntryStairs); + this.useUnit(sdk.unittype.Object, sdk.objects.DuranceEntryStairs, sdk.areas.DuranceofHateLvl1); + } else if (currArea === sdk.areas.DuranceofHateLvl3 && targetArea === sdk.areas.PandemoniumFortress) { + // Durance Lvl 3 -> Pandemonium Fortress + if (me.getQuest(sdk.quest.id.TheGuardian, sdk.quest.states.Completed) !== 1) { + console.log(sdk.colors.Red + "(journeyTo) :: Incomplete Quest"); + return false; + } + + Pather.moveTo(17581, 8070); + delay(250 + me.ping * 2); + this.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct4, sdk.areas.PandemoniumFortress); + } else if (currArea === sdk.areas.Harrogath && targetArea === sdk.areas.BloodyFoothills) { + // Harrogath -> Bloody Foothills + this.moveTo(5026, 5095); + this.openUnit(sdk.unittype.Object, sdk.objects.Act5Gate); + this.moveToExit(targetArea, true); + } else if (currArea === sdk.areas.Harrogath && targetArea === sdk.areas.NihlathaksTemple) { + // Harrogath -> Nihlathak's Temple + Town.move(NPC.Anya); + if (!Pather.getPortal(sdk.areas.NihlathaksTemple) + && Misc.checkQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.ReqComplete)) { + Town.npcInteract("Anya"); + } + this.usePortal(sdk.areas.NihlathaksTemple); + } else if (currArea === sdk.areas.FrigidHighlands && targetArea === sdk.areas.Abaddon) { + // Abaddon + this.moveToPreset(sdk.areas.FrigidHighlands, sdk.unittype.Object, sdk.objects.RedPortal); + this.usePortal(sdk.areas.Abaddon); + } else if (currArea === sdk.areas.ArreatPlateau && targetArea === sdk.areas.PitofAcheron) { + // Pits of Archeon + this.moveToPreset(sdk.areas.ArreatPlateau, sdk.unittype.Object, sdk.objects.RedPortal); + this.usePortal(sdk.areas.PitofAcheron); + } else if (currArea === sdk.areas.FrozenTundra && targetArea === sdk.areas.InfernalPit) { + // Infernal Pit + this.moveToPreset(sdk.areas.FrozenTundra, sdk.unittype.Object, sdk.objects.RedPortal); + this.usePortal(sdk.areas.InfernalPit); + } else if (targetArea === sdk.areas.MooMooFarm) { + // Moo Moo farm + currArea !== sdk.areas.RogueEncampment && Town.goToTown(1); + Town.move("stash") && (unit = this.getPortal(targetArea)); + unit && this.usePortal(null, null, unit); + } else if ([ + sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain, sdk.areas.UberTristram + ].includes(targetArea)) { + // Uber Portals + currArea !== sdk.areas.Harrogath && Town.goToTown(5); + Town.move("stash") && (unit = this.getPortal(targetArea)); + unit && this.usePortal(null, null, unit); + } else { + this.moveToExit(targetArea, true); + } + + // give time for act to load, increases stabilty of changing acts + delay(500); + + if (me.area === targetArea) { + target.course.shift(); + retry = 0; + } else { + if (retry > 3) { + console.warn("Failed to journeyTo " + getAreaName(area) + " currentarea: " + getAreaName(me.area)); + return false; + } + retry++; + } + } + + console.info(false, "ÿc4MyArea: ÿc0" + getAreaName(me.area), "journeyTo"); + return me.area === area; + }, + + plotCourse_openedWpMenu: false, + + /** + * Plot a course to a specific area + * @param {number} src - starting area id + * @param {number} dest - destination area id + * @returns {{ course: number[], useWP: boolean } | false} + * @todo this needs more checks + */ + plotCourse: function (dest, src) { + let node, prevArea; + let useWP = false; + let arr = []; + // need to redo this...that's gonna be a pain + const previousAreas = [ + sdk.areas.None, sdk.areas.None, sdk.areas.RogueEncampment, sdk.areas.BloodMoor, sdk.areas.ColdPlains, + sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, sdk.areas.BlackMarsh, + sdk.areas.BloodMoor, sdk.areas.ColdPlains, sdk.areas.StonyField, + sdk.areas.BlackMarsh, sdk.areas.TamoeHighland, sdk.areas.CaveLvl1, + sdk.areas.UndergroundPassageLvl1, sdk.areas.HoleLvl1, + sdk.areas.PitLvl1, sdk.areas.ColdPlains, sdk.areas.BurialGrounds, + sdk.areas.BurialGrounds, sdk.areas.BlackMarsh, sdk.areas.ForgottenTower, + sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, + sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TamoeHighland, + sdk.areas.MonasteryGate, sdk.areas.OuterCloister, sdk.areas.Barracks, + sdk.areas.JailLvl1, sdk.areas.JailLvl2, + sdk.areas.JailLvl3, sdk.areas.InnerCloister, sdk.areas.Cathedral, + sdk.areas.CatacombsLvl1, sdk.areas.CatacombsLvl2, sdk.areas.CatacombsLvl3, + sdk.areas.StonyField, sdk.areas.RogueEncampment, + sdk.areas.RogueEncampment, sdk.areas.LutGholein, sdk.areas.RockyWaste, + sdk.areas.DryHills, sdk.areas.FarOasis, sdk.areas.LostCity, + sdk.areas.ArcaneSanctuary, sdk.areas.LutGholein, + sdk.areas.A2SewersLvl1, sdk.areas.A2SewersLvl2, sdk.areas.LutGholein, + sdk.areas.HaremLvl1, sdk.areas.HaremLvl2, sdk.areas.PalaceCellarLvl1, + sdk.areas.PalaceCellarLvl2, sdk.areas.RockyWaste, + sdk.areas.DryHills, sdk.areas.HallsoftheDeadLvl1, sdk.areas.ValleyofSnakes, + sdk.areas.StonyTombLvl1, sdk.areas.HallsoftheDeadLvl2, sdk.areas.ClawViperTempleLvl1, sdk.areas.FarOasis, + sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.LostCity, + sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, + sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, + sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.RogueEncampment, + sdk.areas.PalaceCellarLvl3, sdk.areas.RogueEncampment, sdk.areas.KurastDocktown, sdk.areas.SpiderForest, + sdk.areas.SpiderForest, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, + sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.KurastCauseway, + sdk.areas.SpiderForest, sdk.areas.SpiderForest, sdk.areas.FlayerJungle, + sdk.areas.SwampyPitLvl1, sdk.areas.FlayerJungle, sdk.areas.FlayerDungeonLvl1, + sdk.areas.SwampyPitLvl2, sdk.areas.FlayerDungeonLvl2, + sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.KurastBazaar, + sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.UpperKurast, + sdk.areas.KurastCauseway, sdk.areas.KurastCauseway, + sdk.areas.Travincal, sdk.areas.DuranceofHateLvl1, sdk.areas.DuranceofHateLvl2, + sdk.areas.DuranceofHateLvl3, sdk.areas.PandemoniumFortress, sdk.areas.OuterSteppes, sdk.areas.PlainsofDespair, + sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, sdk.areas.PandemoniumFortress, + sdk.areas.Harrogath, sdk.areas.BloodyFoothills, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, + sdk.areas.CrystalizedPassage, sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, + sdk.areas.GlacialTrail, sdk.areas.FrozenTundra, sdk.areas.AncientsWay, + sdk.areas.AncientsWay, sdk.areas.Harrogath, + sdk.areas.NihlathaksTemple, sdk.areas.HallsofAnguish, sdk.areas.HallsofPain, + sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.FrozenTundra, + sdk.areas.ArreatSummit, sdk.areas.WorldstoneLvl1, + sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction, + sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath + ]; + let visitedNodes = []; + let toVisitNodes = [{ from: dest, to: null }]; + + !src && (src = me.area); + + if (!Pather.initialized + && me.inTown + && Pather.nextAreas[me.area] !== dest + && Pather.useWaypoint(null)) { + Pather.initialized = true; + } + + while (toVisitNodes.length > 0) { + node = toVisitNodes[0]; + + // If we've already visited it, just move on + if (visitedNodes[node.from] === undefined) { + visitedNodes[node.from] = node.to; + + if (this.areasConnected(node.from, node.to)) { + // If we have this wp we can start from there + if ((me.inTown // check wp in town + || ((src !== previousAreas[dest] && dest !== previousAreas[src]) // check wp if areas aren't linked + && previousAreas[src] !== previousAreas[dest])) // check wp if areas aren't linked with a common area + && Pather.wpAreas.indexOf(node.from) > 0 && getWaypoint(Pather.wpAreas.indexOf(node.from)) + ) { + if (node.from !== src) { + useWP = true; + } + + src = node.from; + } + + // We found it, time to go + if (node.from === src) { + break; + } + + if ((prevArea = previousAreas[node.from]) !== 0 && visitedNodes.indexOf(prevArea) === -1) { + toVisitNodes.push({ from: prevArea, to: node.from }); + } + + for (prevArea = 1; prevArea < previousAreas.length; prevArea += 1) { + // Only interested in those connected to node + if (previousAreas[prevArea] === node.from && visitedNodes.indexOf(prevArea) === -1) { + toVisitNodes.push({ from: prevArea, to: node.from }); + } + } + } + + toVisitNodes.shift(); + } else { + useWP = true; + } + } + + arr.push(src); + + node = src; + + while (node !== dest && node !== undefined) { + arr.push(node = visitedNodes[node]); + } + + // Something failed + if (node === undefined) { + return false; + } + + return { course: arr, useWP: useWP }; + }, + + /** + * Check if two areas are connected + * @param {number} src - starting area id + * @param {number} dest - destination area id + * @returns {boolean} + * @todo this needs more checks + */ + areasConnected: function (src, dest) { + if (src === sdk.areas.CanyonofMagic && dest === sdk.areas.ArcaneSanctuary) { + return false; + } + + return true; + }, + + /** + * @param {number} xMin + * @param {number} xMax + * @param {number} yMin + * @param {number} yMax + * @param {number} factor + */ + randMove: function (xMin, xMax, yMin, yMax, factor) { + xMin === undefined && (xMin = -4); + xMax === undefined && (xMax = 4); + yMin === undefined && (yMin = -4); + yMax === undefined && (yMax = 4); + factor === undefined && (factor = 1); + /** @type {PathNode} */ + const coord = CollMap.getRandCoordinate(me.x, -4, 4, me.y, -4, 4, factor); + return Pather.move(coord, { retry: 3, allowClearing: false }); + }, + + /** + * @param {number} x + * @param {number} y + * @param {number} [area] + * @param {number} [xx] + * @param {number} [yy] + * @param {number} [reductionType] + * @param {number} [radius] + * @returns {number} + */ + getWalkDistance: function (x, y, area, xx, yy, reductionType, radius) { + area === undefined && (area = me.area); + xx === undefined && (xx = me.x); + yy === undefined && (yy = me.y); + reductionType === undefined && (reductionType = 2); + radius === undefined && (radius = 5); + // distance between node x and x-1 + return (getPath(area, x, y, xx, yy, reductionType, radius) || []) + .map(function (e, i, s) { + return i && getDistance(s[i - 1], e) || 0; + }) + .reduce(function (acc, cur) { + return acc + cur; + }, 0) || Infinity; + }, +}; + +Pather.nextAreas[sdk.areas.RogueEncampment] = sdk.areas.BloodMoor; +Pather.nextAreas[sdk.areas.LutGholein] = sdk.areas.RockyWaste; +Pather.nextAreas[sdk.areas.KurastDocktown] = sdk.areas.SpiderForest; +Pather.nextAreas[sdk.areas.PandemoniumFortress] = sdk.areas.OuterSteppes; +Pather.nextAreas[sdk.areas.Harrogath] = sdk.areas.BloodyFoothills; + +/** + * Trick to let the OOG script cache the getWaypoint + * @param {Object} globalThis + * @param {(id: number) => boolean} original + */ +(function (globalThis, original) { + globalThis._getWaypoint = original; + + globalThis.getWaypoint = function (id, noCache = false) { + if (noCache) { + return original(id); + } + // You got it + if (me.waypoints[id]) { + return true; + } + // You cant lose a wp, you can gain one. Store the result + const result = original(id); + if (result !== me.waypoints[id]) { + // we've got a mismatch, update the cache + me.waypoints[id] = result; + scriptBroadcast({ type: "cache-waypoints", data: me.waypoints }); + } + return result; + }; +})([].filter.constructor("return this")(), getWaypoint); diff --git a/d2bs/kolbot/libs/core/Pickit.js b/d2bs/kolbot/libs/core/Pickit.js new file mode 100644 index 000000000..f24b07017 --- /dev/null +++ b/d2bs/kolbot/libs/core/Pickit.js @@ -0,0 +1,762 @@ +/** +* @filename Pickit.js +* @author kolton, theBGuy +* @desc handle item pickup +* +*/ + +/** + * @namespace Pickit + */ +const Pickit = { + gidList: new Set(), + invoLocked: true, + beltSize: 1, + /** @enum */ + Result: { + UNID: -1, + UNWANTED: 0, + WANTED: 1, + CUBING: 2, + RUNEWORD: 3, + TRASH: 4, + CRAFTING: 5, + UTILITY: 6 + }, + /** + * Ignored item types for item logging + */ + ignoreLog: [ + sdk.items.type.Gold, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver, + sdk.items.type.Scroll, sdk.items.type.Key, sdk.items.type.HealingPotion, + sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion, + sdk.items.type.StaminaPotion, sdk.items.type.AntidotePotion, sdk.items.type.ThawingPotion + ], + tkable: [ + sdk.items.type.Gold, sdk.items.type.Scroll, sdk.items.type.HealingPotion, + sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion, + sdk.items.type.StaminaPotion, sdk.items.type.AntidotePotion, sdk.items.type.ThawingPotion + ], + essentials: [ + sdk.items.type.Gold, sdk.items.type.Scroll, + sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion + ], + + /** + * @param {boolean} notify + */ + init: function (notify) { + Config.PickitFiles.forEach((file) => NTIP.OpenFile("pickit/" + file, notify)); + Config.PickitLines.forEach(function (line) { + if (Array.isArray(line)) { + let [str, file] = line; + NTIP.addLine(str, file); + } else { + NTIP.addLine(line); + } + }); + // check if we can pick up items, only do this is our inventory slots aren't completly locked + Pickit.invoLocked = !Config.Inventory.some(row => row.some(el => el > 0)); + + // sometime Storage isn't loaded? + if (typeof Storage !== "undefined") { + Pickit.beltSize = Storage.BeltSize(); + // If MinColumn is set to be more than our current belt size, set it to be 1 less than the belt size 4x3 belt will give us Config.MinColumn = [2, 2, 2, 2] + Config.MinColumn.forEach((el, index) => { + el >= Pickit.beltSize && (Config.MinColumn[index] = Math.max(1, Pickit.beltSize - 1)); + }); + } + }, + + // eslint-disable-next-line no-unused-vars + itemEvent: function (gid, mode, code, global) { + // console.log("gid: " + gid, " mode: " + mode, " code: " + code, " global: " + global); + if (gid > 0 && mode === 0) { + Pickit.gidList.add(gid); + } + }, + + /** + * Just sort by distance for general item pickup + * @param {ItemUnit} unitA + * @param {ItemUnit} unitB + */ + sortItems: function (unitA, unitB) { + return getDistance(me, unitA) - getDistance(me, unitB); + }, + + /** + * Prioritize runes and unique items for fast pick + * @param {ItemUnit} unitA + * @param {ItemUnit} unitB + */ + sortFastPickItems: function (unitA, unitB) { + if (unitA.itemType === sdk.items.type.Rune || unitA.unique) return -1; + if (unitB.itemType === sdk.items.type.Rune || unitB.unique) return 1; + + return getDistance(me, unitA) - getDistance(me, unitB); + }, + + checkBelt: function () { + let check = 0; + let item = me.getItem(-1, sdk.items.mode.inBelt); + + if (item) { + do { + if (item.x < 4) { + check += 1; + } + } while (item.getNext()); + } + + return check === 4; + }, + + /** + * @param {ItemUnit} unit + */ + canPick: function (unit) { + if (!unit) return false; + if (sdk.quest.items.includes(unit.classid) && me.getItem(unit.classid)) return false; + + switch (unit.itemType) { + case sdk.items.type.Gold: + // Check current gold vs max capacity (cLvl*10000) + if (me.getStat(sdk.stats.Gold) === me.maxgold) { + return false; // Skip gold if full + } + return true; + case sdk.items.type.Scroll: + { + // 518 - Tome of Town Portal or 519 - Tome of Identify + let tome = me.getItem(unit.classid - 11, sdk.items.mode.inStorage); + // Don't pick scrolls if there's no tome + if (!tome) return false; + do { + if (tome.isInInventory && tome.getStat(sdk.stats.Quantity) < 20) { + return true; + } + } while (tome.getNext()); + } + // Couldn't find a tome that wasn't full. Skipping scroll + return false; + case sdk.items.type.Key: + { + // Assassins don't ever need keys + if (me.assassin) return false; + + let myKey = me.getItem(sdk.items.Key, sdk.items.mode.inStorage); + let key = Game.getItem(-1, -1, unit.gid); // Passed argument isn't an actual unit, we need to get it + + if (myKey && key) { + do { + if (myKey.isInInventory && myKey.getStat(sdk.stats.Quantity) + key.getStat(sdk.stats.Quantity) > 12) { + return false; + } + } while (myKey.getNext()); + } + } + break; + case sdk.items.type.SmallCharm: + case sdk.items.type.LargeCharm: + case sdk.items.type.GrandCharm: + if (unit.unique) { + let charm = me.getItem(unit.classid, sdk.items.mode.inStorage); + + if (charm) { + do { + // Skip Gheed's Fortune, Hellfire Torch or Annihilus if we already have one + if (charm.unique) return false; + } while (charm.getNext()); + } + } + + break; + case sdk.items.type.HealingPotion: + case sdk.items.type.ManaPotion: + case sdk.items.type.RejuvPotion: + { + let needPots = 0; + const _pots = new Map([ + [sdk.items.type.HealingPotion, { count: 0 }], + [sdk.items.type.ManaPotion, { count: 0 }], + [sdk.items.type.RejuvPotion, { count: 0 }], + [sdk.items.type.AntidotePotion, { count: 0 }], + [sdk.items.type.StaminaPotion, { count: 0 }], + [sdk.items.type.ThawingPotion, { count: 0 }], + ]); + + for (let column of Config.BeltColumn) { + if (unit.code && unit.code.includes(column)) { + needPots += Pickit.beltSize; + } + } + + let potion = me.getItem(-1, sdk.items.mode.inBelt); + + if (potion) { + do { + _pots.get(potion.itemType).count += 1; + if (potion.itemType === unit.itemType) { + needPots -= 1; + } + } while (potion.getNext()); + } + + if (needPots < 1 && this.checkBelt()) { + const _buffers = new Map([ + ["HPBuffer", { type: sdk.items.type.HealingPotion, amount: Config.HPBuffer }], + ["MPBuffer", { type: sdk.items.type.ManaPotion, amount: Config.MPBuffer }], + ["RejuvBuffer", { type: sdk.items.type.RejuvPotion, amount: Config.RejuvBuffer }] + ]); + + for (let buffer of _buffers) { + if (buffer[1].amount <= 0) continue; + if (buffer[1].type !== unit.itemType) continue; + needPots = buffer[1].amount; + potion = me.getItem(-1, sdk.items.mode.inStorage); + + if (potion) { + do { + if (potion.isInInventory && _pots.has(potion.itemType)) { + _pots.get(potion.itemType).count += 1; + if (potion.itemType === buffer[1].type) { + needPots -= 1; + } + } + } while (potion.getNext()); + } + } + } + + if (needPots < 1) { + potion = me.getItem(); + + if (potion) { + do { + if (potion.itemType === unit.itemType + && (potion.isInInventory || potion.isInBelt)) { + if (potion.classid < unit.classid) { + potion.use(); + needPots += 1; + + break; + } + } + } while (potion.getNext()); + } + } + + return (needPots > 0); + } + case undefined: // Yes, it does happen + console.warn("undefined item (!?)"); + + return false; + default: + // don't attempt items we are simply unable to pick up + return Storage.Inventory.IsPossibleToFit(unit); + } + + return true; + }, + + /** + * @param {ItemUnit} unit + * @returns { { result: PickitResult, line: string } } + * -1 : Needs iding, + * 0 : Unwanted, + * 1 : NTIP wants, + * 2 : Cubing wants, + * 3 : Runeword wants, + * 4 : Pickup to sell (triggered when low on gold) + */ + checkItem: function (unit) { + const rval = NTIP.CheckItem(unit, false, true); + const resultObj = function (result, line = null) { + return { + result: result, + line: line + }; + }; + + // make sure we have essentials - no pickit files loaded + if (rval.result === Pickit.Result.UNWANTED && Config.PickitFiles.length === 0 + && Pickit.essentials.includes(unit.itemType) && this.canPick(unit)) { + return resultObj(Pickit.Result.WANTED, "Essentials"); + } + + if ((unit.classid === sdk.items.runes.Ort || unit.classid === sdk.items.runes.Ral) + && Cubing.repairIngredientCheck(unit)) { + return resultObj(Pickit.Result.UTILITY, "Cubing Repair Ingredients"); + } + + if (CraftingSystem.checkItem(unit)) return resultObj(Pickit.Result.CRAFTING, "Crafting System"); + if (Cubing.checkItem(unit)) return resultObj(Pickit.Result.CUBING, "Cubing"); + if (Runewords.checkItem(unit)) return resultObj(Pickit.Result.RUNEWORD, "Runewords"); + + // if Gemhunting, pick Item for Cubing, if no other system needs it + if (Scripts.GemHunter && rval.result === Pickit.Result.UNWANTED) { + // gemhunter active + if (Config.GemHunter.GemList.some((p) => [unit.classid - 1, unit.classid].includes(p))) { + // base and upgraded gem will be kept + let _items = me.getItemsEx(unit.classid, sdk.items.mode.inStorage) + .filter(function (i) { + return i.gid !== unit.gid + && !CraftingSystem.checkItem(i) + && !Cubing.checkItem(i) + && !Runewords.checkItem(i); + }); + if (_items.length === 0) return resultObj(Pickit.Result.WANTED, "GemHunter"); + } + } + + if (rval.result === Pickit.Result.UNWANTED + && !Town.ignoreType(unit.itemType) + && !unit.questItem + && ( + (unit.isInInventory && (me.inTown || !Config.FieldID.Enabled)) + || me.gold < Config.LowGold + || (me.gold < 500000 && Config.PickitFiles.length === 0) + || (me.gold < me.getRepairCost()) + )) { + // Gold doesn't ta=ke up room, just pick it up + if (unit.classid === sdk.items.Gold) return resultObj(Pickit.Result.WANTED, "LowGold"); + + if (!this.invoLocked) { + const itemValue = unit.getItemCost(sdk.items.cost.ToSell); + const itemValuePerSquare = itemValue / (unit.sizex * unit.sizey); + + if (itemValuePerSquare >= 2000) { + // If total gold is less than 500k pick up anything worth 2k gold per square to sell in town. + return resultObj(Pickit.Result.TRASH, "Valuable LowGold Item: " + itemValue); + } else if (itemValuePerSquare >= 10) { + // If total gold is less than LowGold setting pick up anything worth 10 gold per square to sell in town. + return resultObj(Pickit.Result.TRASH, "LowGold Item: " + itemValue); + } + } + } + + return rval; + }, + + track: { + lastItem: null, + }, + + /** + * @param {ItemUnit} unit + * @param {PickitResult} status + * @param {string} keptLine + * @param {number} retry + * @todo figure out why sometimes we double print picking up an item, gut feeling is recursion somewhere + */ + pickItem: function (unit, status, keptLine, retry = 3) { + /** + * @constructor + * @param {ItemUnit} unit + */ + function ItemStats (unit) { + this.gid = unit.gid; + this.ilvl = unit.ilvl; + this.type = unit.itemType; + this.classid = unit.classid; + this.name = unit.name; + this.color = Item.color(unit); + this.gold = unit.getStat(sdk.stats.Gold); + this.dist = (unit.distance || Infinity); + this.useTk = (Skill.haveTK && Pickit.tkable.includes(this.type) + && this.dist > 5 && this.dist < 20 && !checkCollision(me, unit, sdk.collision.WallOrRanged)); + this.picked = false; + } + + const itemCount = me.itemcount; + const cancelFlags = [ + sdk.uiflags.Inventory, sdk.uiflags.NPCMenu, + sdk.uiflags.Waypoint, sdk.uiflags.Shop, + sdk.uiflags.Stash, sdk.uiflags.Cube + ]; + + if (!unit.gid) return false; + let item = Game.getItem(-1, -1, unit.gid); + if (!item) return false; + if (!item.onGroundOrDropping) return false; + + if (cancelFlags.some(getUIFlag)) { + delay(500); + me.cancel(0); + } + + const stats = new ItemStats(item); + const tkMana = stats.useTk + ? Skill.getManaCost(sdk.skills.Telekinesis) * 2 + : Infinity; + + MainLoop: + for (let i = 0; i < retry; i++) { + if (me.dead) return false; + // recursion appeared + if (this.track.lastItem === stats.gid) return true; + // can't find the item + if (!Game.getItem(-1, -1, stats.gid)) return false; + + if (me.getItem(stats.classid, -1, stats.gid)) { + console.debug("Already picked item"); + return true; + } + + while (!me.idle) { + delay(40); + } + + if (!item.onGroundOrDropping) { + break; + } + + // fastPick check? should only pick items if surrounding monsters have been cleared or if fastPick is active + // note: clear of surrounding monsters of the spectype we are set to clear + if (stats.useTk && me.mp > tkMana) { + if (!Packet.telekinesis(item)) { + i > 1 && (stats.useTk = false); + continue; + } + } else { + if (item.distance > (Config.FastPick || i < 1 ? 6 : 4) + || checkCollision(me, item, sdk.collision.BlockWall)) { + if (!Pather.move(item, { retry: 3, allowPicking: false, minDist: 4 })) { + continue; + } + // we had to move, lets check to see if it's still there + if (me.getItem(stats.classid, -1, stats.gid)) { + // we picked the item during another process - recursion happened + // this has pontential to skip logging an item + return true; + } + if (!Game.getItem(stats.classid, -1, stats.gid)) { + // it's gone so don't continue, + return false; + } + } + + // use packet first, if we fail and not using fast pick use click + (Config.FastPick || i < 1) + ? Packet.click(item) + : Misc.click(0, 0, item); + } + + let tick = getTickCount(); + + while (getTickCount() - tick < 1000) { + // why the use of copyUnit here? + item = copyUnit(item); + + if (stats.classid === sdk.items.Gold) { + let _gold = item.gold; + if (!_gold || _gold < stats.gold) { + console.log( + "ÿc7Picked up " + stats.color + + (_gold ? (_gold - stats.gold) : stats.gold) + + " " + stats.name + + (keptLine ? " ÿc0(" + keptLine + ")" : "") + ); + return true; + } + } + + if (!item.onGroundOrDropping) { + switch (stats.classid) { + case sdk.items.Key: + console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc7(" + me.checkKeys() + "/12)"); + + return true; + case sdk.items.ScrollofTownPortal: + case sdk.items.ScrollofIdentify: + console.log( + "ÿc7Picked up " + stats.color + stats.name + + " ÿc7(" + me.checkScrolls(stats.classid === sdk.items.ScrollofTownPortal ? "tbk" : "ibk") + "/20)" + ); + return true; + } + + break MainLoop; + } + + delay(20); + } + + // TK failed, disable it + stats.useTk = false; + } + + stats.picked = me.itemcount > itemCount || !!me.getItem(stats.classid, -1, stats.gid); + + if (stats.picked) { + DataFile.updateStats("lastArea"); + const _common = "ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + ")"; + const pickedItem = me.getItem(stats.classid, -1, stats.gid); + if (!pickedItem) return false; + + switch (status) { + case Pickit.Result.WANTED: + console.log(_common + (keptLine ? " (" + keptLine + ")" : "")); + if (Pickit.ignoreLog.indexOf(pickedItem.itemType) === -1) { + Item.logger("Kept", pickedItem); + Item.logItem("Kept", pickedItem, keptLine); + } + + break; + case Pickit.Result.CUBING: + console.log(_common + " (Cubing)"); + Item.logger("Kept", pickedItem, "Cubing " + me.findItems(pickedItem.classid).length); + Cubing.update(); + + break; + case Pickit.Result.RUNEWORD: + console.log(_common + " (Runewords)"); + Item.logger("Kept", pickedItem, "Runewords"); + Runewords.update(pickedItem.classid, pickedItem.gid); + + break; + case Pickit.Result.CRAFTING: + console.log(_common + " (Crafting System)"); + CraftingSystem.update(pickedItem); + + break; + default: + console.log(_common + (keptLine ? " (" + keptLine + ")" : "")); + + break; + } + + this.track.lastItem = pickedItem.gid; + } + + return true; + }, + + /** + * Check if we can even free up the inventory + */ + canMakeRoom: function () { + if (!Config.MakeRoom) return false; + + let items = Storage.Inventory.Compare(Config.Inventory) || []; + + if (items.length) { + return items.some(function (item) { + switch (Pickit.checkItem(item).result) { + case Pickit.Result.UNID: + // For low level chars that can't actually get id scrolls -> prevent an infinite loop + return (me.gold > 100); + case Pickit.Result.UNWANTED: + case Pickit.Result.TRASH: + // if we've got items to sell then we can make room as long as we can get to town + return me.canTpToTown(); + default: // Check if a kept item can be stashed + return Town.canStash(item); + } + }); + } + + return false; + }, + + /** @type {ItemUnit[]} */ + pickList: [], + /** @type {Set} */ + ignoreList: new Set(), + + /** + * @param {number} range + * @returns {boolean} If we picked items + */ + pickItems: function (range = Config.PickRange) { + if (me.dead) return false; + + let needMule = false; + const canUseMule = AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo"); + const _pots = [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion]; + /** @param {ItemUnit} item */ + const copyItem = function (item) { + return { + gid: item.gid, + x: item.x, + y: item.y, + classid: item.classid, + itemType: item.itemType, + }; + }; + + // why wait for idle? + while (!me.idle) { + delay(40); + } + + let item = Game.getItem(); + + if (item) { + do { + if (Pickit.ignoreList.has(item.gid)) continue; + if (item.classid === sdk.items.Gold && item.distance <= 4 && Pickit.canPick(item)) { + if (Pickit.pickItem(item, Pickit.Result.WANTED, "gold", 1)) continue; + } + if (Pickit.pickList.some(el => el.gid === item.gid)) continue; + if (item.onGroundOrDropping && item.distance <= range) { + Pickit.pickList.push(copyItem(item)); + } + } while (item.getNext()); + } + + if (Pickit.pickList.some(function (el) { + return _pots.includes(el.itemType); + })) { + me.clearBelt(); + } + + while (Pickit.pickList.length > 0) { + if (me.dead) return false; + Pickit.pickList.sort(this.sortItems); + const currItem = Pickit.pickList[0]; + + if (Pickit.ignoreList.has(currItem.gid)) { + Pickit.pickList.shift(); + + continue; + } + + // get the real item + const _item = Game.getItem(currItem.classid, -1, currItem.gid); + if (!_item || copyUnit(_item).x === undefined) { + Pickit.pickList.shift(); + + continue; + } + const itemName = _item.prettyPrint; + + // Check if the item unit is still valid and if it's on ground or being dropped + // Don't pick items behind walls/obstacles when walking + if (_item.onGroundOrDropping + && (Pather.useTeleport() || me.inTown || !checkCollision(me, _item, sdk.collision.BlockWall))) { + // Check if the item should be picked + let status = this.checkItem(_item); + + if (status.result && this.canPick(_item)) { + // Override canFit for scrolls, potions and gold + let canFit = (Storage.Inventory.CanFit(_item) || Pickit.essentials.includes(_item.itemType)); + + // Field id when our used space is above a certain percent or if we are full try to make room with FieldID + if (Config.FieldID.Enabled && (!canFit || Storage.Inventory.UsedSpacePercent() > Config.FieldID.UsedSpace)) { + me.fieldID() && (canFit = (_item.gid !== undefined && Storage.Inventory.CanFit(_item))); + } + + // Try to make room by selling items in town + if (!canFit) { + let usedSpace = Storage.Inventory.UsedSpacePercent(); + // Check if any of the current inventory items can be stashed or need to be identified and eventually sold to make room + if (this.canMakeRoom()) { + console.log("ÿc7Trying to make room for " + Item.color(_item) + _item.name); + + // Go to town and do town chores + if (Town.visitTown()) { + // Recursive check after going to town. We need to remake item list because gids can change. + // Called only if room can be made so it shouldn't error out or block anything. + if (Storage.Inventory.UsedSpacePercent() < usedSpace) { + console.log( + "ÿc7Made room for " + Item.color(_item) + _item.prettyPrint + + " (" + usedSpace + "% -> " + Storage.Inventory.UsedSpacePercent() + "%)" + ); + Pickit.ignoreList.clear(); + return this.pickItems(); + } + } else { + // Town visit failed - abort + console.warn("Failed to visit town. ÿc7Not enough room for " + Item.color(_item) + _item.name); + + return false; + } + } + + // Can't make room - trigger automule + if (copyUnit(_item).x !== undefined) { + Item.logger("No room for", _item); + console.warn("ÿc7Not enough room for " + Item.color(_item) + _item.name); + Pickit.ignoreList.add(_item.gid); + if (canUseMule) { + console.debug("Attempt to trigger automule"); + needMule = true; + } + + break; + } + } + + // Item can fit - pick it up + if (canFit) { + let picked = this.pickItem(_item, status.result, status.line); + if (!picked) { + console.warn("Failed to pick item " + itemName); + + break; + } + } + } + } + Pickit.pickList.shift(); + } + + // Quit current game and transfer the items to mule + if (needMule && canUseMule && AutoMule.getMuleItems().length > 0) { + scriptBroadcast("mule"); + scriptBroadcast("quit"); + + return false; + } + + return true; + }, + + /** + * @param {number} retry + */ + fastPick: function (retry = 3) { + let item; + const _removeList = []; + const itemList = []; + const range = Config.FastPickRange || Config.PickRange; + + for (let gid of this.gidList) { + _removeList.push(gid); + item = Game.getItem(-1, -1, gid); + if (item && item.onGroundOrDropping + && (!Town.ignoreType(item.itemType) || (item.itemType >= sdk.items.type.HealingPotion + && item.itemType <= sdk.items.type.RejuvPotion)) + && item.itemType !== sdk.items.type.Gold + && getDistance(me, item) <= range) { + itemList.push(copyUnit(item)); + } + } + + while (_removeList.length > 0) { + this.gidList.delete(_removeList.shift()); + } + + while (itemList.length > 0) { + itemList.sort(this.sortFastPickItems); + let check = itemList.shift(); + // we were passed the copied unit, lets find the real thing + item = Game.getItem(check.classid, -1, check.gid); + + // Check if the item unit is still valid + if (item && item.x !== undefined) { + let status = this.checkItem(item); + + if (status.result && this.canPick(item) + && (Storage.Inventory.CanFit(item) || Pickit.essentials.includes(item.itemType))) { + this.pickItem(item, status.result, status.line + " / (fastpick)", retry); + } + } + } + + return true; + }, +}; diff --git a/d2bs/kolbot/libs/core/Precast.js b/d2bs/kolbot/libs/core/Precast.js new file mode 100644 index 000000000..be571a2e0 --- /dev/null +++ b/d2bs/kolbot/libs/core/Precast.js @@ -0,0 +1,598 @@ +/** +* @filename Precast.js +* @author noah-, kolton, theBGuy +* @desc handle player prebuff sequence +* +*/ + +const Precast = (function () { + includeIfNotIncluded("core/Skill.js"); + /** + * @constructor + * @param {number} skillId + */ + function PrecastSkill (skillId) { + this.skillId = skillId; + this.state = Skill.getState(skillId); + this.lastCast = 0; + this.duration = 0; + } + PrecastSkill.prototype.canUse = function () { + return Skill.canUse(this.skillId); + }; + PrecastSkill.prototype.remaining = function () { + if (!this.duration) { + this.duration = Skill.getDuration(this.skillId); + } + const pRemaining = 100 * (1 - (getTickCount() - this.lastCast) / this.duration); + return Math.max(0, Math.min(100, pRemaining)); + }; + PrecastSkill.prototype.needSoon = function (percent = 25) { + return this.remaining() < percent; + }; + PrecastSkill.prototype.needToCast = function (force = false, percent = 25) { + if (!this.canUse()) return false; + return force || !me.getState(this.state) || this.needSoon(percent); + }; + PrecastSkill.prototype.update = function () { + this.lastCast = getTickCount(); + }; + + /** + * @constructor + * @augments PrecastSkill + * @param {number} skillId + */ + function PrecastArmorSkill (skillId) { + PrecastSkill.call(this, skillId); + this.max = 0; + } + PrecastArmorSkill.prototype = Object.create(PrecastSkill.prototype); + PrecastArmorSkill.prototype.constructor = PrecastArmorSkill; + + PrecastArmorSkill.prototype.remaining = function () { + return this.max > 0 + ? Math.round(me.getStat(sdk.stats.SkillBoneArmor) * 100 / this.max) + : 0; + }; + PrecastArmorSkill.prototype.update = function () { + this.lastCast = getTickCount(); + this.max = me.getStat(sdk.stats.SkillBoneArmorMax); + }; + return { + enabled: true, + /** @type {number} */ + coldArmor: null, + shieldGid: 0, + haveCTA: -1, + bestSlot: {}, + + // TODO: build better method of keeping track of duration based skills so we can reduce resource usage + // build obj -> figure out which skills we have -> calc duration -> assign tick of last casted -> track tick (background worker maybe?) + // would reduce checking have skill and state calls, just let tick = getTickCount(); -> obj.some((el) => tick - el.lastTick > el.duration) -> true then cast + // would probably make sense to just re-cast everything (except summons) if one of our skills is about to run out rather than do this process again 3 seconds later + skills: new Map([ + [sdk.skills.FrozenArmor, new PrecastSkill(sdk.skills.FrozenArmor)], + [sdk.skills.ShiverArmor, new PrecastSkill(sdk.skills.ShiverArmor)], + [sdk.skills.ChillingArmor, new PrecastSkill(sdk.skills.ChillingArmor)], + [sdk.skills.Enchant, new PrecastSkill(sdk.skills.Enchant)], + [sdk.skills.ThunderStorm, new PrecastSkill(sdk.skills.ThunderStorm)], + [sdk.skills.EnergyShield, new PrecastSkill(sdk.skills.EnergyShield)], + [sdk.skills.HolyShield, new PrecastSkill(sdk.skills.HolyShield)], + [sdk.skills.Shout, new PrecastSkill(sdk.skills.Shout)], + [sdk.skills.BattleOrders, new PrecastSkill(sdk.skills.BattleOrders)], + [sdk.skills.BattleCommand, new PrecastSkill(sdk.skills.BattleCommand)], + [sdk.skills.Hurricane, new PrecastSkill(sdk.skills.Hurricane)], + [sdk.skills.Armageddon, new PrecastSkill(sdk.skills.Armageddon)], + [sdk.skills.Fade, new PrecastSkill(sdk.skills.Fade)], + [sdk.skills.BurstofSpeed, new PrecastSkill(sdk.skills.BurstofSpeed)], + [sdk.skills.BladeShield, new PrecastSkill(sdk.skills.BladeShield)], + [sdk.skills.Venom, new PrecastSkill(sdk.skills.Venom)], + [sdk.skills.BoneArmor, new PrecastArmorSkill(sdk.skills.BoneArmor)], + [sdk.skills.CycloneArmor, new PrecastArmorSkill(sdk.skills.CycloneArmor)], + ]), + nonPacketSkills: new Set([ + sdk.skills.Valkyrie, sdk.skills.Decoy, sdk.skills.RaiseSkeleton, + sdk.skills.ClayGolem, sdk.skills.RaiseSkeletalMage, sdk.skills.BloodGolem, + sdk.skills.Shout, sdk.skills.IronGolem, sdk.skills.Revive, + sdk.skills.Werewolf, sdk.skills.Werebear, sdk.skills.OakSage, + sdk.skills.SpiritWolf, sdk.skills.PoisonCreeper, sdk.skills.BattleOrders, + sdk.skills.SummonDireWolf, sdk.skills.Grizzly, sdk.skills.HeartofWolverine, + sdk.skills.SpiritofBarbs, sdk.skills.ShadowMaster, + sdk.skills.ShadowWarrior, sdk.skills.BattleCommand, + ]), + + checkCTA: function () { + if (this.haveCTA > -1) return true; + + let check = me.checkItem({ name: sdk.locale.items.CalltoArms, equipped: true }); + + if (check.have) { + Precast.haveCTA = check.item.isOnSwap ? 1 : 0; + } + + return this.haveCTA > -1; + }, + + /** + * @param {boolean} force + * @returns {boolean} + */ + precastCTA: function (force = false) { + if (!Config.UseCta || this.haveCTA === -1 || me.classic || me.barbarian || me.inTown || me.shapeshifted) { + return false; + } + if (!force && me.getState(sdk.states.BattleOrders)) return true; + + if (this.haveCTA > -1) { + const slot = me.weaponswitch; + const { x, y } = me; + + me.switchWeapons(this.haveCTA); + this.cast(sdk.skills.BattleCommand, x, y, false); + this.cast(sdk.skills.BattleCommand, x, y, false); + this.cast(sdk.skills.BattleOrders, x, y, false); + + // does this need to be re-calculated everytime? if no autobuild should really just be done when we initialize + if (!Precast.skills.get(sdk.skills.BattleOrders).duration) { + this.skills.get(sdk.skills.BattleOrders).duration = Skill.getDuration(sdk.skills.BattleOrders); + } + + me.switchWeapons(slot); + + return true; + } + + return false; + }, + + /** + * Check which slot (primary or secondary) gives us the most skillpoints in a skill + * @param {number} skillId + * @returns {0 | 1} best slot to give us the most skillpoints in a skill + * @todo Move this to be part of the SkillData class + */ + getBetterSlot: function (skillId) { + if (this.bestSlot[skillId] !== undefined) return this.bestSlot[skillId]; + + const [classid, skillTab] = [ + Skill.getCharClass(skillId), Skill.getSkillTab(skillId) + ]; + + if (classid < 0 || classid === 255) return me.weaponswitch; + + me.weaponswitch !== 0 && me.switchWeapons(0); + + let [sumCurr, sumSwap] = [0, 0]; + const sumStats = function (item) { + return (item.getStat(sdk.stats.AllSkills) + + item.getStat(sdk.stats.AddClassSkills, classid) + item.getStat(sdk.stats.AddSkillTab, skillTab) + + item.getStat(sdk.stats.SingleSkill, skillId) + item.getStat(sdk.stats.NonClassSkill, skillId)); + }; + + me.getItemsEx() + .filter(item => item.isEquipped && [ + sdk.body.RightArm, sdk.body.LeftArm, sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary + ].includes(item.bodylocation)) + .forEach(function (item) { + if (item.isOnMain) { + sumCurr += sumStats(item); + return; + } + + if (item.isOnSwap) { + sumSwap += sumStats(item); + return; + } + }); + this.bestSlot[skillId] = (sumSwap > sumCurr) ? me.weaponswitch ^ 1 : me.weaponswitch; + return this.bestSlot[skillId]; + }, + + cast: function (skillId, x = me.x, y = me.y, allowSwitch = true) { + if (!skillId || !Skill.wereFormCheck(skillId) || (me.inTown && !Skill.townSkill(skillId))) { + return false; + } + if (Skill.getManaCost(skillId) > me.mp) return false; + + const swap = me.weaponswitch; + // don't use packet casting with summons - or boing + const usePacket = !Precast.nonPacketSkills.has(skillId); + const state = Precast.skills.has(skillId) + ? Precast.skills.get(skillId).state + : 0; + (typeof x !== "number" || typeof y !== "number") && ({ x, y } = me); + + try { + allowSwitch && me.switchWeapons(this.getBetterSlot(skillId)); + if (me.getSkill(sdk.skills.get.RightId) !== skillId + && !me.setSkill(skillId, sdk.skills.hand.Right)) { + throw new Error( + "Failed to set " + getSkillById(skillId) + " on hand." + + "Current: " + getSkillById(me.getSkill(sdk.skills.get.RightId))); + } + + if (Config.PacketCasting > 1 || usePacket) { + Config.DebugMode.Skill && console.debug("Packet casting: " + skillId); + + if (typeof x === "number") { + Packet.castSkill(sdk.skills.hand.Right, x, y); + } else if (typeof x === "object") { + Packet.unitCast(sdk.skills.hand.Right, x); + } + delay(250); + } else { + // Right hand + No Shift + const clickType = sdk.clicktypes.click.map.RightDown; + const shift = sdk.clicktypes.shift.NoShift; + + for (let n = 0; n < 3; n += 1) { + typeof x === "object" + ? clickMap(clickType, shift, x) + : clickMap(clickType, shift, x, y); + delay(20); + typeof x === "object" + ? clickMap(clickType + 2, shift, x) + : clickMap(clickType + 2, shift, x, y); + + if (Misc.poll(function () { + return me.attacking; + }, 200, 20)) { + break; + } + } + + while (me.attacking) { + delay(10); + } + } + + // account for lag, state 121 doesn't kick in immediately + if (Skill.isTimed(skillId)) { + Misc.poll(function () { + return ( + me.skillDelay + || me.mode === sdk.player.mode.GettingHit + || me.mode === sdk.player.mode.Blocking + ); + }, 100, 10); + } + if (Precast.skills.has(skillId)) { + Precast.skills.get(skillId).update(); + } + return state ? me.getState(state) : true; + } catch (e) { + console.error(e); + + return false; + } finally { + allowSwitch && me.switchWeapons(swap); + } + }, + + summon: function (skillId, minionType) { + if (!Skill.canUse(skillId)) return false; + + let rv, retry = 0; + let count = Skill.getMaxSummonCount(skillId); + + while (me.getMinionCount(minionType) < count) { + rv = true; + + if (retry > count * 2) { + if (me.inTown) { + Town.heal() && me.cancelUIFlags(); + Town.move("portalspot"); + Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y); + } else { + let coord = CollMap.getRandCoordinate(me.x, -6, 6, me.y, -6, 6); + + // Keep bots from getting stuck trying to summon + if (!!coord && Attack.validSpot(coord.x, coord.y)) { + Pather.moveTo(coord.x, coord.y); + Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y); + } + } + + if (me.getMinionCount(minionType) === count) { + return true; + } else { + console.warn("Failed to summon minion " + skillId); + + return false; + } + } + + // todo - only delay if we are close to the mana amount we need based on our mana regen rate or potion state + // also take into account surrounding mobs so we don't delay for mana in the middle of a mob pack + if (Skill.getManaCost(skillId) > me.mp) { + if (!Misc.poll(() => me.mp >= Skill.getManaCost(skillId), 500, 100)) { + retry++; + continue; + } + } + + let coord = CollMap.getRandCoordinate(me.x, -4, 4, me.y, -4, 4); + + if (!!coord && Attack.validSpot(coord.x, coord.y)) { + Skill.cast(skillId, sdk.skills.hand.Right, coord.x, coord.y); + + if (me.getMinionCount(minionType) === count) { + break; + } else { + retry++; + } + } + + delay(200); + } + + return !!rv; + }, + + enchant: (function () { + let chantDuration = 0; + + /** @constructor */ + function ChantTracker () { + this.lastChant = getTickCount(); + } + + ChantTracker.prototype.reChant = function () { + return getTickCount() - this.lastChant >= chantDuration - Time.seconds(10); + }; + + ChantTracker.prototype.update = function () { + this.lastChant = getTickCount(); + }; + + /** @type {Map 40) continue; + if (!unit.getState(sdk.states.Enchant) + || (chantList.has(unit.name) && chantList.get(unit.name).reChant())) { + Skill.cast(sdk.skills.Enchant, sdk.skills.hand.Right, unit); + if (Misc.poll(() => unit.getState(sdk.states.Enchant), 500, 100)) { + chanted.push(unit.name); + chantList.has(unit.name) + ? chantList.get(unit.name).update() + : chantList.set(unit.name, new ChantTracker()); + } + } + } while (unit.getNext()); + } + + // Minion + unit = Game.getMonster(); + + if (unit) { + do { + if (unit.getParent() + && chanted.includes(unit.getParent().name) + && unit.distance <= 40 + && !unit.getState(sdk.states.Enchant)) { + Skill.cast(sdk.skills.Enchant, sdk.skills.hand.Right, unit); + } + } while (unit.getNext()); + } + + me.switchWeapons(slot); + + return true; + }; + })(), + + // should the config check still be included even though its part of Skill.init? + /** + * @description Handle precast related skills + * @param {boolean} force - force re-cast of all precast skills + * @param {boolean} partial - force re-cast of all state related precast skills + * @returns {boolean} sucessfully casted + * @todo durations + */ + doPrecast: function (force = false, partial = false) { + if (!this.enabled) return false; + + while (!me.gameReady) { + delay(40); + } + + let [buffSummons, forceBo] = [false, false]; + + // Force BO 30 seconds before it expires + if (Precast.haveCTA > -1) { + forceBo = (force || partial + || Precast.skills.get(sdk.skills.BattleOrders).remaining() < 25 + || !me.getState(sdk.states.BattleCommand)); + forceBo && this.precastCTA(forceBo); + } + + switch (me.classid) { + case sdk.player.class.Amazon: + if (Skill.canUse(sdk.skills.Valkyrie)) { + buffSummons = Precast.summon(sdk.skills.Valkyrie, sdk.summons.type.Valkyrie); + } + break; + case sdk.player.class.Sorceress: + if (Precast.skills.get(sdk.skills.ThunderStorm).needToCast(force || partial)) { + this.cast(sdk.skills.ThunderStorm); + } + + if (Precast.skills.get(sdk.skills.EnergyShield).needToCast(force || partial)) { + this.cast(sdk.skills.EnergyShield); + } + + if (Config.UseColdArmor) { + let choosenSkill = (typeof Config.UseColdArmor === "number" && Skill.canUse(Config.UseColdArmor) + ? Config.UseColdArmor + : (Precast.coldArmor || -1)); + + if (choosenSkill && Precast.skills.has(choosenSkill)) { + if (Precast.skills.get(choosenSkill).needToCast(force || partial)) { + Precast.cast(choosenSkill); + } + } + } + + if (Precast.skills.get(sdk.skills.Enchant).needToCast(force || partial)) { + this.enchant(); + } + + break; + case sdk.player.class.Necromancer: + if (Precast.skills.get(sdk.skills.BoneArmor).needToCast(force, 75)) { + this.cast(sdk.skills.BoneArmor); + if (Precast.skills.get(sdk.skills.BoneArmor).max === 0) { + Precast.skills.get(sdk.skills.BoneArmor).max = me.getStat(sdk.stats.SkillBoneArmorMax); + } + } + + if (!!Config.Golem && Config.Golem !== "None") { + Precast.summon(Config.Golem, sdk.summons.type.Golem); + } + + Config.ActiveSummon && ClassAttack.raiseArmy(); + + break; + case sdk.player.class.Paladin: + if (Precast.skills.get(sdk.skills.HolyShield).needToCast(force || partial, 15)) { + let _wearingShield = me.getItem(-1, sdk.items.mode.Equipped, Precast.shieldGid); + if (!_wearingShield) { + // try once to locate, in case we just swapped + _wearingShield = me.usingShield(); + Precast.shieldGid = _wearingShield ? _wearingShield.gid : 0; + if (!_wearingShield) { + break; + } + } + if (Precast.shieldGid > 0) { + Precast.cast(sdk.skills.HolyShield); + } + } + + break; + case sdk.player.class.Barbarian: // - TODO: durations + if (!Config.UseWarcries) { + break; + } + let needShout = (Precast.skills.get(sdk.skills.Shout).needToCast(force || partial)); + let needBo = (Precast.skills.get(sdk.skills.BattleOrders).needToCast(force || partial)); + let needBc = (Precast.skills.get(sdk.skills.BattleCommand).needToCast(force || partial)); + + if (needShout || needBo || needBc) { + let primary = Attack.getPrimarySlot(); + let { x, y } = me; + (needBo || needBc) && me.switchWeapons(this.getBetterSlot(sdk.skills.BattleOrders)); + + needBc && this.cast(sdk.skills.BattleCommand, x, y, false); + needBo && this.cast(sdk.skills.BattleOrders, x, y, false); + needShout && this.cast(sdk.skills.Shout, x, y, false); + + me.weaponswitch !== primary && me.switchWeapons(primary); + } + + break; + case sdk.player.class.Druid: + if (Precast.skills.get(sdk.skills.CycloneArmor).needToCast(force || partial)) { + this.cast(sdk.skills.CycloneArmor); + } + + Skill.canUse(sdk.skills.Raven) && Precast.summon(sdk.skills.Raven, sdk.summons.type.Raven); + + if (!!Config.SummonAnimal && Config.SummonAnimal !== "None") { + buffSummons = Precast.summon(Config.SummonAnimal, Skill.getSummonType(Config.SummonAnimal)); + } + + if (!!Config.SummonVine && Config.SummonVine !== "None") { + buffSummons = Precast.summon(Config.SummonVine, sdk.summons.type.Vine); + } + + if (!!Config.SummonSpirit && Config.SummonSpirit !== "None") { + buffSummons = ( + Config.SummonSpirit === sdk.skills.OakSage + && me.hardcore + && !me.getState(sdk.states.BattleOrders) + && me.inTown + ) + ? buffSummons + : Precast.summon(Config.SummonSpirit, sdk.summons.type.Spirit); + } + + if (Precast.skills.get(sdk.skills.Hurricane).needToCast(force || partial)) { + this.cast(sdk.skills.Hurricane); + } + + break; + case sdk.player.class.Assassin: + if (Precast.skills.get(sdk.skills.Fade).needToCast(force || partial)) { + this.cast(sdk.skills.Fade); + } + + if (Precast.skills.get(sdk.skills.Venom).needToCast(force || partial)) { + this.cast(sdk.skills.Venom); + } + + if (Precast.skills.get(sdk.skills.BladeShield).needToCast(force || partial)) { + this.cast(sdk.skills.BladeShield); + } + + if (!Config.UseFade && Precast.skills.get(sdk.skills.BurstofSpeed).needToCast(force || partial)) { + this.cast(sdk.skills.BurstofSpeed); + } + + if (!!Config.SummonShadow && !!Config.SummonShadow !== "None") { + buffSummons = Precast.summon(Config.SummonShadow, sdk.summons.type.Shadow); + } + + break; + } + + buffSummons && this.haveCTA > -1 && this.precastCTA(force); + me.switchWeapons(Attack.getPrimarySlot()); + + return true; + }, + + needOutOfTownCast: function () { + return Skill.canUse(sdk.skills.Shout) || Skill.canUse(sdk.skills.BattleOrders) || Precast.checkCTA(); + }, + + doRandomPrecast: function (force = false, goToWhenDone = undefined) { + const returnTo = (goToWhenDone && typeof goToWhenDone === "number" + ? goToWhenDone + : me.area); + + try { + // Only do this is you are a barb or actually have a cta. Otherwise its just a waste of time and you can precast in town + if (Precast.needOutOfTownCast()) { + Pather.useWaypoint("random") && Precast.doPrecast(force); + } else { + Precast.doPrecast(force); + } + Pather.useWaypoint(returnTo); + } catch (e) { + console.error(e); + } finally { + if (me.area !== returnTo && (!Pather.useWaypoint(returnTo) || !Pather.useWaypoint(sdk.areas.townOf(me.area)))) { + Pather.journeyTo(returnTo); + } + } + + return (me.area === returnTo); + }, + }; +})(); diff --git a/d2bs/kolbot/libs/core/Prototypes.js b/d2bs/kolbot/libs/core/Prototypes.js new file mode 100644 index 000000000..8ce168b2d --- /dev/null +++ b/d2bs/kolbot/libs/core/Prototypes.js @@ -0,0 +1,2551 @@ +/** +* @filename Prototypes.js +* @author kolton, theBGuy +* @credit Jaenster +* @desc various 'Unit' prototypes +* +*/ + +// Ensure these are in polyfill.js +!isIncluded("Polyfill.js") && include("Polyfill.js"); +!isIncluded("core/Me.js") && include("core/Me.js"); + +(function (global, original) { + let firstRun = true; + global.getUnit = function (...args) { + if (firstRun) { + delay(1500); + firstRun = false; + } + + // Stupid reference thing + // eslint-disable-next-line no-unused-vars + const test = original(-1); + + let [first] = args, second = args.length >= 2 ? args[1] : undefined; + + const ret = original.apply(this, args); + + // deal with bug + if (first === 1 && typeof second === "string" && ret + && ((me.act === 1 && ret.classid === sdk.monsters.Dummy1) + || me.act === 2 && ret.classid === sdk.monsters.Dummy2)) { + return null; + } + + return original.apply(this, args); + }; +})([].filter.constructor("return this")(), getUnit); + +// Check if party unit is in town +Party.prototype.__defineGetter__("inTown", function () { + return sdk.areas.Towns.includes(this.area); +}); + +Object.defineProperties(Unit.prototype, { + isChampion: { + get: function () { + return (this.spectype & sdk.monsters.spectype.Champion) > 0; + }, + }, + isUnique: { + get: function () { + return (this.spectype & sdk.monsters.spectype.Unique) > 0; + }, + }, + isMinion: { + get: function () { + return (this.spectype & sdk.monsters.spectype.Minion) > 0; + }, + }, + isSuperUnique: { + get: function () { + return (this.spectype & (sdk.monsters.spectype.Super | sdk.monsters.spectype.Unique)) > 0; + }, + }, + isSpecial: { + get: function () { + return (this.isChampion || this.isUnique || this.isSuperUnique); + }, + }, + isPlayer: { + get: function () { + return this.type === sdk.unittype.Player; + }, + }, + isMonster: { + get: function () { + return this.type === sdk.unittype.Monster; + }, + }, + isNPC: { + get: function () { + return this.type === sdk.unittype.Monster && this.getStat(sdk.stats.Alignment) === 2; + }, + }, + // todo - monster types + isPrimeEvil: { + get: function () { + return [ + sdk.monsters.Andariel, sdk.monsters.Duriel, sdk.monsters.Mephisto, + sdk.monsters.Diablo, sdk.monsters.Baal, sdk.monsters.BaalClone, + sdk.monsters.UberDuriel, sdk.monsters.UberIzual, sdk.monsters.UberMephisto, + sdk.monsters.UberDiablo, sdk.monsters.UberBaal, sdk.monsters.Lilith, sdk.monsters.DiabloClone + ].includes(this.classid) || getBaseStat("monstats", this.classid, "primeevil"); + }, + }, + isBoss: { + get: function () { + return this.isPrimeEvil + || [ + sdk.monsters.TheSmith, sdk.monsters.BloodRaven, sdk.monsters.Radament, sdk.monsters.Griswold, + sdk.monsters.TheSummoner, sdk.monsters.Izual, sdk.monsters.Hephasto, sdk.monsters.KorlictheProtector, + sdk.monsters.TalictheDefender, sdk.monsters.MadawctheGuardian, sdk.monsters.ListerTheTormenter, + sdk.monsters.TheCowKing, sdk.monsters.ColdwormtheBurrower, sdk.monsters.Nihlathak + ].includes(this.classid); + }, + }, + isGhost: { + get: function () { + return [ + sdk.monsters.Ghost1, sdk.monsters.Wraith1, sdk.monsters.Specter1, + sdk.monsters.Apparition, sdk.monsters.DarkShape, sdk.monsters.Ghost2, + sdk.monsters.Wraith2, sdk.monsters.Specter2 + ].includes(this.classid) || getBaseStat("monstats", this.classid, "MonType") === sdk.monsters.type.Wraith; + }, + }, + isDoll: { + get: function () { + return [ + sdk.monsters.BoneFetish1, sdk.monsters.BoneFetish2, sdk.monsters.BoneFetish3, + sdk.monsters.SoulKiller3, sdk.monsters.StygianDoll2, sdk.monsters.StygianDoll6, sdk.monsters.SoulKiller + ].includes(this.classid); + }, + }, + isMonsterObject: { + get: function () { + return [ + sdk.monsters.Turret1, sdk.monsters.Turret2, sdk.monsters.Turret3, sdk.monsters.MummyGenerator, + sdk.monsters.GargoyleTrap, sdk.monsters.LightningSpire, sdk.monsters.FireTower, + sdk.monsters.BarricadeDoor1, sdk.monsters.BarricadeDoor2, + sdk.monsters.BarricadeWall1, sdk.monsters.BarricadeWall2, + sdk.monsters.CatapultS, sdk.monsters.CatapultE, sdk.monsters.CatapultSiege, sdk.monsters.CatapultW, + sdk.monsters.BarricadeTower, sdk.monsters.PrisonDoor, sdk.monsters.DiablosBoneCage, sdk.monsters.Hut, + ].includes(this.classid); + }, + }, + isMonsterEgg: { + get: function () { + return [ + sdk.monsters.SandMaggotEgg, sdk.monsters.RockWormEgg, sdk.monsters.DevourerEgg, sdk.monsters.GiantLampreyEgg, + sdk.monsters.WorldKillerEgg1, sdk.monsters.WorldKillerEgg2 + ].includes(this.classid); + }, + }, + isMonsterNest: { + get: function () { + return [ + sdk.monsters.FoulCrowNest, sdk.monsters.BlackVultureNest, + sdk.monsters.BloodHawkNest, sdk.monsters.BloodHookNest, + sdk.monsters.BloodWingNest, sdk.monsters.CloudStalkerNest, + sdk.monsters.FeederNest, sdk.monsters.SuckerNest + ].includes(this.classid); + }, + }, + isBaalTentacle: { + get: function () { + return [ + sdk.monsters.Tentacle1, sdk.monsters.Tentacle2, + sdk.monsters.Tentacle3, sdk.monsters.Tentacle4, sdk.monsters.Tentacle5 + ].includes(this.classid); + }, + }, + isShaman: { + get: function () { + return [ + sdk.monsters.FallenShaman, sdk.monsters.CarverShaman2, sdk.monsters.DevilkinShaman2, sdk.monsters.DarkShaman1, + sdk.monsters.WarpedShaman, sdk.monsters.CarverShaman, sdk.monsters.DevilkinShaman, sdk.monsters.DarkShaman2 + ].includes(this.classid); + }, + }, + isUnraveler: { + get: function () { + return getBaseStat("monstats", this.classid, "MonType") === sdk.monsters.type.Unraveler; + }, + }, + isFallen: { + get: function () { + return [ + sdk.monsters.Fallen, sdk.monsters.Carver2, sdk.monsters.Devilkin2, + sdk.monsters.DarkOne1, sdk.monsters.WarpedFallen, + sdk.monsters.Carver1, sdk.monsters.Devilkin, sdk.monsters.DarkOne2 + ].includes(this.classid); + }, + }, + isBeetle: { + get: function () { + return getBaseStat("monstats", this.classid, "MonType") === sdk.monsters.type.Scarab; + }, + }, + isWalking: { + get: function () { + return (this.mode === sdk.monsters.mode.Walking && (this.targetx !== this.x || this.targety !== this.y)); + } + }, + isRunning: { + get: function () { + return (this.mode === sdk.monsters.mode.Running && (this.targetx !== this.x || this.targety !== this.y)); + } + }, + isMoving: { + get: function () { + return (this.isWalking || this.isRunning); + }, + }, + isFrozen: { + get: function () { + return this.getState(sdk.states.FrozenSolid); + }, + }, + isChilled: { + get: function () { + return this.getState(sdk.states.Frozen); + }, + }, + extraStrong: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ExtraStrong); + }, + }, + extraFast: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ExtraFast); + }, + }, + cursed: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.Cursed); + }, + }, + magicResistant: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.MagicResistant); + }, + }, + fireEnchanted: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.FireEnchanted); + }, + }, + lightningEnchanted: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.LightningEnchanted); + }, + }, + coldEnchanted: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ColdEnchanted); + }, + }, + manBurn: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ManaBurn); + }, + }, + teleportation: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.Teleportation); + }, + }, + spectralHit: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.SpectralHit); + }, + }, + stoneSkin: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.StoneSkin); + }, + }, + multiShot: { + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.MultipleShots); + }, + }, + resPenalty: { + value: me.classic ? [0, 20, 50][me.diff] : [0, 40, 100][me.diff], + writable: true + }, + fireRes: { + get: function () { + let modifier = 0; + if (this === me) { + me.getState(sdk.states.ShrineResFire) && (modifier += 75); + } + return this.getStat(sdk.stats.FireResist) - me.resPenalty - modifier; + } + }, + coldRes: { + get: function () { + let modifier = 0; + if (this === me) { + me.getState(sdk.states.ShrineResCold) && (modifier += 75); + me.getState(sdk.states.Thawing) && (modifier += 50); + } + return this.getStat(sdk.stats.ColdResist) - me.resPenalty - modifier; + } + }, + lightRes: { + get: function () { + let modifier = 0; + if (this === me) { + me.getState(sdk.states.ShrineResLighting) && (modifier += 75); + } + return this.getStat(sdk.stats.LightResist) - me.resPenalty - modifier; + } + }, + poisonRes: { + get: function () { + let modifier = 0; + if (this === me) { + me.getState(sdk.states.ShrineResPoison) && (modifier += 75); + me.getState(sdk.states.Antidote) && (modifier += 50); + } + return this.getStat(sdk.stats.PoisonResist) - me.resPenalty - modifier; + } + }, + hpPercent: { + get: function () { + return Math.round(this.hp * 100 / this.hpmax); + } + }, + attacking: { + get: function () { + if (this.type > sdk.unittype.Monster) { + throw new Error("Unit.attacking: Must be used with Monster or Player units."); + } + switch (this.type) { + case sdk.unittype.Player: + return [ + sdk.player.mode.Attacking1, sdk.player.mode.Attacking2, + sdk.player.mode.CastingSkill, sdk.player.mode.ThrowingItem, + sdk.player.mode.Kicking, sdk.player.mode.UsingSkill1, + sdk.player.mode.UsingSkill2, sdk.player.mode.UsingSkill3, + sdk.player.mode.UsingSkill4, sdk.player.mode.SkillActionSequence + ].includes(this.mode); + case sdk.unittype.Monster: + return [ + sdk.monsters.mode.Attacking1, sdk.monsters.mode.Attacking2, + sdk.monsters.mode.CastingSkill, sdk.monsters.mode.UsingSkill1, + sdk.monsters.mode.UsingSkill2, sdk.monsters.mode.UsingSkill3, sdk.monsters.mode.UsingSkill4 + ].includes(this.mode); + default: + return false; + } + } + }, + idle: { + get: function () { + if (this.type > sdk.unittype.Player) throw new Error("Unit.idle: Must be used with player units."); + // Dead is pretty idle too + return (this.mode === sdk.player.mode.StandingOutsideTown + || this.mode === sdk.player.mode.StandingInTown || this.mode === sdk.player.mode.Dead); + } + }, + gold: { + /** @this {Unit} */ + get: function () { + if (this.type === sdk.unittype.Item) { + return this.getStat(sdk.stats.Gold); + } + return this.getStat(sdk.stats.Gold) + this.getStat(sdk.stats.GoldBank); + } + }, + dead: { + get: function () { + switch (this.type) { + case sdk.unittype.Player: + return this.mode === sdk.player.mode.Death || this.mode === sdk.player.mode.Dead; + case sdk.unittype.Monster: + return this.mode === sdk.monsters.mode.Death || this.mode === sdk.monsters.mode.Dead; + default: + return false; + } + } + }, + inTown: { + get: function () { + if (this.type > sdk.unittype.Player) throw new Error("Unit.inTown: Must be used with player units."); + return sdk.areas.Towns.includes(this.area); + } + } +}); + +/** + * @extends ItemUnit + */ +Object.defineProperties(Unit.prototype, { + strreq: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + let ethereal = this.getFlag(sdk.items.flags.Ethereal); + let reqModifier = this.getStat(sdk.stats.ReqPercent); + let baseReq = getBaseStat("items", this.classid, "reqstr"); + let finalReq = baseReq + Math.floor(baseReq * reqModifier / 100) - (ethereal ? 10 : 0); + + return Math.max(finalReq, 0); + } + }, + dexreq: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + let ethereal = this.getFlag(sdk.items.flags.Ethereal); + let reqModifier = this.getStat(sdk.stats.ReqPercent); + let baseReq = getBaseStat("items", this.classid, "reqdex"); + let finalReq = baseReq + Math.floor(baseReq * reqModifier / 100) - (ethereal ? 10 : 0); + + return Math.max(finalReq, 0); + } + }, + parentName: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + let parent = this.getParent(); + + return parent ? parent.name : false; + } + }, + itemclass: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + const itemCode = getBaseStat("items", this.classid, "code"); + if (itemCode === undefined) return 0; + if (itemCode === getBaseStat(0, this.classid, "ultracode")) return 2; + if (itemCode === getBaseStat(0, this.classid, "ubercode")) return 1; + + return 0; + } + }, + charclass: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + let charclass = getBaseStat("itemtypes", this.itemType, "class"); + // hacky? Essentially just using this to check if we can use the item and if the item doesn't have a specific + // class requirement, we'll just assume it's for our class. As this makes the actualy checks easy + return charclass === 255 ? me.classid : charclass; + } + }, + isEquipped: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.location === sdk.storage.Equipped; + } + }, + isEquippedCharm: { + // todo - fix this for storage checks + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return (this.location === sdk.storage.Inventory + && [sdk.items.type.SmallCharm, sdk.items.type.LargeCharm, sdk.items.type.GrandCharm].includes(this.itemType)); + } + }, + isInInventory: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.location === sdk.storage.Inventory && this.mode === sdk.items.mode.inStorage; + } + }, + isInStash: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.location === sdk.storage.Stash && this.mode === sdk.items.mode.inStorage; + } + }, + isInCube: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.location === sdk.storage.Cube && this.mode === sdk.items.mode.inStorage; + } + }, + isInStorage: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.mode === sdk.items.mode.inStorage + && [sdk.storage.Inventory, sdk.storage.Cube, sdk.storage.Stash].includes(this.location); + } + }, + isInBelt: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.location === sdk.storage.Belt && this.mode === sdk.items.mode.inBelt; + } + }, + isOnMain: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item || this.location !== sdk.storage.Equipped) return false; + switch (me.weaponswitch) { + case sdk.player.slot.Secondary: + return [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary].includes(this.bodylocation); + case sdk.player.slot.Main: + default: + return [sdk.body.RightArm, sdk.body.LeftArm].includes(this.bodylocation); + } + } + }, + isOnSwap: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item || this.location !== sdk.storage.Equipped) return false; + switch (me.weaponswitch) { + case sdk.player.slot.Main: + return [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary].includes(this.bodylocation); + case sdk.player.slot.Secondary: + default: + return [sdk.body.RightArm, sdk.body.LeftArm].includes(this.bodylocation); + } + } + }, + identified: { + /** @this {ItemUnit} */ + get: function () { + // Can't tell, as it isn't an item + if (this.type !== sdk.unittype.Item) return undefined; + // Is also true for white items + return this.getFlag(sdk.items.flags.Identified); + } + }, + ethereal: { + /** @this {ItemUnit} */ + get: function () { + // Can't tell, as it isn't an item + if (this.type !== sdk.unittype.Item) return undefined; + return this.getFlag(sdk.items.flags.Ethereal); + } + }, + twoHanded: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return getBaseStat("items", this.classid, "2handed") === 1; + } + }, + oneOrTwoHanded: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return getBaseStat("items", this.classid, "1or2handed") === 1; + } + }, + strictlyTwoHanded: { + /** @this {ItemUnit} */ + get: function () { + return this.twoHanded && !this.oneOrTwoHanded; + } + }, + runeword: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return !!this.getFlag(sdk.items.flags.Runeword); + } + }, + questItem: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return (this.itemType === sdk.items.type.Quest + || [ + sdk.items.quest.HoradricMalus, sdk.items.quest.WirtsLeg, + sdk.items.quest.HoradricStaff, sdk.items.quest.ShaftoftheHoradricStaff, + sdk.items.quest.ViperAmulet, sdk.items.quest.DecoyGidbinn, + sdk.items.quest.TheGidbinn, sdk.items.quest.KhalimsFlail, + sdk.items.quest.KhalimsWill, sdk.items.quest.HellForgeHammer, sdk.items.quest.StandardofHeroes + ].includes(this.classid)); + } + }, + sellable: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + if (this.getItemCost(sdk.items.cost.ToSell) <= 1) return false; + return (!this.questItem + && [ + sdk.items.quest.KeyofTerror, sdk.items.quest.KeyofHate, + sdk.items.quest.KeyofDestruction, sdk.items.quest.DiablosHorn, + sdk.items.quest.BaalsEye, sdk.items.quest.MephistosBrain, + sdk.items.quest.TokenofAbsolution, sdk.items.quest.TwistedEssenceofSuffering, + sdk.items.quest.ChargedEssenceofHatred, sdk.items.quest.BurningEssenceofTerror, + sdk.items.quest.FesteringEssenceofDestruction + ].indexOf(this.classid) === -1); + } + }, + lowquality: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.LowQuality; + }, + }, + normal: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.Normal; + }, + }, + superior: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.Superior; + }, + }, + magic: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.Magic; + }, + }, + set: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.Set; + }, + }, + rare: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.Rare; + }, + }, + unique: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.Unique; + }, + }, + crafted: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.quality === sdk.items.quality.Crafted; + }, + }, + sockets: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.getStat(sdk.stats.NumSockets); + }, + }, + onGroundOrDropping: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return (this.mode === sdk.items.mode.onGround || this.mode === sdk.items.mode.Dropping); + }, + }, + isShield: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return [sdk.items.type.Shield, sdk.items.type.AuricShields, sdk.items.type.VoodooHeads].includes(this.itemType); + }, + }, + isCharm: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return [sdk.items.SmallCharm, sdk.items.LargeCharm, sdk.items.GrandCharm].includes(this.classid); + } + }, + isAnni: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.unique && this.itemType === sdk.items.type.SmallCharm; + }, + }, + isTorch: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.unique && this.itemType === sdk.items.type.LargeCharm; + }, + }, + isGheeds: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return false; + return this.unique && this.itemType === sdk.items.type.GrandCharm; + }, + }, + prettyPrint: { + /** @this {ItemUnit} */ + get: function () { + if (this.type !== sdk.unittype.Item) return this.name; + if (this.fname === undefined) return typeof this.name === "string" ? this.name : "undefined"; + return this.fname.split("\n").reverse().join(" "); + } + }, + durabilityPercent: { + get: function () { + if (this.type !== sdk.unittype.Item) throw new Error("Unit.durabilityPercent: Must be used on items."); + if (this.getStat(sdk.stats.Quantity) || !this.getStat(sdk.stats.MaxDurability)) return 100; + return Math.round(this.getStat(sdk.stats.Durability) * 100 / this.getStat(sdk.stats.MaxDurability)); + } + }, +}); + +/** + * Open NPC menu + * @this {NPCUnit} + * @param {number} [addDelay] + * @returns {boolean} + */ +Unit.prototype.openMenu = function (addDelay) { + if (this.type !== sdk.unittype.NPC) throw new Error("Unit.openMenu: Must be used on NPCs."); + if (getUIFlag(sdk.uiflags.NPCMenu)) return true; + + addDelay === undefined && (addDelay = 0); + const pingDelay = (me.gameReady ? me.ping : 125); + + for (let i = 0; i < 5; i += 1) { + if (getDistance(me, this) > 4) { + Pather.moveNearUnit(this, 4); + } + + Config.PacketShopping + ? Packet.entityInteract(this) + : Misc.click(0, 0, this); + let tick = getTickCount(); + + while (getTickCount() - tick < 5000) { + if (getUIFlag(sdk.uiflags.NPCMenu)) { + delay(Math.max(700 + pingDelay, 500 + pingDelay * 2 + addDelay * 500)); + + return true; + } + + if ((getTickCount() - tick > 1000 && getInteractedNPC()) + || (getTickCount() - tick > 500 && getIsTalkingNPC())) { + me.cancel(); + break; + } + + delay(100); + } + + new PacketBuilder() + .byte(sdk.packets.send.NPCInit) + .dword(1) + .dword(this.gid) + .send(); + delay(pingDelay * 2 + 1); + Packet.cancelNPC(this); + delay(pingDelay * 2 + 1); + Packet.flash(me.gid); + } + + return false; +}; + +/** + * @this {NPCUnit} + * @param {string} mode "Gamble", "Repair" or "Shop" + * @returns {boolean} + */ +Unit.prototype.startTrade = function (mode) { + if (Config.PacketShopping) return Packet.startTrade(this, mode); + if (this.type !== sdk.unittype.NPC) throw new Error("Unit.startTrade: Must be used on NPCs."); + console.log("Starting " + mode + " at " + this.name); + if (getUIFlag(sdk.uiflags.Shop)) return true; + + const menuId = mode === "Gamble" + ? sdk.menu.Gamble + : mode === "Repair" + ? sdk.menu.TradeRepair + : sdk.menu.Trade; + + for (let i = 0; i < 3; i += 1) { + // Incremental delay on retries + if (this.openMenu(i)) { + Misc.useMenu(menuId); + + let tick = getTickCount(); + + while (getTickCount() - tick < 1000) { + if (getUIFlag(sdk.uiflags.Shop) && this.itemcount > 0) { + delay(200); + console.log("Successfully started " + mode + " at " + this.name); + + return true; + } + + delay(25); + } + + me.cancel(); + } + } + + return false; +}; + +/** + * optionally repair all or a single item + * @todo improve this, at the moment it's here as more of a proof of concept + */ +Unit.prototype.repairItem = function () { + // lets check if we have and can afford to repair this item + if (me.gold < this.getItemCost(2)) return false; + let npc = getInteractedNPC(); + if (!npc || npc.name.toLowerCase() !== Town.tasks.get(me.act).Repair) return false; + // if (!this.startTrade("Repair")) return false; + let preDurability = this.getStat(sdk.stats.Durability); + new PacketBuilder() + .byte(0x35) + .dword(npc.gid) + .dword(this.gid) + .dword(1/* 1 for single item | 0 for all*/) + .dword(0) + .send(); + return Misc.poll(() => this.getStat(sdk.stats.Durability) !== preDurability, 500, 50); +}; + +Unit.prototype.buy = function (shiftBuy, gamble) { + if (Config.PacketShopping) return Packet.buyItem(this, shiftBuy, gamble); + // Check if it's an item we want to buy + if (this.type !== sdk.unittype.Item) throw new Error("Unit.buy: Must be used on items."); + + // Check if it's an item belonging to a NPC + if (!getUIFlag(sdk.uiflags.Shop) || (this.getParent() && this.getParent().gid !== getInteractedNPC().gid)) { + throw new Error("Unit.buy: Must be used in shops."); + } + + // Can we afford the item? + if (me.gold < this.getItemCost(sdk.items.cost.ToBuy)) return false; + + let oldGold = me.gold; + let itemCount = me.itemcount; + + for (let i = 0; i < 3; i += 1) { + this.shop(shiftBuy ? 6 : 2); + + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(2000, me.ping * 2 + 500)) { + if ((shiftBuy && me.gold < oldGold) || itemCount !== me.itemcount) { + delay(500); + + return true; + } + + delay(10); + } + } + + return false; +}; + +// You MUST use a delay after Unit.sell() if using custom scripts. delay(500) works best, dynamic delay is used when identifying/selling (500 - item id time) +Unit.prototype.sell = function () { + if (Config.PacketShopping) return Packet.sellItem(this); + + // Check if it's an item we want to buy + if (this.type !== sdk.unittype.Item) throw new Error("Unit.sell: Must be used on items."); + if (!this.sellable) { + console.error((new Error("Item is unsellable"))); + return false; + } + + // Check if it's an item belonging to a NPC + if (!getUIFlag(sdk.uiflags.Shop)) throw new Error("Unit.sell: Must be used in shops."); + + let itemCount = me.itemcount; + + for (let i = 0; i < 5; i += 1) { + this.shop(1); + + let tick = getTickCount(); + + while (getTickCount() - tick < 2000) { + if (me.itemcount !== itemCount) { + return true; + } + + delay(10); + } + } + + return false; +}; + +/** + * @this ItemUnit + * @param {boolean} usePacket + */ +Unit.prototype.toCursor = function (usePacket = false) { + if (this.type !== sdk.unittype.Item) throw new Error("Unit.toCursor: Must be used with items."); + if (me.itemoncursor && this.mode === sdk.items.mode.onCursor) return true; + + this.location === sdk.storage.Stash && Town.openStash(); + this.location === sdk.storage.Cube && Cubing.openCube(); + + if (usePacket) return Packet.itemToCursor(this); + + for (let i = 0; i < 3; i += 1) { + try { + if (this.mode === sdk.items.mode.Equipped) { + if (this.isOnSwap) { + // fix crash when item is equipped on switch and we try to move it directly to cursor + me.switchWeapons(sdk.player.slot.Secondary); + } + // fix for equipped items (cubing viper staff for example) + clickItem(sdk.clicktypes.click.item.Left, this.bodylocation); + } else { + clickItem(sdk.clicktypes.click.item.Left, this); + } + } catch (e) { + return false; + } + + let tick = getTickCount(); + + while (getTickCount() - tick < 1000) { + if (me.itemoncursor) { + delay(200); + + return true; + } + + delay(10); + } + } + + return false; +}; + +Unit.prototype.drop = function () { + if (this.type !== sdk.unittype.Item) { + throw new Error("Unit.drop: Must be used with items. Unit Name: " + this.name); + } + if (!this.toCursor()) return false; + + let tick = getTickCount(); + let timeout = Math.max(1000, me.ping * 6); + + while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash) || !me.gameReady) { + if (getTickCount() - tick > timeout) return false; + + if (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { + me.cancel(0); + } + + delay(me.ping * 2 + 100); + } + + for (let i = 0; i < 3; i += 1) { + clickMap(0, 0, me.x, me.y); + delay(40); + clickMap(2, 0, me.x, me.y); + + tick = getTickCount(); + + while (getTickCount() - tick < 500) { + if (!me.itemoncursor) { + delay(200); + + return true; + } + + delay(10); + } + } + + return false; +}; + +/** + * @description use consumable item, fixes issue with interact() returning false even if we used an item + */ +Unit.prototype.use = function () { + if (this === undefined || !this.type) return false; + if (this.type !== sdk.unittype.Item) throw new Error("Unit.use: Must be used with items. Unit Name: " + this.name); + if (!getBaseStat("items", this.classid, "useable")) { + throw new Error("Unit.use: Must be used with consumable items. Unit Name: " + this.name); + } + + let gid = this.gid; + let pingDelay = me.getPingDelay(); + let quantity = 0; + let iType = this.itemType; + let checkQuantity = false; + + if (me.mode === sdk.player.mode.SkillActionSequence) { + while (me.mode === sdk.player.mode.SkillActionSequence) { + delay (25); + } + } + + // make sure we don't have anything on cursor + if (me.itemoncursor) { + if (!Game.getCursorUnit().drop()) return false; + } + + switch (this.location) { + case sdk.storage.Stash: + case sdk.storage.Inventory: + if (this.isInStash && !Town.openStash()) return false; + // doesn't work, not sure why but it's missing something + // new PacketBuilder().byte(sdk.packets.send.UseItem).dword(gid).dword(this.x).dword(this.y).send(); + checkQuantity = iType === sdk.items.type.Book; + checkQuantity && (quantity = this.getStat(sdk.stats.Quantity)); + this.interact(); // use interact instead, was hoping to skip this since its really just doing the same thing over but oh well + + break; + case sdk.storage.Belt: + new PacketBuilder() + .byte(sdk.packets.send.UseBeltItem) + .dword(gid) + .dword(0) + .dword(0) + .send(); + break; + default: + return false; + } + + if (checkQuantity) { + return Misc.poll(() => this.getStat(sdk.stats.Quantity) < quantity, 200 + pingDelay, 50); + } else { + return Misc.poll(() => !Game.getItem(-1, -1, gid), 200 + pingDelay, 50); + } +}; + +/** + * @typedef {Object} ItemInfo + * @property {number} [classid] + * @property {number} [itemtype] + * @property {number} [quality] + * @property {boolean} [runeword] + * @property {boolean} [ethereal] + * @property {boolean | number} [equipped] + * @property {boolean} [basetype] + * @property {string | number} [name] + */ + +/** + * @description Returns item given by itemInfo + * @param {ItemInfo} itemInfo + * @returns {ItemUnit[]} + */ +Unit.prototype.checkItem = function (itemInfo) { + if (this === undefined || this.type > 1 || typeof itemInfo !== "object") { + return { have: false, item: null }; + } + + const itemObj = Object.assign({}, { + classid: -1, + itemtype: -1, + quality: -1, + runeword: null, + ethereal: null, + equipped: null, + basetype: null, + name: "" + }, itemInfo); + + // convert id into string + typeof itemObj.name === "number" && (itemObj.name = getLocaleString(itemObj.name)); + + let items = this.getItemsEx() + .filter(function (item) { + return (!item.questItem + && (itemObj.classid === -1 || item.classid === itemObj.classid) + && (itemObj.itemtype === -1 || item.itemType === itemObj.itemtype) + && (itemObj.quality === -1 || item.quality === itemObj.quality) + && (itemObj.runeword === null || (item.runeword === itemObj.runeword)) + && (itemObj.ethereal === null || (item.ethereal === itemObj.ethereal)) + && (itemObj.equipped === null || ( + typeof itemObj.equipped === "number" + ? item.bodylocation === itemObj.equipped + : item.isEquipped === itemObj.equipped) + ) + && (itemObj.basetype === null || ((item.normal || item.superior) === itemObj.basetype)) + && (!itemObj.name || item.fname.toLowerCase().includes(itemObj.name.toLowerCase())) + ); + }); + if (items.length > 0) { + return { + have: true, + item: copyUnit(items.first()) + }; + } else { + return { + have: false, + item: null + }; + } +}; + +/** + * @description Returns first item given by itemInfo + * @param {ItemInfo} itemInfo + * @returns {ItemUnit[]} + */ +Unit.prototype.findFirst = function (itemInfo = []) { + if (this === undefined || this.type > 1) return { have: false, item: null }; + if (!Array.isArray(itemInfo) || typeof itemInfo[0] !== "object") return { have: false, item: null }; + let itemList = this.getItemsEx(); + + for (let i = 0; i < itemInfo.length; i++) { + const itemObj = Object.assign({}, { + classid: -1, + itemtype: -1, + quality: -1, + runeword: null, + ethereal: null, + equipped: null, + name: "" + }, itemInfo[i]); + + // convert id into string + typeof itemObj.name === "number" && (itemObj.name = getLocaleString(itemObj.name)); + + let items = itemList + .filter(function (item) { + return (!item.questItem + && (itemObj.classid === -1 || item.classid === itemObj.classid) + && (itemObj.itemtype === -1 || item.itemType === itemObj.itemtype) + && (itemObj.quality === -1 || item.quality === itemObj.quality) + && (itemObj.runeword === null || (item.runeword === itemObj.runeword)) + && (itemObj.ethereal === null || (item.ethereal === itemObj.ethereal)) + && (itemObj.equipped === null || ( + typeof itemObj.equipped === "number" + ? item.bodylocation === itemObj.equipped + : item.isEquipped === itemObj.equipped) + ) + && (!itemObj.name || item.fname.toLowerCase().includes(itemObj.name.toLowerCase())) + ); + }); + if (items.length > 0) { + return { + have: true, + item: copyUnit(items.first()) + }; + } + } + + return { + have: false, + item: null + }; +}; + +/** + * @description Check if we have all the items given by itemInfo + * @param {ItemInfo} itemInfo + * @returns {boolean} + */ +Unit.prototype.haveAll = function (itemInfo = [], returnIfSome = false) { + if (this === undefined || this.type > 1) return false; + // if an object but not an array convert to array + !Array.isArray(itemInfo) && typeof itemInfo === "object" && (itemInfo = [itemInfo]); + if (!Array.isArray(itemInfo) || typeof itemInfo[0] !== "object") return false; + let itemList = this.getItemsEx(); + let haveAll = false; + let checkedGids = []; + + for (let i = 0; i < itemInfo.length; i++) { + const itemObj = Object.assign({}, { + classid: -1, + itemtype: -1, + quality: -1, + runeword: null, + ethereal: null, + equipped: null, + basetype: null, + name: "" + }, itemInfo[i]); + + // convert id into string + typeof itemObj.name === "number" && (itemObj.name = getLocaleString(itemObj.name)); + + let items = itemList + .filter(function (item) { + return (!item.questItem + && (checkedGids.indexOf(item.gid) === -1) + && (itemObj.classid === -1 || item.classid === itemObj.classid) + && (itemObj.itemtype === -1 || item.itemType === itemObj.itemtype) + && (itemObj.quality === -1 || item.quality === itemObj.quality) + && (itemObj.runeword === null || (item.runeword === itemObj.runeword)) + && (itemObj.ethereal === null || (item.ethereal === itemObj.ethereal)) + && (itemObj.equipped === null || ( + typeof itemObj.equipped === "number" + ? item.bodylocation === itemObj.equipped + : item.isEquipped === itemObj.equipped) + ) + && (itemObj.basetype === null || ((item.normal || item.superior) === itemObj.basetype)) + && (!itemObj.name.length || item.fname.toLowerCase().includes(itemObj.name.toLowerCase())) + ); + }); + if (items.length > 0) { + if (returnIfSome) return true; + checkedGids.push(items.first().gid); + haveAll = true; + } else { + if (returnIfSome) continue; + return false; + } + } + + return haveAll; +}; + +/** + * @description Check if we have some of the items given by itemInfo + * @param {ItemInfo[]} itemInfo + * @returns {boolean} + */ +Unit.prototype.haveSome = function (itemInfo = []) { + return this.haveAll(itemInfo, true); +}; + +/** + * @description Return the items of a player, or an empty array + * @param args + * @returns {ItemUnit[]} + */ +Unit.prototype.getItems = function (...args) { + let items = []; + let item = this.getItem.apply(this, args); + + if (item) { + do { + items.push(copyUnit(item)); + } while (item.getNext()); + } + + return Array.isArray(items) ? items : []; +}; + +Unit.prototype.getItemsEx = function (...args) { + let items = []; + let item = this.getItem.apply(this, args); + + if (item) { + do { + items.push(copyUnit(item)); + } while (item.getNext()); + } + + return items; +}; + +Unit.prototype.getPrefix = function (id) { + switch (typeof id) { + case "number": + if (typeof this.prefixnums !== "object") return this.prefixnum === id; + + for (let i = 0; i < this.prefixnums.length; i += 1) { + if (id === this.prefixnums[i]) { + return true; + } + } + + break; + case "string": + if (typeof this.prefixes !== "object") { + return this.prefix.replace(/\s+/g, "").toLowerCase() === id.replace(/\s+/g, "").toLowerCase(); + } + + for (let i = 0; i < this.prefixes.length; i += 1) { + if (id.replace(/\s+/g, "").toLowerCase() === this.prefixes[i].replace(/\s+/g, "").toLowerCase()) { + return true; + } + } + + break; + } + + return false; +}; + +Unit.prototype.getSuffix = function (id) { + switch (typeof id) { + case "number": + if (typeof this.suffixnums !== "object") return this.suffixnum === id; + + for (let i = 0; i < this.suffixnums.length; i += 1) { + if (id === this.suffixnums[i]) { + return true; + } + } + + break; + case "string": + if (typeof this.suffixes !== "object") { + return this.suffix.replace(/\s+/g, "").toLowerCase() === id.replace(/\s+/g, "").toLowerCase(); + } + + for (let i = 0; i < this.suffixes.length; i += 1) { + if (id.replace(/\s+/g, "").toLowerCase() === this.suffixes[i].replace(/\s+/g, "").toLowerCase()) { + return true; + } + } + + break; + } + + return false; +}; + +Unit.prototype.getStatEx = function (id, subid) { + let temp, rval, regex; + + switch (id) { + case sdk.stats.AllRes: + // calculates all res, doesn't exist though + // Block scope due to the variable declaration + { + // Get all res + let allres = [ + this.getStatEx(sdk.stats.FireResist), + this.getStatEx(sdk.stats.ColdResist), + this.getStatEx(sdk.stats.LightningResist), + this.getStatEx(sdk.stats.PoisonResist) + ]; + + // What is the minimum of the 4? + let min = Math.min.apply(null, allres); + + // Cap all res to the minimum amount of res + allres = allres.map(res => res > min ? min : res); + + // Get it in local variables, its more easy to read + let [fire, cold, light, psn] = allres; + + return fire === cold && cold === light && light === psn ? min : 0; + } + case sdk.stats.ToBlock: + switch (this.classid) { + case sdk.items.Buckler: + return this.getStat(sdk.stats.ToBlock); + case sdk.items.PreservedHead: + case sdk.items.MummifiedTrophy: + case sdk.items.MinionSkull: + return this.getStat(sdk.stats.ToBlock) - 3; + case sdk.items.SmallShield: + case sdk.items.ZombieHead: + case sdk.items.FetishTrophy: + case sdk.items.HellspawnSkull: + return this.getStat(sdk.stats.ToBlock) - 5; + case sdk.items.KiteShield: + case sdk.items.UnravellerHead: + case sdk.items.SextonTrophy: + case sdk.items.OverseerSkull: + return this.getStat(sdk.stats.ToBlock) - 8; + case sdk.items.SpikedShield: + case sdk.items.Defender: + case sdk.items.GargoyleHead: + case sdk.items.CantorTrophy: + case sdk.items.SuccubusSkull: + case sdk.items.Targe: + case sdk.items.AkaranTarge: + return this.getStat(sdk.stats.ToBlock) - 10; + case sdk.items.LargeShield: + case sdk.items.RoundShield: + case sdk.items.DemonHead: + case sdk.items.HierophantTrophy: + case sdk.items.BloodlordSkull: + return this.getStat(sdk.stats.ToBlock) - 12; + case sdk.items.Scutum: + return this.getStat(sdk.stats.ToBlock) - 14; + case sdk.items.Rondache: + case sdk.items.AkaranRondache: + return this.getStat(sdk.stats.ToBlock) - 15; + case sdk.items.GothicShield: + case sdk.items.AncientShield: + return this.getStat(sdk.stats.ToBlock) - 16; + case sdk.items.BarbedShield: + return this.getStat(sdk.stats.ToBlock) - 17; + case sdk.items.DragonShield: + return this.getStat(sdk.stats.ToBlock) - 18; + case sdk.items.VortexShield: + return this.getStat(sdk.stats.ToBlock) - 19; + case sdk.items.BoneShield: + case sdk.items.GrimShield: + case sdk.items.Luna: + case sdk.items.BladeBarrier: + case sdk.items.TrollNest: + case sdk.items.HeraldicShield: + case sdk.items.ProtectorShield: + return this.getStat(sdk.stats.ToBlock) - 20; + case sdk.items.Heater: + case sdk.items.Monarch: + case sdk.items.AerinShield: + case sdk.items.GildedShield: + case sdk.items.ZakarumShield: + return this.getStat(sdk.stats.ToBlock) - 22; + case sdk.items.TowerShield: + case sdk.items.Pavise: + case sdk.items.Hyperion: + case sdk.items.Aegis: + case sdk.items.Ward: + return this.getStat(sdk.stats.ToBlock) - 24; + case sdk.items.CrownShield: + case sdk.items.RoyalShield: + case sdk.items.KurastShield: + return this.getStat(sdk.stats.ToBlock) - 25; + case sdk.items.SacredRondache: + return this.getStat(sdk.stats.ToBlock) - 28; + case sdk.items.SacredTarge: + return this.getStat(sdk.stats.ToBlock) - 30; + } + + break; + case sdk.stats.MinDamage: + case sdk.stats.MaxDamage: + if (subid === 1) { + temp = this.getStat(-1); + rval = 0; + + for (let i = 0; i < temp.length; i += 1) { + switch (temp[i][0]) { + case id: // plus one handed dmg + case id + 2: // plus two handed dmg + // There are 2 occurrences of min/max if the item has +damage. Total damage is the sum of both. + // First occurrence is +damage, second is base item damage. + + if (rval) { // First occurence stored, return if the second one exists + return rval; + } + + if (this.getStat(temp[i][0]) > 0 && this.getStat(temp[i][0]) > temp[i][2]) { + rval = temp[i][2]; // Store the potential +dmg value + } + + break; + } + } + + return 0; + } + + break; + case sdk.stats.Defense: + if (subid === 0) { + if ([0, 1].indexOf(this.mode) < 0) { + break; + } + + switch (this.itemType) { + case sdk.items.type.Jewel: + case sdk.items.type.SmallCharm: + case sdk.items.type.LargeCharm: + case sdk.items.type.GrandCharm: + // defense is the same as plusdefense for these items + return this.getStat(sdk.stats.Defense); + } + + // can fail sometimes + !this.desc && (this.desc = this.description); + + if (this.desc) { + temp = this.desc.split("\n"); + regex = new RegExp("\\+\\d+ " + getLocaleString(sdk.locale.text.Defense).replace(/^\s+|\s+$/g, "")); + + for (let i = 0; i < temp.length; i += 1) { + if (temp[i].match(regex, "i")) { + return parseInt(temp[i].replace(/ÿc[0-9!"+<;.*]/, ""), 10); + } + } + } + + return 0; + } + + break; + case sdk.stats.PoisonMinDamage: + if (subid === 1) { + return Math.round(this.getStat(sdk.stats.PoisonMinDamage) * this.getStat(sdk.stats.PoisonLength) / 256); + } + + break; + case sdk.stats.AddClassSkills: + if (subid === undefined) { + for (let i = 0; i < 7; i += 1) { + let cSkill = this.getStat(sdk.stats.AddClassSkills, i); + if (cSkill) return cSkill; + } + + return 0; + } + + break; + case sdk.stats.AddSkillTab: + if (subid === undefined) { + temp = Object.values(sdk.skills.tabs); + + for (let i = 0; i < temp.length; i += 1) { + let sTab = this.getStat(sdk.stats.AddSkillTab, temp[i]); + if (sTab) return sTab; + } + + return 0; + } + + break; + case sdk.stats.SkillOnAttack: + case sdk.stats.SkillOnKill: + case sdk.stats.SkillOnDeath: + case sdk.stats.SkillOnStrike: + case sdk.stats.SkillOnLevelUp: + case sdk.stats.SkillWhenStruck: + case sdk.stats.ChargedSkill: + if (subid === 1) { + temp = this.getStat(-2); + + if (temp.hasOwnProperty(id)) { + if (temp[id] instanceof Array) { + for (let i = 0; i < temp[id].length; i += 1) { + if (temp[id][i] !== undefined) { + return temp[id][i].skill; + } + } + } else { + return temp[id].skill; + } + } + + return 0; + } + + if (subid === 2) { + temp = this.getStat(-2); + + if (temp.hasOwnProperty(id)) { + if (temp[id] instanceof Array) { + for (let i = 0; i < temp[id].length; i += 1) { + if (temp[id][i] !== undefined) { + return temp[id][i].level; + } + } + } else { + return temp[id].level; + } + } + + return 0; + } + + break; + case sdk.stats.PerLevelHp: // (for example Fortitude with hp per lvl can be defined now with 1.5) + return this.getStat(sdk.stats.PerLevelHp) / 2048; + } + + if (this.getFlag(sdk.items.flags.Runeword)) { + switch (id) { + case sdk.stats.ArmorPercent: + if ([0, 1].indexOf(this.mode) < 0) { + break; + } + + !this.desc && (this.desc = this.description); + + if (this.desc) { + temp = this.desc.split("\n"); + + for (let i = 0; i < temp.length; i += 1) { + if (temp[i].match(getLocaleString(sdk.locale.text.EnhancedDefense).replace(/^\s+|\s+$/g, ""), "i")) { + return parseInt(temp[i].replace(/ÿc[0-9!"+<;.*]/, ""), 10); + } + } + } + + return 0; + case sdk.stats.EnhancedDamage: + if ([0, 1].indexOf(this.mode) < 0) { + break; + } + + !this.desc && (this.desc = this.description); + + if (this.desc) { + temp = this.desc.split("\n"); + + for (let i = 0; i < temp.length; i += 1) { + if (temp[i].match(getLocaleString(sdk.locale.text.EnhancedDamage).replace(/^\s+|\s+$/g, ""), "i")) { + return parseInt(temp[i].replace(/ÿc[0-9!"+<;.*]/, ""), 10); + } + } + } + + return 0; + } + } + + return (subid === undefined ? this.getStat(id) : this.getStat(id, subid)); +}; + +/* + _NTIPAliasColor["black"] = 3; + _NTIPAliasColor["lightblue"] = 4; + _NTIPAliasColor["darkblue"] = 5; + _NTIPAliasColor["crystalblue"] = 6; + _NTIPAliasColor["lightred"] = 7; + _NTIPAliasColor["darkred"] = 8; + _NTIPAliasColor["crystalred"] = 9; + _NTIPAliasColor["darkgreen"] = 11; + _NTIPAliasColor["crystalgreen"] = 12; + _NTIPAliasColor["lightyellow"] = 13; + _NTIPAliasColor["darkyellow"] = 14; + _NTIPAliasColor["lightgold"] = 15; + _NTIPAliasColor["darkgold"] = 16; + _NTIPAliasColor["lightpurple"] = 17; + _NTIPAliasColor["orange"] = 19; + _NTIPAliasColor["white"] = 20; +*/ + +Unit.prototype.getColor = function () { + let colors; + let Color = { + black: 3, + lightblue: 4, + darkblue: 5, + crystalblue: 6, + lightred: 7, + darkred: 8, + crystalred: 9, + darkgreen: 11, + crystalgreen: 12, + lightyellow: 13, + darkyellow: 14, + lightgold: 15, + darkgold: 16, + lightpurple: 17, + orange: 19, + white: 20 + }; + + // check type + switch (this.itemType) { + case sdk.items.type.Shield: + case sdk.items.type.Armor: + case sdk.items.type.Boots: + case sdk.items.type.Gloves: + case sdk.items.type.Belt: + case sdk.items.type.AuricShields: + case sdk.items.type.VoodooHeads: + case sdk.items.type.Helm: + case sdk.items.type.PrimalHelm: + case sdk.items.type.Circlet: + case sdk.items.type.Pelt: + case sdk.items.type.Scepter: + case sdk.items.type.Wand: + case sdk.items.type.Staff: + case sdk.items.type.Bow: + case sdk.items.type.Axe: + case sdk.items.type.Club: + case sdk.items.type.Sword: + case sdk.items.type.Hammer: + case sdk.items.type.Knife: + case sdk.items.type.Spear: + case sdk.items.type.Polearm: + case sdk.items.type.Crossbow: + case sdk.items.type.Mace: + case sdk.items.type.ThrowingKnife: + case sdk.items.type.ThrowingAxe: + case sdk.items.type.Javelin: + case sdk.items.type.Orb: + case sdk.items.type.AmazonBow: + case sdk.items.type.AmazonSpear: + case sdk.items.type.AmazonJavelin: + case sdk.items.type.MissilePotion: + case sdk.items.type.HandtoHand: + case sdk.items.type.AssassinClaw: + break; + default: + return -1; + } + + // check quality + if ([sdk.items.quality.Magic, sdk.items.quality.Set, sdk.items.quality.Rare, sdk.items.quality.Unique] + .indexOf(this.quality) === -1) { + return -1; + } + + if (this.quality === sdk.items.quality.Magic || this.quality === sdk.items.quality.Rare) { + colors = { + "Screaming": Color.orange, + "Howling": Color.orange, + "Wailing": Color.orange, + "Sapphire": Color.lightblue, + "Snowy": Color.lightblue, + "Shivering": Color.lightblue, + "Boreal": Color.lightblue, + "Hibernal": Color.lightblue, + "Ruby": Color.lightred, + "Amber": Color.lightyellow, + "Static": Color.lightyellow, + "Glowing": Color.lightyellow, + "Buzzing": Color.lightyellow, + "Arcing": Color.lightyellow, + "Shocking": Color.lightyellow, + "Emerald": Color.crystalgreen, + "Saintly": Color.darkgold, + "Holy": Color.darkgold, + "Godly": Color.darkgold, + "Visionary": Color.white, + "Mnemonic": Color.crystalblue, + "Bowyer's": Color.lightgold, + "Gymnastic": Color.lightgold, + "Spearmaiden's": Color.lightgold, + "Archer's": Color.lightgold, + "Athlete's": Color.lightgold, + "Lancer's": Color.lightgold, + "Charged": Color.lightgold, + "Blazing": Color.lightgold, + "Freezing": Color.lightgold, + "Glacial": Color.lightgold, + "Powered": Color.lightgold, + "Volcanic": Color.lightgold, + "Blighting": Color.lightgold, + "Noxious": Color.lightgold, + "Mojo": Color.lightgold, + "Cursing": Color.lightgold, + "Venomous": Color.lightgold, + "Golemlord's": Color.lightgold, + "Warden's": Color.lightgold, + "Hawk Branded": Color.lightgold, + "Commander's": Color.lightgold, + "Marshal's": Color.lightgold, + "Rose Branded": Color.lightgold, + "Guardian's": Color.lightgold, + "Veteran's": Color.lightgold, + "Resonant": Color.lightgold, + "Raging": Color.lightgold, + "Echoing": Color.lightgold, + "Furious": Color.lightgold, + "Master's": Color.lightgold, // there's 2x masters... + "Caretaker's": Color.lightgold, + "Terrene": Color.lightgold, + "Feral": Color.lightgold, + "Gaean": Color.lightgold, + "Communal": Color.lightgold, + "Keeper's": Color.lightgold, + "Sensei's": Color.lightgold, + "Trickster's": Color.lightgold, + "Psychic": Color.lightgold, + "Kenshi's": Color.lightgold, + "Cunning": Color.lightgold, + "Shadow": Color.lightgold, + "Faithful": Color.white, + "Priest's": Color.crystalgreen, + "Dragon's": Color.crystalblue, + "Vulpine": Color.crystalblue, + "Shimmering": Color.lightpurple, + "Rainbow": Color.lightpurple, + "Scintillating": Color.lightpurple, + "Prismatic": Color.lightpurple, + "Chromatic": Color.lightpurple, + "Hierophant's": Color.crystalgreen, + "Berserker's": Color.crystalgreen, + "Necromancer's": Color.crystalgreen, + "Witch-hunter's": Color.crystalgreen, + "Arch-Angel's": Color.crystalgreen, + "Valkyrie's": Color.crystalgreen, + "Massive": Color.darkgold, + "Savage": Color.darkgold, + "Merciless": Color.darkgold, + "Ferocious": Color.black, + "Grinding": Color.white, + "Cruel": Color.black, + "Gold": Color.lightgold, + "Platinum": Color.lightgold, + "Meteoric": Color.lightgold, + "Strange": Color.lightgold, + "Weird": Color.lightgold, + "Knight's": Color.darkgold, + "Lord's": Color.darkgold, + "Fool's": Color.white, + "King's": Color.darkgold, + //"Master's": Color.darkgold, + "Elysian": Color.darkgold, + "Fiery": Color.darkred, + "Smoldering": Color.darkred, + "Smoking": Color.darkred, + "Flaming": Color.darkred, + "Condensing": Color.darkred, + "Septic": Color.darkgreen, + "Foul": Color.darkgreen, + "Corrosive": Color.darkgreen, + "Toxic": Color.darkgreen, + "Pestilent": Color.darkgreen, + "of Quickness": Color.darkyellow, + "of the Glacier": Color.darkblue, + "of Winter": Color.darkblue, + "of Burning": Color.darkred, + "of Incineration": Color.darkred, + "of Thunder": Color.darkyellow, + "of Storms": Color.darkyellow, + "of Carnage": Color.black, + "of Slaughter": Color.black, + "of Butchery": Color.black, + "of Evisceration": Color.black, + "of Performance": Color.black, + "of Transcendence": Color.black, + "of Pestilence": Color.darkgreen, + "of Anthrax": Color.darkgreen, + "of the Locust": Color.crystalred, + "of the Lamprey": Color.crystalred, + "of the Wraith": Color.crystalred, + "of the Vampire": Color.crystalred, + "of Icebolt": Color.lightblue, + "of Nova": Color.crystalblue, + "of the Mammoth": Color.crystalred, + "of Frost Shield": Color.lightblue, + "of Nova Shield": Color.crystalblue, + "of Wealth": Color.lightgold, + "of Fortune": Color.lightgold, + "of Luck": Color.lightgold, + "of Perfection": Color.darkgold, + "of Regrowth": Color.crystalred, + "of Spikes": Color.orange, + "of Razors": Color.orange, + "of Swords": Color.orange, + "of Stability": Color.darkyellow, + "of the Colosuss": Color.crystalred, + "of the Squid": Color.crystalred, + "of the Whale": Color.crystalred, + "of Defiance": Color.darkred, + "of the Titan": Color.darkgold, + "of Atlas": Color.darkgold, + "of Wizardry": Color.darkgold + }; + + switch (this.itemType) { + case sdk.items.type.Boots: + colors["of Precision"] = Color.darkgold; + + break; + case sdk.items.type.Gloves: + colors["of Alacrity"] = Color.darkyellow; + colors["of the Leech"] = Color.crystalred; + colors["of the Bat"] = Color.crystalred; + colors["of the Giant"] = Color.darkgold; + + break; + } + } else if (this.set) { + if (this.identified) { + for (let i = 0; i < 127; i += 1) { + if (this.fname.split("\n").reverse()[0].includes(getLocaleString(getBaseStat(16, i, 3)))) { + return getBaseStat(16, i, 12) > 20 ? -1 : getBaseStat(16, i, 12); + } + } + } else { + return Color.lightyellow; // Unidentified set item + } + } else if (this.unique) { + for (let i = 0; i < 401; i += 1) { + if (this.code === getBaseStat(17, i, 4).replace(/^\s+|\s+$/g, "") + && this.fname.split("\n").reverse()[0].includes(getLocaleString(getBaseStat(17, i, 2)))) { + return getBaseStat(17, i, 13) > 20 ? -1 : getBaseStat(17, i, 13); + } + } + } + + for (let i = 0; i < this.suffixes.length; i += 1) { + if (colors.hasOwnProperty(this.suffixes[i])) { + return colors[this.suffixes[i]]; + } + } + + for (let i = 0; i < this.prefixes.length; i += 1) { + if (colors.hasOwnProperty(this.prefixes[i])) { + return colors[this.prefixes[i]]; + } + } + + return -1; +}; + +/** + * @description Used upon item units like ArachnidMesh.castChargedSkill([skillId]) + * or directly on the "me" unit me.castChargedSkill(278); + * @param {number} skillId + * @param {number} x + * @param {number} y + * @returns {boolean} + * @throws Error + */ +Unit.prototype.castChargedSkill = function (...args) { + let skillId, x, y; + /** @type {Monster} */ + let unit; + /** @type {ItemUnit} */ + let chargedItem; + /** @type {Charge} */ + let charge; + /** @param {Charge} itemCharge */ + let validCharge = function (itemCharge) { + return itemCharge.skill === skillId && itemCharge.charges; + }; + + switch (args.length) { + case 0: // item.castChargedSkill() + break; + case 1: + if (args[0] instanceof Unit) { // hellfire.castChargedSkill(monster); + unit = args[0]; + } else { + skillId = args[0]; + } + + break; + case 2: + if (typeof args[0] === "number") { + if (args[1] instanceof Unit) { // me.castChargedSkill(skillId,unit) + [skillId, unit] = [...args]; + } else if (typeof args[1] === "number") { // item.castChargedSkill(x,y) + [x, y] = [...args]; + } + } else { + throw new Error(" invalid arguments, expected (skillId, unit) or (x, y)"); + } + + break; + case 3: + // If all arguments are numbers + if (typeof args[0] === "number" && typeof args[1] === "number" && typeof args[2] === "number") { + [skillId, x, y] = [...args]; + } + + break; + default: + throw new Error("invalid arguments, expected 'me' object or 'item' unit"); + } + + // Charged skills can only be casted on x, y coordinates + unit && ([x, y] = [unit.x, unit.y]); + + if (this !== me && this.type !== sdk.unittype.Item) { + throw Error("invalid arguments, expected 'me' object or 'item' unit"); + } + + // Called the function the unit, me. + if (this === me) { + if (!skillId) throw Error("Must supply skillId on me.castChargedSkill"); + if (!Skill.charges.length || !Skill.charges.some(validCharge)) { + // only rebuild list if we are unsure if we have the skill + Skill.getCharges(); + } + if (!Skill.charges.length) return false; + let chargedItems = Skill.charges.filter(validCharge); + + if (chargedItems.length === 0) { + throw Error("Don't have the charged skill (" + skillId + "), or not enough charges"); + } + + chargedItem = chargedItems + .sort(function (a, b) { + return b.charge.level - a.charge.level; + }).first().unit; + return chargedItem.castChargedSkill.apply(chargedItem, args); + } else if (this.type === sdk.unittype.Item) { + /** @type {Charge[]} */ + let charges = this.getStat(-2)[sdk.stats.ChargedSkill]; // WARNING. Somehow this gives duplicates + if (!charges) throw Error("No charged skill on this item"); + if (!Array.isArray(charges)) { + charges = [charges]; + } + + if (skillId) { + // Filter out all other charged skills + charges = charges.filter(item => (skillId && item.skill === skillId) && !!item.charges); + } else if (charges.length > 1) { + throw new Error("multiple charges on this item without a given skillId"); + } + + charge = charges.first(); + + if (charge) { + // Setting skill on hand + if (!Config.PacketCasting || Config.PacketCasting === 1 && skillId !== sdk.skills.Teleport) { + // Non packet casting + return Skill.cast(skillId, sdk.skills.hand.Right, x || me.x, y || me.y, this); + } + + // Packet casting + // Setting skill on hand + new PacketBuilder() + .byte(sdk.packets.send.SelectSkill) + .word(charge.skill) + .byte(0x00) + .byte(0x00) + .dword(this.gid) + .send(); + // No need for a delay, since its TCP, the server recv's the next statement always after the send cast skill packet + // Cast the skill + new PacketBuilder() + .byte(sdk.packets.send.RightSkillOnLocation) + .word(x || me.x) + .word(y || me.y) + .send(); + // The result of "successfully" casted is different, so we cant wait for it here. We have to assume it worked + + return true; + } + } + + return false; +}; + +/** + * @this {ItemUnit} + * @description equip an item. + * @param {number | number[]} [destLocation] + */ +Unit.prototype.equip = function (destLocation) { + if (this.isEquipped) return true; // Item already equiped + /** @type {ItemUnit} */ + const _self = this; + + /** @param {ItemUnit} */ + const findspot = function (item) { + let tempspot = Storage.Stash.FindSpot(item); + + if (getUIFlag(sdk.uiflags.Stash) && tempspot) { + return { location: Storage.Stash.location, coord: tempspot }; + } + + tempspot = Storage.Inventory.FindSpot(item); + + return tempspot ? { location: Storage.Inventory.location, coord: tempspot } : false; + }; + + // Not an item, or unidentified, or not enough stats + if (_self.type !== sdk.unittype.Item || !_self.identified + || _self.lvlreq > me.getStat(sdk.stats.Level) + || _self.dexreq > me.getStat(sdk.stats.Dexterity) + || _self.strreq > me.getStat(sdk.stats.Strength)) { + return false; + } + + // If not a specific location is given, figure it out (can be useful to equip a double weapon) + !destLocation && (destLocation = _self.getBodyLoc()); + // If destLocation isnt an array, make it one + !Array.isArray(destLocation) && (destLocation = [destLocation]); + + console.log("equiping " + _self.prettyPrint + " to bodylocation: " + destLocation.first()); + + let currentEquiped = me.getItemsEx(-1) + .filter(function (item) { + return (destLocation.includes(item.bodylocation) + || ( item.isOnMain// Deal with double handed weapons + && [sdk.body.RightArm, sdk.body.LeftArm].indexOf(destLocation) // in case destination is on the weapon/shield slot + && (item.strictlyTwoHanded || _self.strictlyTwoHanded) // one of the items is strictly two handed + ) + ); + }).sort(function (a, b) { + return b - a; + }); // shields first + + // if nothing is equipped at the moment, just equip it + if (!currentEquiped.length) { + clickItemAndWait(sdk.clicktypes.click.item.Left, this); + clickItemAndWait(sdk.clicktypes.click.item.Left, destLocation.first()); + } else { + // unequip / swap items + currentEquiped.forEach(function (item, index) { + // Last item, so swap instead of putting off first + if (index === (currentEquiped.length - 1)) { + console.log("swap " + _self.name + " for " + item.name); + let oldLoc = { x: _self.x, y: _self.y, location: _self.location }; + clickItemAndWait(sdk.clicktypes.click.item.Left, _self); // Pick up current item + clickItemAndWait(sdk.clicktypes.click.item.Left, destLocation.first()); // the swap of items + // Find a spot for the current item + let spot = findspot(item); + + if (!spot) { // If no spot is found for the item, rollback + clickItemAndWait(sdk.clicktypes.click.item.Left, destLocation.first()); // swap again + clickItemAndWait(sdk.clicktypes.click.item.Left, oldLoc.x, oldLoc.y, oldLoc.location); // put item back on old spot + throw Error("cant find spot for unequipped item"); + } + + clickItemAndWait(sdk.clicktypes.click.item.Left, spot.coord.y, spot.coord.x, spot.location); // put item on the found spot + + return; + } + + console.log("Unequip item first " + item.name); + // Incase multiple items are equipped + let spot = findspot(item); // Find a spot for the current item + + if (!spot) throw Error("cant find spot for unequipped item"); + + clickItemAndWait(sdk.clicktypes.click.item.Left, item.bodylocation); + clickItemAndWait(sdk.clicktypes.click.item.Left, spot.coord.x, spot.coord.y, spot.location); + }); + } + + return { + success: this.bodylocation === destLocation.first(), + unequiped: currentEquiped, + rollback: () => currentEquiped.forEach(item => item.equip()) // Note; rollback only works if you had other items equipped before. + }; +}; + +/** + * @this {ItemUnit} + * @returns {number[]} + */ +Unit.prototype.getBodyLoc = function () { + const _types = new Map([ + [sdk.body.Head, [sdk.items.type.Helm, sdk.items.type.Pelt, sdk.items.type.PrimalHelm]], + [sdk.body.Neck, [sdk.items.type.Amulet]], + [sdk.body.Armor, [sdk.items.type.Armor]], + [sdk.body.RightArm, [ + sdk.items.type.Scepter, sdk.items.type.Wand, sdk.items.type.Staff, sdk.items.type.Bow, + sdk.items.type.Axe, sdk.items.type.Club, sdk.items.type.Sword, sdk.items.type.Hammer, + sdk.items.type.Knife, sdk.items.type.Spear, sdk.items.type.Polearm, sdk.items.type.Crossbow, + sdk.items.type.Mace, sdk.items.type.Javelin, sdk.items.type.ThrowingKnife, sdk.items.type.ThrowingAxe, + sdk.items.type.MissilePotion, sdk.items.type.Javelin, sdk.items.type.Orb, + sdk.items.type.HandtoHand, sdk.items.type.AmazonBow, + sdk.items.type.AmazonSpear + ]], // right arm + [sdk.body.LeftArm, [ + sdk.items.type.Shield, sdk.items.type.BowQuiver, + sdk.items.type.CrossbowQuiver, sdk.items.type.AuricShields, sdk.items.type.VoodooHeads + ]], // left arm + [sdk.body.RingRight, [sdk.items.type.Ring]], + [sdk.body.RingLeft, [sdk.items.type.Ring]], + [sdk.body.Belt, [sdk.items.type.Belt]], + [sdk.body.Feet, [sdk.items.type.Boots]], + [sdk.body.Gloves, [sdk.items.type.Gloves]], + ]); + let bodyLoc = []; + + for (let [key, value] of _types) { + if (value.includes(this.itemType)) { + bodyLoc.push(key); + } + } + + return bodyLoc; +}; + +Unit.prototype.getRes = function (type, difficulty) { + if (!type) return -1; + if (![ + sdk.stats.FireResist, sdk.stats.ColdResist, + sdk.stats.PoisonResist, sdk.stats.LightningResist + ].includes(type)) { + return -1; + } + + difficulty === undefined || difficulty < 0 && (difficulty = 0); + difficulty > 2 && (difficulty = 2); + + let modifier = me.classic + ? [0, 20, 50][difficulty] + : [0, 40, 100][difficulty]; + if (this === me) { + switch (type) { + case sdk.stats.FireResist: + me.getState(sdk.states.ShrineResFire) && (modifier += 75); + + break; + case sdk.stats.ColdResist: + me.getState(sdk.states.ShrineResCold) && (modifier += 75); + me.getState(sdk.states.Thawing) && (modifier += 50); + + break; + case sdk.stats.LightningResist: + me.getState(sdk.states.ShrineResLighting) && (modifier += 75); + + break; + case sdk.stats.PoisonResist: + me.getState(sdk.states.ShrineResPoison) && (modifier += 75); + me.getState(sdk.states.Antidote) && (modifier += 50); + + break; + } + } + return this.getStat(type) - modifier; +}; + +{ + let coords = function () { + if (Array.isArray(this) && this.length > 1) { + return [this[0], this[1]]; + } + + if (typeof this.x !== "undefined" && typeof this.y !== "undefined") { + return this instanceof PresetUnit && [this.roomx * 5 + this.x, this.roomy * 5 + this.y] || [this.x, this.y]; + } + + return [undefined, undefined]; + }; + + Object.defineProperties(Object.prototype, { + distance: { + get: function () { + return !me.gameReady ? NaN : /* Math.round */(getDistance.apply(null, [me, ...coords.apply(this)])); + }, + enumerable: false, + }, + }); + + Object.defineProperty(Object.prototype, "mobCount", { + writable: true, + enumerable: false, + configurable: true, + value: function (givenSettings = {}) { + let [x, y] = coords.apply(this); + const settings = Object.assign({}, { + range: 5, + coll: ( + sdk.collision.BlockWall | sdk.collision.ClosedDoor | sdk.collision.LineOfSight | sdk.collision.BlockMissile + ), + type: 0, + ignoreClassids: [], + }, givenSettings); + return getUnits(sdk.unittype.Monster) + .filter(function (mon) { + return mon.attackable && getDistance(x, y, mon.x, mon.y) < settings.range + && (!settings.type || (settings.type & mon.spectype)) + && (settings.ignoreClassids.indexOf(mon.classid) === -1) + && !CollMap.checkColl({ x: x, y: y }, mon, settings.coll, 1); + }).length; + } + }); +} + +Unit.prototype.hasEnchant = function (...enchants) { + if (!this.isMonster) return false; + for (let enchant of enchants) { + if (this.getEnchant(enchant)) return true; + } + return false; +}; + +Unit.prototype.usingShield = function () { + if (this.type > sdk.unittype.Monster) return false; + // always switch to main hand if we are checking ourselves + if (this === me && me.weaponswitch !== sdk.player.slot.Main) { + me.switchWeapons(sdk.player.slot.Main); + } + return this.getItemsEx(-1, sdk.items.mode.Equipped) + .filter(function (el) { + return el.isShield; + }) + .first(); +}; + +// something in here is causing demon imps in barricade towers to be skipped - todo: figure out what +Unit.prototype.__defineGetter__("attackable", function () { + if (this === undefined || !copyUnit(this).x) return false; + if (this.type > sdk.unittype.Monster) return false; + // must be in same area + if (this.area !== me.area) return false; + // player and they are hostile + if (this.type === sdk.unittype.Player && getPlayerFlag(me.gid, this.gid, 8) && !this.dead) return true; + // Dead monster + if (this.hp === 0 || this.mode === sdk.monsters.mode.Death || this.mode === sdk.monsters.mode.Dead) return false; + // Friendly monster/NPC + if (this.getStat(sdk.stats.Alignment) === 2) return false; + // catapults were returning a level of 0 and hanging up clear scripts + if (this.charlvl < 1) return false; + // neverCount base stat - hydras, traps etc. + if (!this.isMonsterObject && getBaseStat("monstats", this.classid, "neverCount")) { + return false; + } + // Monsters that are in flight + if ([ + sdk.monsters.CarrionBird1, sdk.monsters.UndeadScavenger, sdk.monsters.HellBuzzard, + sdk.monsters.WingedNightmare, sdk.monsters.SoulKiller2/*feel like this one is wrong*/, + sdk.monsters.CarrionBird2].includes(this.classid) && this.mode === sdk.monsters.mode.UsingSkill1) { + return false; + } + // Monsters that are Burrowed/Submerged + if ([ + sdk.monsters.SandMaggot, sdk.monsters.RockWorm, sdk.monsters.Devourer, + sdk.monsters.GiantLamprey, sdk.monsters.WorldKiller2, + sdk.monsters.WaterWatcherLimb, sdk.monsters.RiverStalkerLimb, sdk.monsters.StygianWatcherLimb, + sdk.monsters.WaterWatcherHead, sdk.monsters.RiverStalkerHead, sdk.monsters.StygianWatcherHead + ].includes(this.classid) && this.mode === sdk.monsters.mode.Spawning) { + return false; + } + + return [sdk.monsters.ThroneBaal, sdk.monsters.Cow/*an evil force*/].indexOf(this.classid) === -1; +}); + +Object.defineProperty(Unit.prototype, "curseable", { + /** @this {Player | Monster} */ + get: function () { + // must be player or monster + if (this === undefined || !copyUnit(this).x || this.type > 1) return false; + // Dead monster + if (this.hp === 0 || this.mode === sdk.monsters.mode.Death || this.mode === sdk.monsters.mode.Dead) return false; + // attract can't be overridden + if (this.getState(sdk.states.Attract)) return false; + // "Possessed" + if (!!this.name && !!this.name.includes(getLocaleString(sdk.locale.text.Possessed))) return false; + if (this.type === sdk.unittype.Player && getPlayerFlag(me.gid, this.gid, 8) && !this.dead) return true; + // Friendly monster/NPC + if (this.getStat(sdk.stats.Alignment) === 2) return false; + // catapults were returning a level of 0 and hanging up clear scripts + if (this.charlvl < 1) return false; + // Monsters that are in flight + if ([ + sdk.monsters.CarrionBird1, sdk.monsters.UndeadScavenger, sdk.monsters.HellBuzzard, + sdk.monsters.WingedNightmare, sdk.monsters.SoulKiller2/*feel like this one is wrong*/, + sdk.monsters.CarrionBird2].includes(this.classid) && this.mode === sdk.monsters.mode.UsingSkill1) { + return false; + } + // Monsters that are Burrowed/Submerged + if ([ + sdk.monsters.SandMaggot, sdk.monsters.RockWorm, sdk.monsters.Devourer, + sdk.monsters.GiantLamprey, sdk.monsters.WorldKiller2, + sdk.monsters.WaterWatcherLimb, sdk.monsters.RiverStalkerLimb, sdk.monsters.StygianWatcherLimb, + sdk.monsters.WaterWatcherHead, sdk.monsters.RiverStalkerHead, sdk.monsters.StygianWatcherHead + ].includes(this.classid) && this.mode === sdk.monsters.mode.Spawning) { + return false; + } + + return (!this.isMonsterObject && !this.isMonsterEgg && !this.isMonsterNest && !this.isBaalTentacle && [ + sdk.monsters.WaterWatcherLimb, sdk.monsters.WaterWatcherHead, + sdk.monsters.Flavie, sdk.monsters.ThroneBaal, sdk.monsters.Cow + ].indexOf(this.classid) === -1); + } +}); + +Unit.prototype.__defineGetter__("scareable", function () { + return this.curseable && !(this.isSpecial) && this.classid !== sdk.monsters.ListerTheTormenter; +}); + +Unit.prototype.getMobCount = function (range = 10, coll = 0, type = 0, noSpecialMobs = false) { + if (this === undefined) return 0; + const _this = this; + return getUnits(sdk.unittype.Monster) + .filter(function (mon) { + return mon.attackable && getDistance(_this, mon) < range + && (!type || ((type & mon.spectype) && !noSpecialMobs)) + && (!coll || !checkCollision(_this, mon, coll)); + }).length; +}; + +Unit.prototype.checkForMobs = function (givenSettings = {}) { + if (this === undefined) return 0; + const _this = this; + const settings = Object.assign({ + range: 10, + count: 1, + coll: 0, + spectype: sdk.monsters.spectype.All + }, givenSettings); + let mob = Game.getMonster(); + let count = 0; + if (mob) { + do { + if (getDistance(_this, mob) < settings.range && mob.attackable + && (!settings.spectype || ((settings.spectype & mob.spectype))) + && (!settings.coll || !checkCollision(_this, mob, settings.coll))) { + count++; + } + if (count >= settings.count) { + return true; + } + } while (mob.getNext()); + } + return false; +}; + +/** + * @description check if unit is in an area + * @param {number} area + * @returns {boolean} if unit is in specified area + */ +Unit.prototype.inArea = function (area = 0) { + if (this === undefined) return false; + return this.area === area; +}; + +// should this be broken into two functions for item vs unit (player, monster, ect) +/** + * @description check if unit is a certain unit by classid + * @param {number} classid + * @returns {boolean} if unit matches the specified classid + */ +Unit.prototype.isUnit = function (classid = -1) { + if (this === undefined) return false; + return this.classid === classid; +}; + +Object.defineProperty(Object.prototype, "has", { + writable: true, + enumerable: false, + configurable: true, + value: function (...args) { + if (this === undefined) return undefined; + return this[args[0]] !== undefined + ? typeof this[args[0]] === "function" + ? this[args[0]].apply(this, ([...args].slice(1))) + : this[args[0]] + : {}; + } +}); + +PresetUnit.prototype.realCoords = function () { + return { + id: this.id, + area: this.level, // for some reason, preset units names the area "level" + x: this.roomx * 5 + this.x, + y: this.roomy * 5 + this.y, + }; +}; + +Unit.prototype.openUnit = function () { + if (this === undefined) return false; + if (this.type !== sdk.unittype.Object && this.type !== sdk.unittype.Stairs) { + return false; + } + if (this.mode !== sdk.objects.mode.Inactive) return true; + + for (let i = 0; i < 3; i += 1) { + let usetk = (i < 2 && Skill.useTK(this)); + + if (this.distance > 5) { + Pather.moveNearUnit(this, (usetk ? 20 : 5) - i); + } + + delay(300); + // try to activate it once + if (usetk && i === 0 && this.distance < 21) { + Packet.telekinesis(this); + } else { + Packet.entityInteract(this); + } + + const _self = this; + if (Misc.poll(function () { + return _self.mode !== sdk.objects.mode.Inactive; + }, 2000, 60)) { + delay(100); + + return true; + } + + let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 3); + !!coord && Pather.moveTo(coord.x, coord.y); + } + + return false; +}; + +Unit.prototype.useUnit = function (targetArea) { + if (this === undefined) return false; + if (this.type !== sdk.unittype.Object && this.type !== sdk.unittype.Stairs) { + return false; + } + const preArea = me.area; + + MainLoop: + for (let i = 0; i < 5; i += 1) { + let usetk = (i < 2 && Skill.useTK(this)); + + if (this.distance > 5) { + Pather.moveNearUnit(this, (usetk ? 20 : 5)); + // try to activate it once + if (usetk && i === 0 && this.mode === sdk.objects.mode.Inactive && this.distance < 21) { + Packet.telekinesis(this); + } + } + + if (this.type === sdk.unittype.Object && this.mode === sdk.objects.mode.Inactive) { + if (me.inArea(sdk.areas.Travincal) && targetArea === sdk.areas.DuranceofHateLvl1) { + if (!me.blackendTemple) { + throw new Error("useUnit: Incomplete quest. TargetArea: " + getAreaName(targetArea)); + } + } else if (me.inArea(sdk.areas.ArreatSummit) && targetArea === sdk.areas.WorldstoneLvl1) { + if (!me.ancients) { + throw new Error("useUnit: Incomplete quest. TargetArea: " + getAreaName(targetArea)); + } + } + + me.inArea(sdk.areas.A3SewersLvl1) + ? Pather.openUnit(sdk.unittype.Object, sdk.objects.SewerLever) + : this.openUnit(); + } + + if (this.type === sdk.unittype.Object + && this.classid === sdk.objects.RedPortalToAct4 + && me.inArea(sdk.areas.DuranceofHateLvl3) + && targetArea === sdk.areas.PandemoniumFortress + && me.getQuest(sdk.quest.id.TheGuardian, sdk.quest.states.Completed) !== 1) { + throw new Error("useUnit: Incomplete quest. TargetArea: " + getAreaName(targetArea)); + } + + delay(300); + this.type === sdk.unittype.Stairs + ? Misc.click(0, 0, this) + : usetk && this.distance > 5 + ? Packet.telekinesis(this) + : Packet.entityInteract(this); + delay(300); + + let tick = getTickCount(); + + while (getTickCount() - tick < 3000) { + if ((!targetArea && me.area !== preArea) || me.area === targetArea) { + delay(200); + + break MainLoop; + } + + delay(10); + } + + i > 2 && Packet.flash(me.gid); + let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 3); + !!coord && Pather.moveTo(coord.x, coord.y); + } + + while (!me.idle && !me.gameReady) { + delay(40); + } + + return targetArea ? me.area === targetArea : me.area !== preArea; +}; diff --git a/d2bs/kolbot/libs/core/Runewords.js b/d2bs/kolbot/libs/core/Runewords.js new file mode 100644 index 000000000..8bfb89a0b --- /dev/null +++ b/d2bs/kolbot/libs/core/Runewords.js @@ -0,0 +1,365 @@ +/** +* @filename Runewords.js +* @author kolton, theBGuy +* @desc make and reroll runewords +* +*/ + +const Runeword = require("./GameData/RuneData"); + +const Runewords = { + needList: [], + pickitEntries: [], + validGids: [], + + init: function () { + if (!Config.MakeRunewords) return; + + Runewords.pickitEntries = []; + + // initiate pickit entries + for (let entry of Config.KeepRunewords) { + let info = { + file: "Character Config", + line: entry + }; + + let parsedLine = NTIP.ParseLineInt(entry, info); + if (parsedLine) { + this.pickitEntries.push(parsedLine); + } + } + + // change text to classid + for (let i = 0; i < Config.Runewords.length; i += 1) { + const [runeword, base] = Config.Runewords[i]; + + if (!runeword.ladderRestricted()) { + if (isNaN(base)) { + if (NTIPAliasClassID.hasOwnProperty(base.replace(/\s+/g, "").toLowerCase())) { + Config.Runewords[i][1] = NTIPAliasClassID[base.replace(/\s+/g, "").toLowerCase()]; + } else { + Misc.errorReport("ÿc1Invalid runewords entry:ÿc0 " + base); + Config.Runewords.splice(i, 1); + + i -= 1; + } + } + } + } + + this.buildLists(); + }, + + /** + * Ensures this item isn't wanted by the CraftingSystem + * @param {ItemUnit} item + * @returns {boolean} + * @todo Why only the crafting system? + */ + validItem: function (item) { + return CraftingSystem.validGids.indexOf(item.gid) === -1; + }, + + /** + * build a list of needed runes. won't count runes until the base item is found for a given runeword + * @returns {void} + */ + buildLists: function () { + Runewords.validGids = []; + Runewords.needList = []; + let baseCheck; + let items = me.findItems(-1, sdk.items.mode.inStorage); + + for (let i = 0; i < Config.Runewords.length; i += 1) { + const [runeword, base, ethFlag] = Config.Runewords[i]; + + if (!baseCheck) { + baseCheck = this.getBase(runeword, base, (ethFlag || 0)) || this.getBase(runeword, base, (ethFlag || 0), true); + } + + if (this.getBase(runeword, base, (ethFlag || 0))) { + RuneLoop: + for (let j = 0; j < runeword.runes.length; j += 1) { + for (let k = 0; k < items.length; k += 1) { + if (items[k].classid === runeword.runes[j] && this.validItem(items[k])) { + this.validGids.push(items[k].gid); + items.splice(k, 1); + + k -= 1; + + continue RuneLoop; + } + } + + this.needList.push(runeword.runes[j]); + } + } + } + + // hel rune for rerolling purposes + if (baseCheck) { + let hel = me.getItem(sdk.items.runes.Hel, sdk.items.mode.inStorage); + + if (hel) { + do { + if (this.validGids.indexOf(hel.gid) === -1 && this.validItem(hel)) { + this.validGids.push(hel.gid); + + return; + } + } while (hel.getNext()); + } + + this.needList.push(sdk.items.runes.Hel); + } + }, + + /** + * @param {number} classid + * @param {number} gid + */ + update: function (classid, gid) { + for (let i = 0; i < this.needList.length; i += 1) { + if (this.needList[i] === classid) { + this.needList.splice(i, 1); + + i -= 1; + + break; + } + } + + this.validGids.push(gid); + }, + + /** + * returns an array of items that make a runeword if found, false if we don't have enough items for any + * @returns {ItemUnit[] | boolean} + */ + checkRunewords: function () { + // keep a const reference of our items so failed checks don't remove items from the list + const itemsRef = me.findItems(-1, sdk.items.mode.inStorage); + + for (let i = 0; i < Config.Runewords.length; i += 1) { + let itemList = []; // reset item list + let items = itemsRef.slice(); // copy itemsRef + + const [runeword, wantedBase, ethFlag] = Config.Runewords[i]; + let base = this.getBase(runeword, wantedBase, (ethFlag || 0)); // check base + + if (base) { + itemList.push(base); // push the base + + for (let j = 0; j < runeword.runes.length; j += 1) { + for (let k = 0; k < items.length; k += 1) { + if (items[k].classid === runeword.runes[j]) { // rune matched + itemList.push(items[k]); // push into the item list + items.splice(k, 1); // remove from item list as to not count it twice + + k -= 1; + + break; // stop item cycle - we found the item + } + } + + // can't complete runeword - go to next one + if (itemList.length !== j + 2) { + break; + } + + if (itemList.length === runeword.runes.length + 1) { // runes + base + return itemList; // these items are our runeword + } + } + } + } + + return false; + }, + + /** + * for pickit + * @param {ItemUnit} unit + * @returns {boolean} + */ + checkItem: function (unit) { + if (!Config.MakeRunewords) return false; + return (unit.itemType === sdk.items.type.Rune && this.needList.includes(unit.classid)); + }, + + /** + * for clearInventory - don't drop runes that are a part of runeword recipe + * @param {ItemUnit} unit + * @returns {boolean} + */ + keepItem: function (unit) { + return this.validGids.includes(unit.gid); + }, + + /** + * Get the base item based on classid and runeword recipe + * @param {runeword} runeword + * @param {ItemUnit | number} base - item or classid + * @param {number} [ethFlag] + * @param {boolean} [reroll] - optional reroll argument = gets a runeword that needs rerolling + * @returns {ItemUnit | false} + */ + getBase: function (runeword, base, ethFlag, reroll) { + let item = typeof base === "object" + ? base + : me.getItem(base, sdk.items.mode.inStorage); + + if (item) { + do { + if (item && item.quality < sdk.items.quality.Magic + && item.sockets === runeword.sockets && runeword.itemTypes.includes(item.itemType)) { + /** + * check if item has items socketed in it + * better check than getFlag(sdk.items.flags.Runeword) because randomly socketed items return false for it + */ + + if ((!reroll && !item.getItem()) || (reroll && item.getItem() && !NTIP.CheckItem(item, this.pickitEntries))) { + if (!ethFlag || (ethFlag === Roll.Eth && item.ethereal) || (ethFlag === Roll.NonEth && !item.ethereal)) { + return copyUnit(item); + } + } + } + } while (typeof base !== "object" && item.getNext()); + } + + return false; + }, + + /** + * @param {ItemUnit} base + * @param {ItemUnit} rune + * @returns {boolean} + */ + socketItem: function (base, rune) { + if (!rune.toCursor()) return false; + + for (let i = 0; i < 3; i += 1) { + clickItem(sdk.clicktypes.click.item.Left, base.x, base.y, base.location); + + let tick = getTickCount(); + + while (getTickCount() - tick < 2000) { + if (!me.itemoncursor) { + delay(300); + + return true; + } + + delay(10); + } + } + + return false; + }, + + getScroll: function () { + let scroll = me.getItem(sdk.items.ScrollofTownPortal, sdk.items.mode.inStorage); // check if we already have the scroll + if (scroll) return scroll; + + let npc = Town.initNPC("Shop"); + if (!npc) return false; + + scroll = npc.getItem(sdk.items.ScrollofTownPortal); + + if (scroll) { + for (let i = 0; i < 3; i += 1) { + scroll.buy(true); + + if (me.getItem(sdk.items.ScrollofTownPortal)) { + break; + } + } + } + + me.cancel(); + + return me.getItem(sdk.items.ScrollofTownPortal, sdk.items.mode.inStorage); + }, + + makeRunewords: function () { + if (!Config.MakeRunewords) return false; + + while (true) { + this.buildLists(); + + let items = this.checkRunewords(); // get a runeword. format = [base, runes...] + + // can't make runewords - exit loop + if (!items) { + break; + } + + if (!Town.openStash()) return false; + + for (let i = 1; i < items.length; i += 1) { + this.socketItem(items[0], items[i]); + } + + const madeItem = items[0].fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, ""); + + console.log("ÿc4Runewords: ÿc0Made runeword: " + madeItem); + D2Bot.printToConsole("Made runeword: " + madeItem, sdk.colors.D2Bot.Green); + + if (NTIP.CheckItem(items[0], this.pickitEntries)) { + Item.logger("Runeword Kept", items[0]); + Item.logItem("Runeword Kept", items[0]); + } + } + + me.cancel(); + + this.rerollRunewords(); + + return true; + }, + + rerollRunewords: function () { + for (let i = 0; i < Config.Runewords.length; i += 1) { + let hel = me.getItem(sdk.items.runes.Hel, sdk.items.mode.inStorage); + if (!hel) return false; + + const [runeword, wantedBase, ethFlag] = Config.Runewords[i]; + let base = this.getBase(runeword, wantedBase, (ethFlag || 0), true); // get a bad runeword + + if (base) { + let scroll = this.getScroll(); + + // failed to get scroll or open stash most likely means we're stuck somewhere in town, so it's better to return false + if (!scroll || !Town.openStash() || !Cubing.emptyCube()) return false; + + // not a fatal error, if the cube can't be emptied, the func will return false on next cycle + if (!Storage.Cube.MoveTo(base) || !Storage.Cube.MoveTo(hel) || !Storage.Cube.MoveTo(scroll)) { + continue; + } + + // probably only happens on server crash + if (!Cubing.openCube()) return false; + + let baseRw = base.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, ""); + + console.log("ÿc4Runewords: ÿc0Rerolling runeword: " + baseRw); + D2Bot.printToConsole("Rerolling runeword: " + baseRw, sdk.colors.D2Bot.Green); + transmute(); + delay(500); + + // can't pull the item out = no space = fail + if (!Cubing.emptyCube()) return false; + } + } + + this.buildLists(); + + while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) { + me.cancel(); + delay(300); + } + + return true; + } +}; diff --git a/d2bs/kolbot/libs/core/Skill.js b/d2bs/kolbot/libs/core/Skill.js new file mode 100644 index 000000000..17029a7f4 --- /dev/null +++ b/d2bs/kolbot/libs/core/Skill.js @@ -0,0 +1,765 @@ +/** +* @filename Skill.js +* @author theBGuy +* @credit kolton +* @desc Skill library +* +*/ + +(function () { + const _SkillData = require("./GameData/SkillData"); + + /** + * @todo Move some of the precast functions here + */ + const Skill = { + usePvpRange: false, + /** @type {ChargedSkill[]} */ + charges: [], + needFloor: [ + sdk.skills.Blizzard, sdk.skills.Meteor, sdk.skills.Fissure, + sdk.skills.Volcano, sdk.skills.ShockWeb, sdk.skills.LeapAttack, sdk.skills.Hydra + ], + missileSkills: [ + sdk.skills.MagicArrow, sdk.skills.FireArrow, sdk.skills.ColdArrow, + sdk.skills.MultipleShot, sdk.skills.PoisonJavelin, sdk.skills.ExplodingArrow, + sdk.skills.LightningBolt, sdk.skills.IceArrow, sdk.skills.GuidedArrow, + sdk.skills.PlagueJavelin, sdk.skills.Strafe, sdk.skills.ImmolationArrow, + sdk.skills.FreezingArrow, sdk.skills.LightningFury, sdk.skills.ChargedBolt, + sdk.skills.IceBolt, sdk.skills.FireBolt, sdk.skills.Inferno, + sdk.skills.IceBlast, sdk.skills.FireBall, sdk.skills.Lightning, + sdk.skills.ChainLightning, sdk.skills.GlacialSpike, sdk.skills.FrozenOrb, + sdk.skills.Teeth, sdk.skills.BoneSpear, sdk.skills.BoneSpirit, + sdk.skills.HolyBolt, sdk.skills.FistoftheHeavens, sdk.skills.DoubleThrow, + sdk.skills.Firestorm, sdk.skills.MoltenBoulder, sdk.skills.ArcticBlast, + sdk.skills.Twister, sdk.skills.Tornado, sdk.skills.FireBlast + ], + + /** + * @param {number} skillId + * @returns {SkillDataInfo} + */ + get: function (skillId = -1) { + if (!_SkillData.has(skillId)) return null; + return _SkillData.get(skillId); + }, + + getClassSkillRange: function (classid = me.classid) { + switch (classid) { + case sdk.player.class.Amazon: + return [sdk.skills.MagicArrow, sdk.skills.LightningFury]; + case sdk.player.class.Sorceress: + return [sdk.skills.FireBolt, sdk.skills.ColdMastery]; + case sdk.player.class.Necromancer: + return [sdk.skills.AmplifyDamage, sdk.skills.Revive]; + case sdk.player.class.Paladin: + return [sdk.skills.Sacrifice, sdk.skills.Salvation]; + case sdk.player.class.Barbarian: + return [sdk.skills.Bash, sdk.skills.BattleCommand]; + case sdk.player.class.Druid: + return [sdk.skills.Raven, sdk.skills.Hurricane]; + case sdk.player.class.Assassin: + return [sdk.skills.FireBlast, sdk.skills.PhoenixStrike]; + default: + return [0, 0]; + } + }, + + /** + * @description Get items with charges + * @returns {boolean} + */ + getCharges: function () { + Skill.charges = []; + /** + * @constructor + * @param {Charge} charge + * @param {ItemUnit} unit + */ + function ChargedSkill (charge, unit) { + this.skill = charge.skill; + this.level = charge.level; + this.charges = charge.charges; + this.maxcharges = charge.maxcharges; + this.gid = unit.gid; + this.unit = copyUnit(unit); + } + + /** @param {ItemUnit} [item] */ + ChargedSkill.prototype.update = function (item) { + if (!item) { + item = me.getItem(-1, -1, this.gid); + } + if (!item) return; + let charges = item.getStat(-2)[sdk.stats.ChargedSkill]; + if (!(charges instanceof Array)) charges = [charges]; + let charge = charges.find(c => !!c && c.skill === this.skill); + if (charge) { + this.level = charge.level; + this.charges = charge.charges; + this.maxcharges = charge.maxcharges; + this.unit = copyUnit(item); + } + }; + + let item = me.getItem(-1, sdk.items.mode.Equipped); + + if (item) { + do { + let stats = item.getStat(-2); + if (!stats.hasOwnProperty(sdk.stats.ChargedSkill)) continue; + + /** @type {Array | Charge} */ + let charges = stats[sdk.stats.ChargedSkill]; + // simplfy calc by making it an array if it isn't already + if (!(charges instanceof Array)) charges = [charges]; + + for (let charge of charges) { + // handle wierd case were we get undefined charge + if (!charge || !charge.skill) continue; + if (Skill.charges.find(c => c.gid === item.gid && c.skill === charge.skill)) { + continue; + } + Skill.charges.push(new ChargedSkill(charge, item)); + } + } while (item.getNext()); + } + + return true; + }, + + // initialize our skill data + init: function () { + // reset check values + { + let [min, max] = Skill.getClassSkillRange(); + + for (let i = min; i <= max; i++) { + _SkillData.get(i).reset(); + } + } + if (me.expansion) { + // redo cta check + Precast.checkCTA(); + Skill.getCharges(); + } + + switch (me.classid) { + case sdk.player.class.Amazon: + break; + case sdk.player.class.Sorceress: + if (Config.UseColdArmor === true) { + Precast.coldArmor = (function () { + const _coldSkill = (id) => ({ skillId: id, level: me.getSkill(id, sdk.skills.subindex.SoftPoints) }); + let coldArmor = [ + _coldSkill(sdk.skills.ShiverArmor), + _coldSkill(sdk.skills.ChillingArmor), + _coldSkill(sdk.skills.FrozenArmor), + ].filter(skill => !!skill.level && skill.level > 0).sort((a, b) => b.level - a.level).first(); + return coldArmor !== undefined ? coldArmor.skillId : -1; + })(); + if (Precast.coldArmor > 0) { + Precast.skills.get(Precast.coldArmor).duration = this.getDuration(Precast.coldArmor); + } + } else if (Precast.skills.has(Config.UseColdArmor)) { + Precast.skills.get(Config.UseColdArmor).duration = this.getDuration(Config.UseColdArmor); + } + + break; + case sdk.player.class.Necromancer: + { + let bMax = me.getStat(sdk.stats.SkillBoneArmorMax); + bMax > 0 && (Precast.skills.get(sdk.skills.BoneArmor).max = bMax); + } + if (!!Config.Golem && Config.Golem !== "None") { + Config.Golem = (function () { + switch (Config.Golem) { + case 1: + case "Clay": + return sdk.skills.ClayGolem; + case 2: + case "Blood": + return sdk.skills.BloodGolem; + case 3: + case "Fire": + return sdk.skills.FireGolem; + default: + return Config.Golem; + } + })(); + } + break; + case sdk.player.class.Paladin: + // how to handle if someone manually equips a shield during game play, don't want to build entire item list if we don't need to + // maybe store gid of shield, would still require doing me.getItem(-1, 1, gid) everytime we wanted to cast but that's still less involved + // than getting every item we have and finding shield, for now keeping this. Checks during init if we have a shield or not + let shield = me.usingShield(); + if (shield) { + Precast.shieldGid = shield.gid; + } + Precast.skills.get(sdk.skills.HolyShield).duration = this.getDuration(sdk.skills.HolyShield); + + break; + case sdk.player.class.Barbarian: + if (Skill.canUse(sdk.skills.Shout)) { + Precast.skills.get(sdk.skills.Shout).duration = this.getDuration(sdk.skills.Shout); + } + if (Skill.canUse(sdk.skills.BattleOrders)) { + Precast.skills.get(sdk.skills.BattleOrders).duration = this.getDuration(sdk.skills.BattleOrders); + } + if (Skill.canUse(sdk.skills.BattleCommand)) { + Precast.skills.get(sdk.skills.BattleCommand).duration = this.getDuration(sdk.skills.BattleCommand); + } + + break; + case sdk.player.class.Druid: + { + let cMax = me.getStat(sdk.stats.SkillCycloneArmorMax); + cMax > 0 && (Precast.skills.get(sdk.skills.CycloneArmor).max = cMax); + } + if (!!Config.SummonAnimal && Config.SummonAnimal !== "None") { + Config.SummonAnimal = (function () { + switch (Config.SummonAnimal) { + case 1: + case "Spirit Wolf": + return sdk.skills.SummonSpiritWolf; + case 2: + case "Dire Wolf": + return sdk.skills.SummonDireWolf; + case 3: + case "Grizzly": + return sdk.skills.SummonGrizzly; + default: + return Config.SummonAnimal; + } + })(); + } + if (!!Config.SummonVine && Config.SummonVine !== "None") { + Config.SummonVine = (function () { + switch (Config.SummonVine) { + case 1: + case "Poison Creeper": + return sdk.skills.PoisonCreeper; + case 2: + case "Carrion Vine": + return sdk.skills.CarrionVine; + case 3: + case "Solar Creeper": + return sdk.skills.SolarCreeper; + default: + return Config.SummonVine; + } + })(); + } + if (!!Config.SummonSpirit && Config.SummonSpirit !== "None") { + Config.SummonSpirit = (function () { + switch (Config.SummonSpirit) { + case 1: + case "Oak Sage": + return sdk.skills.OakSage; + case 2: + case "Heart of Wolverine": + return sdk.skills.HeartofWolverine; + case 3: + case "Spirit of Barbs": + return sdk.skills.SpiritofBarbs; + default: + return Config.SummonSpirit; + } + })(); + } + break; + case sdk.player.class.Assassin: + if (!!Config.SummonShadow && !!Config.SummonShadow !== "None") { + Config.SummonShadow = (function () { + switch (Config.SummonShadow) { + case 1: + case "Warrior": + return sdk.skills.ShadowWarrior; + case 2: + case "Master": + return sdk.skills.ShadowMaster; + default: + return Config.SummonShadow; + } + })(); + } + break; + } + }, + + /** + * @param {number} skillId + * @returns {boolean} + */ + canUse: function (skillId = -1) { + if (!_SkillData.has(skillId)) return false; + if (skillId <= sdk.skills.LeftHandSwing) return true; + return _SkillData.get(skillId).have(); + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getDuration: function (skillId = -1) { + if (!_SkillData.has(skillId)) return 0; + return _SkillData.get(skillId).duration(); + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getMaxSummonCount: function (skillId) { + if (!_SkillData.has(skillId)) return 0; + return _SkillData.get(skillId).summonCount(); + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getSummonType: function (skillId) { + if (!_SkillData.has(skillId)) return 0; + return _SkillData.get(skillId).summonType; + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getRange: function (skillId) { + if (!_SkillData.has(skillId)) return 0; + return _SkillData.get(skillId).range(this.usePvpRange); + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getAoE: function (skillId) { + if (!_SkillData.has(skillId)) return 0; + return _SkillData.get(skillId).AoE(); + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getHand: function (skillId) { + if (!_SkillData.has(skillId)) return -1; + return _SkillData.get(skillId).hand; + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getState: function (skillId) { + if (!_SkillData.has(skillId)) return 0; + return _SkillData.get(skillId).state; + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getCharClass: function (skillId) { + if (!_SkillData.has(skillId)) return -1; + return _SkillData.get(skillId).charClass; + }, + + /** + * @param {number} skillId + * @returns {number} + */ + getSkillTab: function (skillId) { + if (!_SkillData.has(skillId)) return -1; + return _SkillData.get(skillId).skillTab; + }, + + /** + * Get mana cost of the skill (mBot) + * @param {number} skillId + * @returns {number} + */ + getManaCost: function (skillId) { + if (!_SkillData.has(skillId)) return 0; + if (skillId < sdk.skills.MagicArrow) return 0; + return _SkillData.get(skillId).manaCost(); + }, + + /** + * Timed skills + * @param {number} skillId + * @returns {boolean} + */ + isTimed: function (skillId) { + if (!_SkillData.has(skillId)) return false; + return _SkillData.get(skillId).timed; + }, + + /** + * Skills that cn be cast in town + * @param {number} skillId + * @returns {boolean} + */ + townSkill: function (skillId = -1) { + if (!_SkillData.has(skillId)) return false; + return _SkillData.get(skillId).townSkill; + }, + + /** + * @param {number} skillId + * @returns {boolean} + */ + missileSkill: function (skillId = -1) { + if (!_SkillData.has(skillId)) return false; + return _SkillData.get(skillId).missleSkill; + }, + + /** + * @param {number} skillId + * @returns {boolean} + */ + isAura: function (skillId = -1) { + if (!_SkillData.has(skillId)) return false; + return _SkillData.get(skillId).aura; + }, + + /** + * Wereform skill check + * @param {number} skillId + * @returns {number} + */ + wereFormCheck: function (skillId) { + // we don't even have the skills to transform or we aren't transformed - add handler for wereform given by an item that is on switch + if (!Skill.canUse(sdk.skills.Werewolf) && !Skill.canUse(sdk.skills.Werebear)) return true; + const shared = new Set([ + sdk.skills.Attack, sdk.skills.Kick, + sdk.skills.Raven, sdk.skills.Werewolf, + sdk.skills.Werebear, sdk.skills.PoisonCreeper, + sdk.skills.OakSage, sdk.skills.SpiritWolf, + sdk.skills.CarrionVine, sdk.skills.HeartofWolverine, + sdk.skills.SummonDireWolf, sdk.skills.FireClaws, + sdk.skills.SolarCreeper, sdk.skills.Hunger, + sdk.skills.SpiritofBarbs, sdk.skills.SummonGrizzly, sdk.skills.Armageddon + ]); + const wolfOnly = new Set([sdk.skills.FeralRage, sdk.skills.Rabies, sdk.skills.Fury]); + const bearOnly = new Set([sdk.skills.Maul, sdk.skills.ShockWave]); + + let wolfForm = me.getState(sdk.states.Wearwolf); + if (wolfForm) return shared.has(skillId) || wolfOnly.has(skillId); + + let bearForm = me.getState(sdk.states.Wearbear); + if (bearForm) return shared.has(skillId) || bearOnly.has(skillId); + + // if we are not in either form, we can use any skill + return true; + }, + + /** + * Check whether this skills is even usable on the target + * @param {number} skillId + * @param {Monster} unit + */ + usableOn: function (skillId, unit) { + if (!unit || !unit.type) return false; + + switch (skillId) { + case sdk.skills.SlowMissiles: + return !unit.isPrimeEvil; + case sdk.skills.Confuse: + case sdk.skills.Attract: + return unit.scareable; + case sdk.skills.DimVision: + if (unit.isSpecial) return false; + if ([ + sdk.monsters.OblivionKnight1, + sdk.monsters.OblivionKnight2, + sdk.monsters.OblivionKnight3 + ].includes(unit.classid)) { + return false; + } + return true; + default: + return true; + } + }, + + // Put a skill on desired slot + setSkill: function (skillId, hand, item) { + const checkHand = (hand === sdk.skills.hand.Right + ? sdk.skills.get.RightId + : sdk.skills.get.LeftId); + // Check if the skill is already set + if (me.getSkill(checkHand) === skillId) { + return true; + } + if (!item && !Skill.canUse(skillId)) return false; + + // Charged skills must be cast from right hand + if (hand === undefined || hand === sdk.skills.hand.RightShift || item) { + if (item && hand !== sdk.skills.hand.Right) { + console.warn("[ÿc9Warningÿc0] charged skills must be cast from right hand"); + } + hand = sdk.skills.hand.Right; + } + + return (me.setSkill(skillId, hand, item)); + }, + + // Change into werewolf or werebear + shapeShift: function (mode) { + const [skill, state] = (() => { + switch (mode.toString().toLowerCase()) { + case "0": + return [-1, -1]; + case "1": + case "werewolf": + return [sdk.skills.Werewolf, sdk.states.Wearwolf]; + case "2": + case "werebear": + return [sdk.skills.Werebear, sdk.states.Wearbear]; + default: + throw new Error("shapeShift: Invalid parameter"); + } + })(); + + // don't have wanted skill + if (!Skill.canUse(skill)) return false; + // already in wanted state + if (me.getState(state)) return true; + const _stateCheck = function () { + return me.getState(state); + }; + + const slot = Attack.getPrimarySlot(); + me.switchWeapons(Precast.getBetterSlot(skill)); + + try { + for (let i = 0; i < 3; i += 1) { + Skill.cast(skill, sdk.skills.hand.Right); + + if (Misc.poll(_stateCheck, 2000, 50)) { + return true; + } + } + + return false; + } finally { + me.weaponswitch !== slot && me.switchWeapons(slot); + } + }, + + // Change back to human shape + unShift: function () { + const [state, skill] = me.getState(sdk.states.Wearwolf) + ? [sdk.states.Wearwolf, sdk.skills.Werewolf] + : me.getState(sdk.states.Wearbear) + ? [sdk.states.Wearbear, sdk.skills.Werebear] + : [0, 0]; + if (!state) return true; + const _stateCheck = function () { + return !me.getState(state); + }; + for (let i = 0; i < 3; i++) { + Skill.cast(skill); + + if (Misc.poll(_stateCheck, 2000, 50)) { + return true; + } + } + + return false; + }, + + /** + * @param {Unit} unit + * @returns {boolean} + */ + useTK: function (unit) { + try { + if (!unit || !Skill.canUse(sdk.skills.Telekinesis) + || typeof unit !== "object" || unit.type !== sdk.unittype.Object + || unit.name.toLowerCase() === "dummy" + || (String.isEqual(unit.name, "portal") + && !me.inTown && unit.classid !== sdk.objects.ArcaneSanctuaryPortal) + || [ + sdk.objects.RedPortalToAct4, sdk.objects.WorldstonePortal, + sdk.objects.RedPortal, sdk.objects.RedPortalToAct5 + ].includes(unit.classid)) { + return false; + } + + return me.inTown || (me.mpPercent > 25); + } catch (e) { + return false; + } + }, + + // Cast a skill on self, Unit or coords + cast: function (skillId, hand, x, y, item, weaponSlot = -1) { + if (skillId === undefined) throw new Error("Unit.cast: Must supply a skill ID"); + const switchWeapons = weaponSlot > -1; + try { + if (switchWeapons && me.weaponswitch !== weaponSlot) { + me.switchWeapons(weaponSlot); + } + switch (true) { + case me.inTown && !this.townSkill(skillId): + case !item && (this.getManaCost(skillId) > me.mp || !this.canUse(skillId)): + case !this.wereFormCheck(skillId): + return false; + } + + hand === undefined && (hand = this.getHand(skillId)); + x === undefined && (x = me.x); + y === undefined && (y = me.y); + + // Check mana cost, charged skills don't use mana + if (!item && this.getManaCost(skillId) > me.mp) { + // Maybe delay on ALL skills that we don't have enough mana for? + if (Config.AttackSkill + .concat([sdk.skills.StaticField, sdk.skills.Teleport]) + .concat(Config.LowManaSkill).includes(skillId)) { + delay(300); + } + + return false; + } + + if (skillId === sdk.skills.Teleport) { + if (typeof x === "number") { + const orgDist = [x, y].distance; + if (Packet.teleport(x, y)) { + return Misc.poll(function () { + return [x, y].distance < orgDist; + }, 300, 25); + } + } + } + + if (!this.setSkill(skillId, hand, item)) return false; + + if (Config.PacketCasting > 1) { + if (typeof x === "number") { + Packet.castSkill(hand, x, y); + } else if (typeof x === "object") { + Packet.unitCast(hand, x); + } + delay(250); + } else { + let [clickType, shift] = (function () { + switch (hand) { + case sdk.skills.hand.Left: // Left hand + Shift + return [sdk.clicktypes.click.map.LeftDown, sdk.clicktypes.shift.Shift]; + case sdk.skills.hand.LeftNoShift: // Left hand + No Shift + return [sdk.clicktypes.click.map.LeftDown, sdk.clicktypes.shift.NoShift]; + case sdk.skills.hand.RightShift: // Right hand + Shift + return [sdk.clicktypes.click.map.RightDown, sdk.clicktypes.shift.Shift]; + case sdk.skills.hand.Right: // Right hand + No Shift + default: + return [sdk.clicktypes.click.map.RightDown, sdk.clicktypes.shift.NoShift]; + } + })(); + + for (let n = 0; n < 3; n += 1) { + typeof x === "object" + ? clickMap(clickType, shift, x) + : clickMap(clickType, shift, x, y); + delay(20); + typeof x === "object" + ? clickMap(clickType + 2, shift, x) + : clickMap(clickType + 2, shift, x, y); + + if (Misc.poll(function () { + return me.attacking; + }, 200, 20)) { + break; + } + } + + while (me.attacking) { + delay(10); + } + } + + // account for lag, state 121 doesn't kick in immediately + if (this.isTimed(skillId)) { + Misc.poll(function () { + return ( + me.skillDelay + || me.mode === sdk.player.mode.GettingHit + || me.mode === sdk.player.mode.Blocking + ); + }, 100, 10); + } + + return true; + } finally { + if (switchWeapons) { + me.switchWeapons(Attack.getPrimarySlot()); + } + } + }, + + /** + * Basic use of charged skill casting + * @param {number} skillId + * @param {Unit | { x: number, y: number }} unit + * @returns {boolean} + */ + castCharges: function (skillId, unit) { + if (!Skill.charges.length) return false; + // TODO: better validity check - for now preventing spamming slow missiles on bosses where it does't work + if (unit && unit.hasOwnProperty("classid") && !Skill.usableOn(skillId, unit)) { + return false; + } + const charge = Skill.charges + .filter(function (c) { + return c.skill === skillId && c.charges > 0; + }) + .sort(function (a, b) { + return b.level - a.level; + }).find(function (charge) { + return me.getItem(-1, sdk.items.mode.Equipped, charge.gid); + }); + if (!charge) return false; + const item = me.getItem(-1, sdk.items.mode.Equipped, charge.gid); + if (!item) return false; + if (!unit) unit = me; + const weaponSwitch = me.weaponswitch; + if ([sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary].includes(item.bodylocation)) { + me.switchWeapons(weaponSwitch ^ 1); + } + try { + return item.castChargedSkill(skillId, unit.x, unit.y); + } catch (e) { + console.error(e); + // maybe rebuild list? + // Skill.getCharges(); + return false; + } finally { + if (weaponSwitch !== me.weaponswitch) { + me.switchWeapons(weaponSwitch); + } + if (item) { + charge.update(item); + } + } + }, + }; + + Object.defineProperties(Skill, { + haveTK: { + get: function () { + return Skill.canUse(sdk.skills.Telekinesis); + }, + }, + }); + + // export to the global scope + global.Skill = Skill; +})(); diff --git a/d2bs/kolbot/libs/core/Storage.js b/d2bs/kolbot/libs/core/Storage.js new file mode 100644 index 000000000..a644c5cb8 --- /dev/null +++ b/d2bs/kolbot/libs/core/Storage.js @@ -0,0 +1,740 @@ +/* eslint-disable max-len */ +/** +* @filename Storage.js +* @author McGod, kolton, esd1, theBGuy +* @desc Manage our storage space, belt, stash, cube, inventory +* +*/ + +(function () { + /** + * @constructor + * @param {string} name - container name + * @param {number} width - container width + * @param {number} height - container height + * @param {number} location - container location + */ + function Container (name, width, height, location) { + this.name = name; + this.width = width; + this.height = height; + this.location = location; + /** @type {number[][]} */ + this.buffer = []; + /** @type {ItemUnit[]} */ + this.itemList = []; + this.openPositions = this.height * this.width; + + for (let h = 0; h < this.height; h += 1) { + this.buffer.push([]); + + for (let w = 0; w < this.width; w += 1) { + this.buffer[h][w] = 0; + } + } + } + + /** + * @param {ItemUnit} item + */ + Container.prototype.Mark = function (item) { + let x, y; + + // Make sure it is in this container. + if (item.location !== this.location + || (item.mode !== sdk.items.mode.inStorage && item.mode !== sdk.items.mode.inBelt)) { + return false; + } + + // Mark item in buffer. + for (x = item.x; x < (item.x + item.sizex); x += 1) { + for (y = item.y; y < (item.y + item.sizey); y += 1) { + this.buffer[y][x] = this.itemList.length + 1; + this.openPositions -= 1; + } + } + + // Add item to list. + this.itemList.push(copyUnit(item)); + + return true; + }; + + /** + * @param {ItemUnit} item + * @param {number[][]} baseRef + */ + Container.prototype.IsLocked = function (item, baseRef) { + let h, w; + let reference = baseRef.slice(0); + + // Make sure it is in this container. + if (item.mode !== sdk.items.mode.inStorage || item.location !== this.location) { + return false; + } + + // Make sure the item is ours + if (!item.getParent() || item.getParent().type !== me.type || item.getParent().gid !== me.gid) { + return false; + } + + //Insure valid reference. + if (typeof (reference) !== "object" + || reference.length !== this.buffer.length || reference[0].length !== this.buffer[0].length) { + throw new Error("Storage.IsLocked: Invalid inventory reference"); + } + + try { + // Check if the item lies in a locked spot. + for (h = item.y; h < (item.y + item.sizey); h += 1) { + for (w = item.x; w < (item.x + item.sizex); w += 1) { + if (reference[h][w] === 0) { + return true; + } + } + } + } catch (e2) { + throw new Error("Storage.IsLocked error! Item info: " + item.name + " " + item.y + " " + item.sizey + " " + item.x + " " + item.sizex + " " + item.mode + " " + item.location); + } + + return false; + }; + + Container.prototype.Reset = function () { + for (let h = 0; h < this.height; h += 1) { + for (let w = 0; w < this.width; w += 1) { + this.buffer[h][w] = 0; + } + } + + this.itemList = []; + this.openPositions = this.height * this.width; + + return true; + }; + + /** + * @param {string} name + */ + Container.prototype.cubeSpot = function (name) { + if (name !== "Stash") return true; + + let cube = me.getItem(sdk.quest.item.Cube); + if (!cube) return false; + + // Cube is in correct location + if (cube && cube.isInStash && cube.x === 0 && cube.y === 0) { + return true; + } + + // If cube is in locked inventory spot, don't touch it + if (cube && cube.isInInventory && Storage.Inventory.IsLocked(cube, Config.Inventory)) { + return true; + } + + let makeCubeSpot = this.MakeSpot(cube, { x: 0, y: 0 }, true); // NOTE: passing these in buffer order [h/x][w/y] + + if (makeCubeSpot) { + // this item cannot be moved + if (makeCubeSpot === -1) return false; + // we couldnt move the item + if (!this.MoveToSpot(cube, makeCubeSpot.y, makeCubeSpot.x)) return false; + } + + return true; + }; + + /** + * @param {ItemUnit} item + */ + Container.prototype.IsPossibleToFit = function (item) { + if (!item) return false; + // only for the inventory as this has to deal with locked spots + if (this.name !== "Inventory") return true; + for (let y = 0; y < this.width - (item.sizex - 1); y++) { + Loop: + for (let x = 0; x < this.height - (item.sizey - 1); x++) { + // If spot is locked move on + if (Config.Inventory[x][y] === 0) continue; + + // Loop the item size to make sure we can fit it in non locked spots. + for (let nx = 0; nx < item.sizey; nx++) { + for (let ny = 0; ny < item.sizex; ny++) { + if (Config.Inventory[x + nx][y + ny] === 0) continue Loop; + } + } + + return true; + } + } + return false; + }; + + /** + * @param {ItemUnit} item + */ + Container.prototype.CanFit = function (item) { + return (!!this.FindSpot(item)); + }; + + /** + * @param {number[]} itemIdsLeft + * @param {number[]} itemIdsRight + */ + Container.prototype.SortItems = function (itemIdsLeft, itemIdsRight) { + Storage.Reload(); + + this.cubeSpot(this.name); + + itemIdsLeft === undefined && (itemIdsLeft = Config.SortSettings.ItemsSortedFromLeft); + itemIdsRight === undefined && (itemIdsRight = Config.SortSettings.ItemsSortedFromRight); + + let x, y, item, nPos; + + for (y = this.width - 1; y >= 0; y--) { + for (x = this.height - 1; x >= 0; x--) { + + delay(1); + + if (this.buffer[x][y] === 0) { + continue; // nothing on this spot + } + + item = this.itemList[this.buffer[x][y] - 1]; + + if (item.classid === sdk.quest.item.Cube + && ( + (item.isInInventory && Storage.Inventory.IsLocked(item, Config.Inventory)) + || (item.isInStash && item.x === 0 && item.y === 0) + )) { + continue; // dont touch the cube + } + + let [ix, iy] = [item.y, item.x]; // x and y are backwards! + + if (this.location !== item.location) { + D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a non-storage item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold); + continue; // dont try to touch non-storage items | TODO: prevent non-storage items from getting this far + } + + if (this.location === sdk.storage.Inventory && this.IsLocked(item, Config.Inventory)) { + continue; // locked spot / item + } + + if (ix < x || iy < y) { + continue; // not top left part of item + } + + if (item.type !== sdk.unittype.Item) { + D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a non-item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold); + continue; // dont try to touch non-items | TODO: prevent non-items from getting this far + } + + if (item.mode === sdk.items.mode.onGround) { + D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a ground item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold); + continue; // dont try to touch ground items | TODO: prevent ground items from getting this far + } + + // always sort stash left-to-right + if (this.location === sdk.storage.Stash) { + nPos = this.FindSpot(item); + } else if (this.location === sdk.storage.Inventory && ((!itemIdsLeft && !itemIdsRight) || !itemIdsLeft || itemIdsRight.includes(item.classid) || itemIdsLeft.indexOf(item.classid) === -1)) { + // sort from right by default or if specified + nPos = this.FindSpot(item, true, false, Config.SortSettings.ItemsSortedFromRightPriority); + } else if (this.location === sdk.storage.Inventory && itemIdsRight.indexOf(item.classid) === -1 && itemIdsLeft.includes(item.classid)) { + // sort from left only if specified + nPos = this.FindSpot(item, false, false, Config.SortSettings.ItemsSortedFromLeftPriority); + } + + // skip if no better spot found + if (!nPos || (nPos.x === ix && nPos.y === iy)) { + continue; + } + + if (!this.MoveToSpot(item, nPos.y, nPos.x)) { + continue; // we couldnt move the item + } + + // We moved an item so reload & restart + Storage.Reload(); + y = this.width - 0; + + break; // Loop again from begin + } + } + + // this.Dump(); + + return true; + }; + + /** + * @param {ItemUnit | { sizex: number, sizey: number }} item + * @param {boolean} reverseX + * @param {boolean} reverseY + * @param {number[]} priorityClassIds + */ + Container.prototype.FindSpot = function (item, reverseX, reverseY, priorityClassIds) { + // Make sure it's a valid item + if (!item) return false; + + if (item.sizex && item.sizey && !(item instanceof Unit)) { + // fake item we are checking if we can fit a certain sized item so mock some props to it + item.gid = -1; + item.classid = -1; + item.quality = -1; + item.gfx = -1; + } + + /** + * @todo review this to see why it sometimes fails when there is actually enough room + */ + + let x, y, nx, ny, makeSpot; + let xDir = 1, yDir = 1; + + let startX = 0; + let startY = 0; + let endX = this.width - (item.sizex - 1); + let endY = this.height - (item.sizey - 1); + + if (reverseX) { // right-to-left + startX = endX - 1; + endX = -1; // stops at 0 + xDir = -1; + } + + if (reverseY) { // bottom-to-top + startY = endY - 1; + endY = -1; // stops at 0 + yDir = -1; + } + + Storage.Reload(); + + //Loop buffer looking for spot to place item. + for (y = startX; y !== endX; y += xDir) { + Loop: + for (x = startY; x !== endY; x += yDir) { + //Check if there is something in this spot. + if (this.buffer[x][y] > 0) { + + // TODO: add makespot logic here. priorityClassIds should only be used when sorting -- in town, where it's safe! + // TODO: collapse this down to just a MakeSpot(item, location) call, and have MakeSpot do the priority checks right at the top + let bufferItemClass = this.itemList[this.buffer[x][y] - 1].classid; + let bufferItemGfx = this.itemList[this.buffer[x][y] - 1].gfx; + let bufferItemQuality = this.itemList[this.buffer[x][y] - 1].quality; + + if (Config.SortSettings.PrioritySorting && priorityClassIds && priorityClassIds.includes(item.classid) + && !this.IsLocked(this.itemList[this.buffer[x][y] - 1], Config.Inventory) // don't try to make a spot by moving locked items! TODO: move this to the start of loop + && (priorityClassIds.indexOf(bufferItemClass) === -1 + || priorityClassIds.indexOf(item.classid) < priorityClassIds.indexOf(bufferItemClass))) { // item in this spot needs to move! + makeSpot = this.MakeSpot(item, { x: x, y: y }); // NOTE: passing these in buffer order [h/x][w/y] + + if (item.classid !== bufferItemClass // higher priority item + || (item.classid === bufferItemClass && item.quality > bufferItemQuality) // same class, higher quality item + || (item.classid === bufferItemClass && item.quality === bufferItemQuality && item.gfx > bufferItemGfx) // same quality, higher graphic item + || (Config.AutoEquip && item.classid === bufferItemClass && item.quality === bufferItemQuality && item.gfx === bufferItemGfx // same graphic, higher tier item + && NTIP.GetTier(item) > NTIP.GetTier(this.itemList[this.buffer[x][y] - 1]))) { + makeSpot = this.MakeSpot(item, { x: x, y: y }); // NOTE: passing these in buffer order [h/x][w/y] + + if (makeSpot) { + // this item cannot be moved + if (makeSpot === -1) return false; + + return makeSpot; + } + } + } + + if (item.gid === undefined) return false; + + // ignore same gid + if (item.gid !== this.itemList[this.buffer[x][y] - 1].gid ) { + continue; + } + } + + // Loop the item size to make sure we can fit it. + for (nx = 0; nx < item.sizey; nx += 1) { + for (ny = 0; ny < item.sizex; ny += 1) { + if (this.buffer[x + nx][y + ny]) { + // ignore same gid + if (item.gid !== this.itemList[this.buffer[x + nx][y + ny] - 1].gid) { + continue Loop; + } + } + } + } + + return ({ x: x, y: y }); + } + } + + return false; + }; + + /** + * @param {ItemUnit} item + * @param {{x: number, y: number}} location + * @param {boolean} force + */ + Container.prototype.MakeSpot = function (item, location, force) { + let x, y, endx, endy, tmpLocation; + let [itemsToMove, itemsMoved] = [[], []]; + // TODO: test the scenario where all possible items have been moved, but this item still can't be placed + // e.g. if there are many LCs in an inventory and the spot for a GC can't be freed up without + // moving other items that ARE NOT part of the position desired + + // Make sure it's a valid item and item is in a priority sorting list + if (!item || !item.classid + || (Config.SortSettings.ItemsSortedFromRightPriority.indexOf(item.classid) === -1 + && Config.SortSettings.ItemsSortedFromLeftPriority.indexOf(item.classid) === -1 + && !force)) { + return false; // only continue if the item is in the priority sort list + } + + // Make sure the item could even fit at the desired location + if (!location //|| !(location.x >= 0) || !(location.y >= 0) + || ((location.y + (item.sizex - 1)) > (this.width - 1)) + || ((location.x + (item.sizey - 1)) > (this.height - 1))) { + return false; // location invalid or item could not ever fit in the location + } + + Storage.Reload(); + + // Do not continue if the container doesn't have enough openPositions. + // TODO: esd1 - this could be extended to use Stash for moving things if inventory is too tightly packed + if (item.sizex * item.sizey > this.openPositions) { + return -1; // return a non-false answer to FindSpot so it doesn't keep looking + } + + endy = location.y + (item.sizex - 1); + endx = location.x + (item.sizey - 1); + + // Collect a list of all the items in the way of using this position + for (x = location.x; x <= endx; x += 1) { // item height + for (y = location.y; y <= endy; y += 1) { // item width + if ( this.buffer[x][y] === 0 ) { + continue; // nothing to move from this spot + } else if (item.gid === this.itemList[this.buffer[x][y] - 1].gid) { + continue; // ignore same gid + } else { + itemsToMove.push(copyUnit(this.itemList[this.buffer[x][y] - 1])); // track items that need to move + } + } + } + + // Move any item(s) out of the way + if (itemsToMove.length) { + for (let i = 0; i < itemsToMove.length; i++) { + let reverseX = !(Config.SortSettings.ItemsSortedFromRight.includes(item.classid)); + tmpLocation = this.FindSpot(itemsToMove[i], reverseX, false); + // D2Bot.printToConsole(itemsToMove[i].name + " moving from " + itemsToMove[i].x + "," + itemsToMove[i].y + " to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold); + + if (this.MoveToSpot(itemsToMove[i], tmpLocation.y, tmpLocation.x)) { + // D2Bot.printToConsole(itemsToMove[i].name + " moved to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold); + itemsMoved.push(copyUnit(itemsToMove[i])); + Storage.Reload(); // success on this item, reload! + delay(1); // give reload a moment of time to avoid moving the same item twice + } else { + D2Bot.printToConsole(itemsToMove[i].name + " failed to move to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold); + + return false; + } + } + } + + //D2Bot.printToConsole("MakeSpot success! " + item.name + " can now be placed at " + location.y + "," + location.x, sdk.colors.D2Bot.Gold); + return ({ x: location.x, y: location.y }); + }; + + /** + * @param {ItemUnit} item + * @param {number} mX + * @param {number} mY + */ + Container.prototype.MoveToSpot = function (item, mX, mY) { + let cItem, cube; + + // handle opening cube + if (this.location === sdk.storage.Cube) { + cube = me.getItem(sdk.quest.item.Cube); + if (!cube) return false; + if ((cube.isInStash || item.isInStash) && !getUIFlag(sdk.uiflags.Stash) && !Town.openStash()) { + return false; + } + } + + if (item.location === sdk.storage.Cube/* && this.location === sdk.storage.Stash && !Storage.Inventory.MoveTo(item) */) { + if (!getUIFlag(sdk.uiflags.Cube) && !Cubing.openCube()) return false; + // Cube -> Stash, must place item in inventory first + if (this.location === sdk.storage.Stash && !Storage.Inventory.MoveTo(item)) return false; + } + + // Can't deal with items on ground! + if (item.mode === sdk.items.mode.onGround) return false; + // Item already on the cursor. + if (me.itemoncursor && item.mode !== sdk.items.mode.onCursor) return false; + + // Make sure stash is open + if (this.location === sdk.storage.Stash && !Town.openStash()) return false; + if (this.location === sdk.storage.Inventory && item.location === sdk.storage.Stash && !Town.openStash()) { + return false; + } + + const [orgX, orgY, orgLoc] = [item.x, item.y, item.location]; + const moveItem = (x, y, location) => { + for (let n = 0; n < 5; n += 1) { + switch (location) { + case sdk.storage.Belt: + cItem = Game.getCursorUnit(); + cItem !== null && sendPacket(1, sdk.packets.send.ItemToBelt, 4, cItem.gid, 4, y); + + break; + case sdk.storage.Inventory: + sendPacket(1, sdk.packets.send.ItemToBuffer, 4, item.gid, 4, x, 4, y, 4, 0x00); + + break; + case sdk.storage.Cube: + cItem = Game.getCursorUnit(); + cube = me.getItem(sdk.quest.item.Cube); + if (cItem !== null && cube !== null) { + sendPacket(1, sdk.packets.send.ItemToCube, 4, cItem.gid, 4, cube.gid); + } + + break; + case sdk.storage.Stash: + sendPacket(1, sdk.packets.send.ItemToBuffer, 4, item.gid, 4, x, 4, y, 4, 0x04); + + break; + default: + clickItemAndWait(sdk.clicktypes.click.item.Left, x, y, location); + + break; + } + + let nDelay = getTickCount(); + + while ((getTickCount() - nDelay) < Math.max(1000, me.ping * 2 + 200)) { + if (!me.itemoncursor) return true; + + delay(10 + me.ping); + } + } + + return false; + }; + + if (Packet.itemToCursor(item)) { + if (moveItem(mX, mY, this.location)) return true; + moveItem(orgX, orgY, orgLoc) && console.debug("Failed to move " + item.fname + " to " + mX + "/" + mY); + } + + return false; + }; + + /** + * @param {ItemUnit} item + */ + Container.prototype.MoveTo = function (item) { + let nPos; + + try { + //Can we even fit it in here? + nPos = this.FindSpot(item); + if (!nPos) return false; + + return this.MoveToSpot(item, nPos.y, nPos.x); + } catch (e) { + console.log("Storage.Container.MoveTo caught error : " + e + " - " + e.toSource()); + + return false; + } + }; + + Container.prototype.Dump = function () { + let x, y, string; + + if (this.UsedSpacePercent() > 60) { + for (x = 0; x < this.height; x += 1) { + string = ""; + + for (y = 0; y < this.width; y += 1) { + string += (this.buffer[x][y] > 0) ? "ÿc1x" : "ÿc0o"; + } + + console.log(string); + } + } + + console.log("ÿc9Storageÿc0: " + this.name + " has used " + this.UsedSpacePercent().toFixed(2) + "% of its total space"); + }; + + Container.prototype.UsedSpacePercent = function () { + let usedSpace = 0; + let totalSpace = this.height * this.width; + + Storage.Reload(); + + for (let x = 0; x < this.height; x += 1) { + for (let y = 0; y < this.width; y += 1) { + if (this.buffer[x][y] > 0) { + usedSpace += 1; + } + } + } + + return usedSpace * 100 / totalSpace; + }; + + /** + * @param {number[][]} baseRef + */ + Container.prototype.Compare = function (baseRef) { + let h, w, n, item, itemList, reference; + + Storage.Reload(); + + try { + itemList = []; + reference = baseRef.slice(0, baseRef.length); + + //Insure valid reference. + if (typeof (reference) !== "object" || reference.length !== this.buffer.length || reference[0].length !== this.buffer[0].length) { + throw new Error("Unable to compare different containers."); + } + + for (h = 0; h < this.height; h += 1) { + Loop: + for (w = 0; w < this.width; w += 1) { + item = this.itemList[this.buffer[h][w] - 1]; + + if (!item) { + continue; + } + + for (n = 0; n < itemList.length; n += 1) { + if (itemList[n].gid === item.gid) { + continue Loop; + } + } + + //Check if the buffers changed and the current buffer has an item there. + if (this.buffer[h][w] > 0 && reference[h][w] > 0) { + itemList.push(copyUnit(item)); + } + } + } + + return itemList; + } catch (e) { + return false; + } + }; + + Container.prototype.toSource = function () { + return this.buffer.toSource(); + }; + + /** + * @type {storage} Storage + */ + const Storage = new function () { + this.Init = () => { + this.StashY = me.classic ? 4 : Config.SortSettings.PlugYStash ? 10 : 8; + this.Inventory = new Container("Inventory", 10, 4, 3); + this.TradeScreen = new Container("Inventory", 10, 4, 5); + this.Stash = new Container("Stash", (Config.SortSettings.PlugYStash ? 10 : 6), this.StashY, 7); + this.Belt = new Container("Belt", 4 * this.BeltSize(), 1, 2); + + /** + * @description Return column status (needed potions in each column) + * @param {0 | 1 | 2 | 3 | 4} beltSize + * @returns {[number, number, number, number]} + */ + this.Belt.checkColumns = function (beltSize) { + beltSize === undefined && (beltSize = this.width / 4); + let col = [beltSize, beltSize, beltSize, beltSize]; + let pot = me.getItem(-1, sdk.items.mode.inBelt); + + // No potions + if (!pot) return col; + + do { + col[pot.x % 4] -= 1; + } while (pot.getNext()); + + return col; + }; + + this.Cube = new Container("Horadric Cube", 3, 4, 6); + this.InvRef = []; + + this.Reload(); + }; + + this.BeltSize = function () { + let item = me.getItem(-1, sdk.items.mode.Equipped); // get equipped item + if (!item) return 1; // nothing equipped + + do { + if (item.bodylocation === sdk.body.Belt) { + switch (item.code) { + case "lbl": // sash + case "vbl": // light belt + return 2; + case "mbl": // belt + case "tbl": // heavy belt + return 3; + default: // everything else + return 4; + } + } + } while (item.getNext()); + + return 1; // no belt + }; + + this.Reload = function () { + this.Inventory.Reset(); + this.Stash.Reset(); + this.Belt.Reset(); + this.Cube.Reset(); + this.TradeScreen.Reset(); + + let item = me.getItem(); + if (!item) return false; + + do { + switch (item.location) { + case sdk.storage.Inventory: + this.Inventory.Mark(item); + + break; + case sdk.storage.TradeWindow: + this.TradeScreen.Mark(item); + + break; + case sdk.storage.Belt: + this.Belt.Mark(item); + + break; + case sdk.storage.Cube: + this.Cube.Mark(item); + + break; + case sdk.storage.Stash: + this.Stash.Mark(item); + + break; + } + } while (item.getNext()); + + return true; + }; + }; + + // export to global scope + global.Storage = Storage; +})(); diff --git a/d2bs/kolbot/libs/core/Town.js b/d2bs/kolbot/libs/core/Town.js new file mode 100644 index 000000000..d5b67d67e --- /dev/null +++ b/d2bs/kolbot/libs/core/Town.js @@ -0,0 +1,2222 @@ +/** +* @filename Town.js +* @author kolton, theBGuy +* @desc do town chores like buying, selling and gambling +* +*/ + +const Town = { + telekinesis: true, + sellTimer: getTickCount(), // shop speedup test + lastChores: 0, + + act: { + 1: { + spot: (function () { + const _spot = {}; + _spot.stash = [0, 0]; + _spot[NPC.Warriv] = [0, 0]; + _spot[NPC.Cain] = [0, 0]; + _spot[NPC.Kashya] = [0, 0]; + _spot[NPC.Akara] = [0, 0]; + _spot[NPC.Charsi] = [0, 0]; + _spot[NPC.Gheed] = [0, 0]; + _spot.portalspot = [0, 0]; + _spot.waypoint = [0, 0]; + _spot.initialized = false; + return _spot; + })(), + }, + 2: { + spot: (function () { + const _spot = {}; + _spot[NPC.Fara] = [5124, 5082]; + _spot[NPC.Cain] = [5124, 5082]; + _spot[NPC.Lysander] = [5118, 5104]; + _spot[NPC.Greiz] = [5033, 5053]; + _spot[NPC.Elzix] = [5032, 5102]; + _spot[NPC.Jerhyn] = [5088, 5153]; + _spot[NPC.Meshif] = [5205, 5058]; + _spot[NPC.Drognan] = [5097, 5035]; + _spot[NPC.Atma] = [5137, 5060]; + _spot[NPC.Warriv] = [5152, 5201]; + _spot.palace = [5088, 5153]; + _spot.sewers = [5221, 5181]; + _spot.portalspot = [5168, 5060]; + _spot.stash = [5124, 5076]; + _spot.waypoint = [5070, 5083]; + _spot.initialized = true; + return _spot; + })(), + }, + 3: { + spot: (function () { + const _spot = {}; + _spot[NPC.Meshif] = [5118, 5168]; + _spot[NPC.Hratli] = [5223, 5048, 5127, 5172]; + _spot[NPC.Ormus] = [5129, 5093]; + _spot[NPC.Asheara] = [5043, 5093]; + _spot[NPC.Alkor] = [5083, 5016]; + _spot[NPC.Cain] = [5148, 5066]; + _spot.stash = [5144, 5059]; + _spot.portalspot = [5150, 5063]; + _spot.waypoint = [5158, 5050]; + _spot.initialized = true; + return _spot; + })(), + }, + 4: { + spot: (function () { + const _spot = {}; + _spot[NPC.Cain] = [5027, 5027]; + _spot[NPC.Halbu] = [5089, 5031]; + _spot[NPC.Tyrael] = [5027, 5027]; + _spot[NPC.Jamella] = [5088, 5054]; + _spot.stash = [5022, 5040]; + _spot.portalspot = [5045, 5042]; + _spot.waypoint = [5043, 5018]; + _spot.initialized = true; + return _spot; + })(), + }, + 5: { + spot: (function () { + const _spot = {}; + _spot[NPC.Larzuk] = [5141, 5045]; + _spot[NPC.Malah] = [5078, 5029]; + _spot[NPC.Cain] = [5119, 5061]; + _spot[NPC.Qual_Kehk] = [5066, 5083]; + _spot[NPC.Anya] = [5112, 5120]; + _spot[NPC.Nihlathak] = [5071, 5111]; + _spot.stash = [5129, 5061]; + _spot.portalspot = [5098, 5019]; + _spot.portal = [5118, 5120]; + _spot.waypoint = [5113, 5068]; + _spot.initialized = true; + return _spot; + })(), + } + }, + + tasks: (function () { + /** + * @param {string} heal + * @param {string} shop + * @param {string} gamble + * @param {string} repair + * @param {string} merc + * @param {string} key + */ + let _taskObj = (heal, shop, gamble, repair, merc, key) => ( + { Heal: heal, Shop: shop, Gamble: gamble, Repair: repair, Merc: merc, Key: key, CainID: NPC.Cain } + ); + return new Map([ + [1, _taskObj(NPC.Akara, NPC.Akara, NPC.Gheed, NPC.Charsi, NPC.Kashya, NPC.Akara)], + [2, _taskObj(NPC.Fara, NPC.Drognan, NPC.Elzix, NPC.Fara, NPC.Greiz, NPC.Lysander)], + [3, _taskObj(NPC.Ormus, NPC.Ormus, NPC.Alkor, NPC.Hratli, NPC.Asheara, NPC.Hratli)], + [4, _taskObj(NPC.Jamella, NPC.Jamella, NPC.Jamella, NPC.Halbu, NPC.Tyrael, NPC.Jamella)], + [5, _taskObj(NPC.Malah, NPC.Malah, NPC.Anya, NPC.Larzuk, NPC.Qual_Kehk, NPC.Malah)] + ]); + })(), + + ignoredItemTypes: [ + // Items that won't be stashed + sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver, sdk.items.type.Book, + sdk.items.type.Scroll, sdk.items.type.Key, sdk.items.type.HealingPotion, + sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion, sdk.items.type.StaminaPotion, + sdk.items.type.AntidotePotion, sdk.items.type.ThawingPotion + ], + + /** + * @description Check if item type is included in the ignore types list + * @param {number} type + * @returns {boolean} If it is an item in the list + */ + ignoreType: function (type) { + return Town.ignoredItemTypes.includes(type); + }, + + /** + * @param {boolean} repair + */ + doChores: function (repair = false) { + console.info(true, null, "doChores"); + + /** + * @todo Pre-build task list so we can more efficiently peform our chores + */ + + !me.inTown && Town.goToTown(); + const readyInTown = function () { + return me.gameReady && me.inTown; + }; + if (!Misc.poll(readyInTown, 2000, 250)) { + throw new Error("Failed to go to town for chores"); + } + + try { + Pather.allowBroadcast = false; + if (Config.FastPick && new RegExp(/[default.dbj|main.js]/gi).test(getScript(true).name)) { + // shopping causes this to bug out sometimes so remove it for duration of chores + removeEventListener("itemaction", Pickit.itemEvent); + } + + const preAct = me.act; + // Burst of speed while in town + if (Skill.canUse(sdk.skills.BurstofSpeed) && !me.getState(sdk.states.BurstofSpeed)) { + Skill.cast(sdk.skills.BurstofSpeed, sdk.skills.hand.Right); + } + + me.switchToPrimary(); + + Town.heal(); + Town.identify(); + Town.clearInventory(); + Town.fillTome(sdk.items.TomeofTownPortal); + Town.buyPotions(); + Config.FieldID.Enabled && Town.fillTome(sdk.items.TomeofIdentify); + Town.shopItems(); + Town.buyKeys(); + Town.repair(repair); + Town.gamble(); + Town.reviveMerc(); + Cubing.doCubing(); + Runewords.makeRunewords(); + Town.stash(true); + Town.checkQuestItems(); + !!me.getItem(sdk.items.TomeofTownPortal) && Town.clearScrolls(); + + me.act !== preAct && Town.goToTown(preAct); + me.cancelUIFlags(); + !me.barbarian && Precast.haveCTA === -1 && Precast.doPrecast(false); + + delay(250); + console.info(false, null, "doChores"); + + return true; + } finally { + if (Config.FastPick && new RegExp(/[default.dbj|main.js]/gi).test(getScript(true).name)) { + addEventListener("itemaction", Pickit.itemEvent); + } + + Pather.allowBroadcast = true; + Town.lastChores = getTickCount(); + } + }, + + /** + * @todo Only use names from the NPC object + * @param {string} name + * @param {boolean} cancel + * @returns {boolean | Unit} + */ + npcInteract: function (name = "", cancel = true) { + // what about finding the closest name in case someone mispells it? + const npcKey = Object.keys(NPC).find(function (key) { + return String.isEqual(key, name); + }); + if (!npcKey) { + // @todo handle if NPC object key is used instead of common name + console.warn("Couldn't find " + name + " in NPC object"); + return false; + } + const npcName = NPC[npcKey]; + + !me.inTown && Town.goToTown(); + + if (!NPC.getAct(npcName).includes(me.act)) { + Town.goToTown(NPC.getAct(npcName).first()); + } + + me.cancelUIFlags(); + + switch (npcName) { + case NPC.Jerhyn: + !Game.getNPC(NPC.Jerhyn) && Town.move("palace"); + break; + case NPC.Hratli: + if (!me.getQuest(sdk.quest.id.SpokeToHratli, sdk.quest.states.Completed)) { + Town.move(NPC.Meshif); + break; + } + // eslint-disable-next-line no-fallthrough + default: + Town.move(npcName); + } + + let npc = Game.getNPC(npcName); + + // In case Jerhyn is by Warriv + if (npcName === NPC.Jerhyn && !npc) { + me.cancel(); + Pather.moveTo(5166, 5206); + npc = Game.getNPC(npcName); + } + + Packet.flash(me.gid); + delay(40); + + if (npc && npc.openMenu()) { + cancel && me.cancel(); + return npc; + } + + return false; + }, + + /** + * @description handle quest consumables if we have them + */ + checkQuestItems: function () { + // Act 1 + // Tools of the trade + if (!me.smith) { + if (me.getItem(sdk.items.quest.HoradricMalus)) { + Town.goToTown(1) && Town.npcInteract("charsi"); + } + } + + // Act 2 + // Radament skill book + if (!me.radament) { + let book = me.getItem(sdk.quest.item.BookofSkill); + if (book) { + book.isInStash && Town.openStash(); + book.use(); + } + } + + // Act 3 + // Figurine -> Golden Bird + if (!me.goldenbird) { + if (me.getItem(sdk.quest.item.AJadeFigurine)) { + Town.goToTown(3) && Town.npcInteract("meshif"); + } + + // Golden Bird -> Ashes + if (me.getItem(sdk.items.quest.TheGoldenBird)) { + Town.goToTown(3) && Town.npcInteract("alkor"); + } + + // Potion of life + let pol = me.getItem(sdk.quest.item.PotofLife); + if (pol) { + pol.isInStash && Town.openStash(); + pol.use(); + } + } + + // LamEssen's Tome + if (!me.lamessen) { + let tome = me.getItem(sdk.quest.item.LamEsensTome); + if (tome) { + !me.inTown && Town.goToTown(3); + tome.isInStash && Town.openStash() && Storage.Inventory.MoveTo(tome); + Town.npcInteract("alkor"); + } + } + + // Scroll of resistance + if (!me.anya) { + let sor = me.getItem(sdk.items.quest.ScrollofResistance); + if (sor) { + sor.isInStash && Town.openStash(); + sor.use(); + } + } + }, + + /** + * @description Start a task and return the NPC Unit + * @param {string} task + * @param {string} reason + * @returns {boolean | Unit} + */ + initNPC: function (task = "", reason = "undefined") { + console.info(true, reason, "initNPC"); + task = task.capitalize(false); + + delay(250); + + /** @type {NPCUnit} */ + let npc = null; + let wantedNpc = Town.tasks.get(me.act)[task] !== undefined + ? Town.tasks.get(me.act)[task] + : "undefined"; + const justUseClosest = ( + ["clearinventory", "sell"].includes(reason.toLowerCase()) + && !me.getUnids().length + ); + + if (getUIFlag(sdk.uiflags.NPCMenu)) { + console.debug("Currently interacting with an npc"); + npc = getInteractedNPC(); + } + + try { + if (npc) { + let npcName = npc.name.toLowerCase(); + if (!justUseClosest && ((npcName !== wantedNpc) + // Jamella gamble fix + || (task === "Gamble" && npcName === NPC.Jamella))) { + me.cancelUIFlags(); + npc = null; + } + } else { + me.cancelUIFlags(); + } + + /** + * we are just trying to clear our inventory, use the closest npc + * Things to conisder: + * - what if we have unid items? Should we use cain if he is closer than the npc with scrolls? + * - what is our next task? + * - would it be faster to change acts and use the closest npc? + */ + if (justUseClosest) { + let choices = new Set(); + let npcs = Town.tasks.get(me.act); + let _needPots = me.needPotions(); + let _needRepair = me.needRepair().length > 0; + if (_needPots && _needRepair) { + if (me.act === 2) { + choices = new Set([npcs.Key, npcs.Repair]); + } else { + choices = new Set([npcs.Key, npcs.Repair, npcs.Gamble, npcs.Shop]); + // todo - handle when we are in normal and current act < 4 + // if we are going to go to a4 for potions anyway we should go ahead and change act + } + } else if (!_needPots && _needRepair) { + choices.add(npcs.Repair); + } else if (!_needPots && !_needRepair) { + choices = new Set([npcs.Key, npcs.Repair, npcs.Gamble, npcs.Shop]); + } + if (choices.size) { + console.log("closest npc choices", choices); + wantedNpc = Array.from(choices.values()).sort(function (a, b) { + return Town.getDistance(a) - Town.getDistance(b); + }).first(); + console.debug("Choosing closest npc", wantedNpc); + } + } + + if (task === "Heal" && me.act === 2) { + // lets see if we are closer to Atma than Fara + if (Town.getDistance(NPC.Atma) < Town.getDistance(NPC.Fara)) { + wantedNpc = NPC.Atma; + } + } + + if (!npc && wantedNpc !== "undefined") { + npc = Game.getNPC(wantedNpc); + + if (!npc && Town.move(wantedNpc)) { + npc = Game.getNPC(wantedNpc); + } + } + + if (!npc || npc.area !== me.area + || (!getUIFlag(sdk.uiflags.NPCMenu) /* && !Town.move(wantedNpc) */ && !npc.openMenu())) { + throw new Error("Couldn't interact with npc"); + } + + delay(40); + + switch (task) { + case "Shop": + case "Repair": + case "Gamble": + if (!getUIFlag(sdk.uiflags.Shop) && !npc.startTrade(task)) { + throw new Error("Failed to complete " + reason + " at " + npc.name); + } + break; + case "Key": + if (!getUIFlag(sdk.uiflags.Shop) && !npc.startTrade(me.act === 3 ? "Repair" : "Shop")) { + throw new Error("Failed to complete " + reason + " at " + npc.name); + } + break; + case "CainID": + Misc.useMenu(sdk.menu.IdentifyItems); + me.cancelUIFlags(); + + break; + case "Heal": + if (String.isEqual(npc.name, NPC.Atma)) { + // prevent crash due to atma not being a shoppable npc + me.cancelUIFlags(); + } + break; + } + + console.info(false, "Did " + reason + " at " + npc.name, "initNPC"); + } catch (e) { + console.error(e); + + if (!!e.message && e.message === "Couldn't interact with npc") { + // getUnit bug probably, lets see if going to different act helps + let highestAct = me.highestAct; + if (highestAct === 1) return false; // can't go to any of the other acts + let myAct = me.act; + let potentialActs = [1, 2, 3, 4, 5].filter(a => a <= highestAct && a !== myAct); + let goTo = potentialActs[rand(0, potentialActs.length - 1)]; + Config.DebugMode.Town && console.debug("Going to Act " + goTo + " to see if it fixes getUnit bug"); + Town.goToTown(goTo); + } + + return false; + } + + Misc.poll(function () { + return me.gameReady; + }, 2000, 3); + + if (task === "Heal") { + Config.DebugMode.Town && console.debug("Checking if we are frozen"); + if (me.getState(sdk.states.Frozen)) { + console.log("We are frozen, lets unfreeze real quick with some thawing pots"); + Town.buyPots(2, sdk.items.ThawingPotion, true, true, npc); + } + } + + return npc; + }, + + /** + * @description Go to a town healer if we are below certain hp/mp percent or have a status effect + */ + heal: function () { + if (!me.needHealing()) return true; + return !!(Town.initNPC("Heal", "heal")); + }, + + buyPotions: function () { + // Ain't got money fo' dat shyt + if (me.gold < 1000) return false; + + me.clearBelt(); + const buffer = { hp: 0, mp: 0 }; + const beltSize = Storage.BeltSize(); + let [needPots, needBuffer, specialCheck] = [false, true, false]; + let col = Town.checkColumns(beltSize); + + const getNeededBuffer = function () { + [buffer.hp, buffer.mp] = [0, 0]; + me.getItemsEx() + .filter(function (p) { + if (!p.isInInventory) return false; + return (p.itemType === sdk.items.type.HealingPotion || p.itemType === sdk.items.type.ManaPotion); + }) + .forEach(function (p) { + if (p.itemType === sdk.items.type.HealingPotion) { + buffer.hp++; + } else { + buffer.mp++; + } + }); + }; + + // HP/MP Buffer + (Config.HPBuffer > 0 || Config.MPBuffer > 0) && getNeededBuffer(); + + // Check if we need to buy potions based on Config.MinColumn + if (Config.BeltColumn.some(function (c, i) { + return ["hp", "mp"].includes(c) && col[i] > (beltSize - Math.min(Config.MinColumn[i], beltSize)); + })) { + needPots = true; + } + + // Check if we need any potions for buffers + if (buffer.mp < Config.MPBuffer || buffer.hp < Config.HPBuffer) { + if (Config.BeltColumn.some(function (c, i) { + return col[i] >= beltSize && (!needPots || c === "rv"); + })) { + specialCheck = true; + } + } + + /** + * @todo If we are set to cube rejuvs, allow buying potions once we have our gem + */ + + // We have enough potions in inventory + (buffer.mp >= Config.MPBuffer && buffer.hp >= Config.HPBuffer) && (needBuffer = false); + + // No columns to fill + if (!needPots && !needBuffer) return true; + // todo: buy the cheaper potions if we are low on gold or don't need the higher ones i.e have low mana/health pool + // why buy potion that heals 225 (greater mana) if we only have sub 100 mana + me.normal && me.highestAct >= 4 && me.act < 4 && Town.goToTown(4); + + let npc = Town.initNPC("Shop", "buyPotions"); + if (!npc) return false; + + // special check, sometimes our rejuv slot is empty but we do still need buffer. Check if we can buy something to slot there + if (specialCheck && Config.BeltColumn.some(function (c, i) { + return c === "rv" && col[i] >= beltSize; + })) { + let pots = [sdk.items.ThawingPotion, sdk.items.AntidotePotion, sdk.items.StaminaPotion]; + Config.BeltColumn.forEach(function (c, i) { + if (c === "rv" && col[i] >= beltSize && pots.length) { + let usePot = pots[0]; + let pot = npc.getItem(usePot); + if (pot) { + Storage.Inventory.CanFit(pot) && Packet.buyItem(pot, false); + pot = me.getItemsEx(usePot, sdk.items.mode.inStorage) + .filter(function (i) { + return i.isInInventory; + }) + .first(); + !!pot && Packet.placeInBelt(pot, i); + pots.shift(); + } else { + needBuffer = false; // we weren't able to find any pots to buy + } + } + }); + } + + for (let i = 0; i < 4; i += 1) { + if (col[i] > 0) { + const useShift = Town.shiftCheck(col, beltSize); + let pot = Town.getPotion(npc, Config.BeltColumn[i]); + + if (pot) { + // console.log("ÿc2column ÿc0" + i + "ÿc2 needs ÿc0" + col[i] + " ÿc2potions"); + // Shift+buy will trigger if there's no empty columns or if only the current column is empty + if (useShift) { + pot.buy(true); + } else { + for (let j = 0; j < col[i]; j += 1) { + pot.buy(false); + } + } + } + } + + col = Town.checkColumns(beltSize); // Re-initialize columns (needed because 1 shift-buy can fill multiple columns) + } + + // re-check + !needBuffer && (Config.HPBuffer > 0 || Config.MPBuffer > 0) && getNeededBuffer(); + + if (needBuffer && buffer.hp < Config.HPBuffer) { + for (let i = 0; i < Config.HPBuffer - buffer.hp; i += 1) { + let pot = Town.getPotion(npc, "hp"); + !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false); + } + } + + if (needBuffer && buffer.mp < Config.MPBuffer) { + for (let i = 0; i < Config.MPBuffer - buffer.mp; i += 1) { + let pot = Town.getPotion(npc, "mp"); + !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false); + } + } + + return true; + }, + + /** + * @description Check when to shift-buy potions + * @param {number} col + * @param {0 | 1 | 2 | 3 | 4} beltSize + */ + shiftCheck: function (col, beltSize) { + let fillType; + + for (let i = 0; i < col.length; i += 1) { + // Set type based on non-empty column + if (!fillType && col[i] > 0 && col[i] < beltSize) { + fillType = Config.BeltColumn[i]; + } + + if (col[i] >= beltSize) { + switch (Config.BeltColumn[i]) { + case "hp": + !fillType && (fillType = "hp"); + if (fillType !== "hp") return false; + + break; + case "mp": + !fillType && (fillType = "mp"); + if (fillType !== "mp") return false; + + break; + case "rv": // Empty rejuv column = can't shift-buy + return false; + } + } + } + + return true; + }, + + /** + * @description Return column status (needed potions in each column) + * @param {0 | 1 | 2 | 3 | 4} beltSize + * @returns {[number, number, number, number]} + */ + checkColumns: function (beltSize) { + (typeof beltSize !== "number" || beltSize < 0 || beltSize > 4) && (beltSize = Storage.BeltSize()); + let col = [beltSize, beltSize, beltSize, beltSize]; + let pot = me.getItem(-1, sdk.items.mode.inBelt); + + // No potions + if (!pot) return col; + + do { + col[pot.x % 4] -= 1; + } while (pot.getNext()); + + return col; + }, + + /** + * @description Get the highest potion from current npc + * @param {Unit} npc + * @param {"hp" | "mp"} type + * @param {1 | 2 | 3 | 4 | 5} highestPot + * @returns {boolean | ItemUnit} + */ + getPotion: function (npc, type, highestPot = 5) { + if (!type) return false; + if (type !== "hp" && type !== "mp") return false; + + for (let i = highestPot; i > 0; i -= 1) { + let result = npc.getItem(type + i); + + if (result) { + return result; + } + } + + return false; + }, + + /** + * @param {number} classid + */ + fillTome: function (classid) { + if (me.gold < 450) return false; + if (me.checkScrolls(classid) >= 13) return true; + + let npc = Town.initNPC("Shop", "fillTome"); + if (!npc) return false; + + if (classid === sdk.items.TomeofTownPortal && !me.getTome(sdk.items.TomeofTownPortal)) { + let tome = npc.getItem(sdk.items.TomeofTownPortal); + + if (tome && Storage.Inventory.CanFit(tome)) { + try { + tome.buy(); + } catch (e1) { + console.log(e1); + // Couldn't buy the tome, don't spam the scrolls + return false; + } + } else { + return false; + } + } + + const scrollID = classid === sdk.items.TomeofTownPortal + ? sdk.items.ScrollofTownPortal + : sdk.items.ScrollofIdentify; + let scroll = npc.getItem(scrollID); + if (!scroll) return false; + + try { + scroll.buy(true); + } catch (e2) { + console.log(e2.message); + + return false; + } + + return true; + }, + + /** + * @deprecated use `me.checkScrolls` instead + * @param {number} id + * @returns {number} quantity of scrolls in tome + */ + checkScrolls: function (id) { + return me.checkScrolls(id); + }, + + identify: function () { + !me.inShop && me.cancelUIFlags(); + if (Town.cainID()) return true; + + let list = (Storage.Inventory.Compare(Config.Inventory) || []); + if (list.length === 0) return false; + + // Avoid unnecessary NPC visits + // Only unid items or sellable junk (low level) should trigger a NPC visit + if (!list.some(function (item) { + const unid = !item.identified; + const results = [Pickit.Result.UNID, Pickit.Result.TRASH]; + return ((unid || Config.LowGold > 0) && (results.includes(Pickit.checkItem(item).result))); + })) { + return false; + } + + let npc = Town.initNPC("Shop", "identify"); + if (!npc) return false; + + let tome = me.getTome(sdk.items.TomeofIdentify); + if (!!tome && tome.getStat(sdk.stats.Quantity) < list.length) { + Town.fillTome(sdk.items.TomeofIdentify); + } + + MainLoop: + while (list.length > 0) { + const item = list.shift(); + if (item.identified || !item.isInInventory || Town.ignoreType(item.itemType)) continue; + let result = Pickit.checkItem(item); + + switch (result.result) { + // Items for gold, will sell magics, etc. w/o id, but at low levels + // magics are often not worth iding. + case Pickit.Result.TRASH: + Item.logger("Sold", item); + item.sell(); + + break; + case Pickit.Result.UNID: + let idTool = tome ? tome : me.getIdTool(); + + if (idTool) { + Town.identifyItem(item, idTool); + } else { + let scroll = npc.getItem(sdk.items.ScrollofIdentify); + + if (scroll) { + if (!Storage.Inventory.CanFit(scroll)) { + let tpTome = me.getTome(sdk.items.TomeofTownPortal); + + if (tpTome) { + tpTome.sell(); + } + } + + delay(500); + + Storage.Inventory.CanFit(scroll) && scroll.buy(); + } + + scroll = me.findItem(sdk.items.ScrollofIdentify, sdk.items.mode.inStorage, sdk.storage.Inventory); + + if (!scroll) { + break MainLoop; + } + + Town.identifyItem(item, scroll); + } + + result = Pickit.checkItem(item); + + switch (result.result) { + case Pickit.Result.WANTED: + Item.logger("Kept", item); + Item.logItem("Kept", item, result.line); + + break; + case Pickit.Result.UNID: + case Pickit.Result.RUNEWORD: // (doesn't trigger normally) + break; + case Pickit.Result.CUBING: + Item.logger("Kept", item, "Cubing-Town"); + Cubing.update(); + + break; + case Pickit.Result.CRAFTING: + Item.logger("Kept", item, "CraftSys-Town"); + CraftingSystem.update(item); + + break; + default: + Item.logger("Sold", item); + item.sell(); + + let timer = getTickCount() - Town.sellTimer; // shop speedup test + + if (timer > 0 && timer < 500) { + delay(timer); + } + + break; + } + + break; + } + } + + Town.fillTome(sdk.items.TomeofTownPortal); // Check for TP tome in case it got sold for ID scrolls + + return true; + }, + + cainID: function () { + // Not enabled or Check if we may use Cain - minimum gold + if (!Config.CainID.Enable || me.gold < Config.CainID.MinGold) return false; + + // Check if we're already in a shop. It would be pointless to go to Cain if so. + let npc = getInteractedNPC(); + if (npc && npc.name.toLowerCase() === Town.tasks.get(me.act).Shop) return false; + + me.cancel(); + Town.stash(false); + + const unids = me.getUnids(); + if (!unids.length) return true; + + // Check if we may use Cain - number of unid items + if (unids.length < Config.CainID.MinUnids) return false; + + // Check if we may use Cain - kept unid items + for (let item of unids) { + if (Pickit.checkItem(item).result > 0) return false; + } + + let cain = Town.initNPC("CainID", "cainID"); + if (!cain) return false; + + for (let item of unids) { + let result = Pickit.checkItem(item); + + switch (result.result) { + case Pickit.Result.UNWANTED: + Item.logger("Dropped", item, "cainID"); + item.drop(); + + break; + case Pickit.Result.WANTED: + Item.logger("Kept", item); + Item.logItem("Kept", item, result.line); + + break; + default: + break; + } + } + return true; + }, + + /** + * @param {ItemUnit} unit + * @param {ItemUnit} tome + * @param {Boolean} packetID + * @returns {boolean} + */ + identifyItem: function (unit, tome, packetID = false) { + if (!unit || unit.identified || !tome) return false; + if (Config.PacketShopping || packetID) return Packet.identifyItem(unit, tome); + + Town.sellTimer = getTickCount(); // shop speedup test + + const idOnCursor = function () { + return getCursorType() === sdk.cursortype.Identify; + }; + const unitIdentified = function () { + return unit.identified; + }; + + for (let i = 0; i < 3; i += 1) { + clickItem(sdk.clicktypes.click.item.Right, tome); + + if (Misc.poll(idOnCursor, 500, 10)) { + break; + } + } + + if (!idOnCursor()) return false; + + delay(270); + + for (let i = 0; i < 3; i += 1) { + if (idOnCursor()) { + clickItem(sdk.clicktypes.click.item.Left, unit); + } + + if (Misc.poll(unitIdentified, 500, 10)) { + delay(25); + + return true; + } + + delay(300); + } + + return false; + }, + + shopItems: function () { + if (!Config.MiniShopBot) return true; + + let npc = getInteractedNPC(); + if (!npc || !npc.itemcount) return false; + + const items = npc.getItemsEx().filter(function (item) { + return !Town.ignoreType(item.itemType); + }); + if (!items.length) return false; + + console.log("ÿc4MiniShopBotÿc0: Scanning " + npc.itemcount + " items."); + + for (let item of items) { + const { result, line } = Pickit.checkItem(item); + + switch (result) { + case Pickit.Result.WANTED: + case Pickit.Result.CUBING: + case Pickit.Result.CRAFTING: + case Pickit.Result.RUNEWORD: + try { + if (Storage.Inventory.CanFit(item) && me.gold >= item.getItemCost(sdk.items.cost.ToBuy)) { + Item.logger("Shopped", item); + Item.logItem("Shopped", item, line); + item.buy(); + } + } catch (e) { + console.error(e); + } + } + + delay(2); + } + + return true; + }, + + /** @type {Set} */ + gambleIds: new Set(), + + gamble: function () { + if (!Town.needGamble() || Config.GambleItems.length === 0) return true; + if (Town.gambleIds.size === 0) { + // change text to classid + for (let item of Config.GambleItems) { + if (isNaN(item)) { + if (NTIPAliasClassID.hasOwnProperty(item.replace(/\s+/g, "").toLowerCase())) { + Town.gambleIds.add(NTIPAliasClassID[item.replace(/\s+/g, "").toLowerCase()]); + } else { + Misc.errorReport("ÿc1Invalid gamble entry:ÿc0 " + item); + } + } else { + Town.gambleIds.add(item); + } + } + } + + if (Town.gambleIds.size === 0) return true; + + // avoid Alkor + me.act === 3 && Town.goToTown(2); + let npc = Town.initNPC("Gamble", "gamble"); + + if (!npc) return false; + + let list = []; + let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory); + + while (items && items.length > 0) { + list.push(items.shift().gid); + } + + while (me.gold >= Config.GambleGoldStop) { + !getInteractedNPC() && npc.startTrade("Gamble"); + + let item = npc.getItem(); + items = []; + + if (item) { + do { + if (Town.gambleIds.has(item.classid)) { + items.push(copyUnit(item)); + } + } while (item.getNext()); + + for (let item of items) { + if (!Storage.Inventory.CanFit(item)) { + return false; + } + + me.overhead("Buy: " + item.name); + item.buy(false, true); + let newItem = Town.getGambledItem(list); + + if (newItem) { + let result = Pickit.checkItem(newItem); + + switch (result.result) { + case Pickit.Result.WANTED: + Item.logger("Gambled", newItem); + Item.logItem("Gambled", newItem, result.line); + list.push(newItem.gid); + + break; + case Pickit.Result.CUBING: + list.push(newItem.gid); + Cubing.update(); + + break; + case Pickit.Result.CRAFTING: + CraftingSystem.update(newItem); + + break; + default: + Item.logger("Sold", newItem, "Gambling"); + me.overhead("Sell: " + newItem.name); + newItem.sell(); + + if (!Config.PacketShopping) { + delay(500); + } + + break; + } + } + } + } + + me.cancel(); + } + + return true; + }, + + needGamble: function () { + return Config.Gamble && me.gold >= Config.GambleGoldStart; + }, + + /** + * @param {ItemUnit[]} list + */ + getGambledItem: function (list = []) { + let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory); + + for (let item of items) { + if (list.indexOf(item.gid) === -1) { + for (let j = 0; j < 3; j += 1) { + if (item.identified) { + break; + } + + delay(100); + } + + return item; + } + } + + return false; + }, + + /** + * @param {number} quantity + * @param {number | string} type + * @param {boolean} [drink=false] + * @param {boolean} [force=false] + * @param {Unit} [npc=null] + */ + buyPots: function (quantity = 0, type = undefined, drink = false, force = false, npc = null) { + if (!quantity || !type) return false; + + // convert to classid if isn't one + typeof type === "string" && (type = (sdk.items[type.capitalize(true) + "Potion"] || false)); + if (!type) return false; + + // can't buy pots if we are full + if (!Storage.Inventory.CanFit({ sizex: 1, sizey: 1 })) return false; + + // todo - change act in a3 if we are next to the wp as it's faster than going all the way to Alkor + // todo - compare distance Ormus -> Alkor compared to Ormus -> WP -> Akara + const potDealers = new Map([ + [1, NPC.Akara], + [2, NPC.Lysander], + [3, NPC.Alkor], + [4, NPC.Jamella], + [5, NPC.Malah], + ]); + let potDealer = potDealers.get(me.act); + + switch (type) { + case sdk.items.ThawingPotion: + // Don't buy if already at max res + if (!force && me.coldRes >= 75) return true; + console.info(null, "Current cold resistance: " + me.coldRes); + + break; + case sdk.items.AntidotePotion: + // Don't buy if already at max res + if (!force && me.poisonRes >= 75) return true; + console.info(null, "Current poison resistance: " + me.poisonRes); + + break; + case sdk.items.StaminaPotion: + // Don't buy if teleport or vigor + if (!force && (Skill.canUse(sdk.skills.Vigor) || Pather.canTeleport())) return true; + + break; + } + + if (potDealer === NPC.Alkor && Town.getDistance(potDealer) > 10) { + Town.goToTown(me.highestAct >= 4 ? 4 : 1); + potDealer = potDealers.get(me.act); + } + + try { + if (!!npc && npc.name.toLowerCase() === potDealer && !getUIFlag(sdk.uiflags.Shop)) { + if (!npc.startTrade("Shop")) throw new Error("Failed to open " + npc.name + " trade menu"); + } else { + me.cancelUIFlags(); + Town.move(potDealer); + npc = Game.getNPC(potDealer); + + if (!npc || !npc.openMenu() || !npc.startTrade("Shop")) { + throw new Error("Failed to open " + npc.name + " trade menu"); + } + } + } catch (e) { + console.error(e); + + return false; + } + + let pot = npc.getItem(type); + if (!pot) { + console.warn("Couldn't find " + type + " from " + npc.name); + return false; + } + let name = (pot.name || ""); + + console.info(null, "Buying " + quantity + " " + name + "s"); + + for (let pots = 0; pots < quantity; pots++) { + if (!!pot && Storage.Inventory.CanFit(pot)) { + Packet.buyItem(pot, false); + } + } + + me.cancelUIFlags(); + drink && Town.drinkPots(type); + + return true; + }, + + /** + * @param {number | string} type + * @param {boolean} [log=true] + * @returns {{ potName: string, quantity: number }} + */ + drinkPots: function (type = undefined, log = true) { + // convert to classid if isn't one + typeof type === "string" && (type = (sdk.items[type.capitalize(true) + "Potion"] || false)); + + let name = ""; + let quantity = 0; + let chugs = me.getItemsEx(type).filter(pot => pot.isInInventory); + + if (chugs.length > 0) { + name = chugs.first().name; + chugs.forEach(function (pot) { + if (!!pot && pot.use()) { + quantity++; + } + }); + + if (log && name) { + console.info(null, "Drank " + quantity + " " + name + "s. Timer [" + Time.format(quantity * 30 * 1000) + "]"); + } + } else { + console.warn("couldn't find my pots"); + } + + return { + potName: name, + quantity: quantity + }; + }, + + buyKeys: function () { + if (me.checkKeys() >= 6) return true; + + // avoid Hratli + me.act === 3 && Town.goToTown(me.accessToAct(4) ? 4 : 2); + + let npc = Town.initNPC("Key", "buyKeys"); + if (!npc) return false; + + let key = npc.getItem("key"); + if (!key) return false; + + try { + key.buy(true); + } catch (e) { + console.error(e); + + return false; + } + + return true; + }, + + /** @deprecated Use `me.checkKeys` instead */ + checkKeys: function () { + console.debug("Town.checkKeys is deprecated, use me.checkKeys instead"); + return me.checkKeys(); + }, + + /** @deprecated Use `me.needKeys` instead */ + needKeys: function () { + return me.needKeys(); + }, + + /** + * @deprecated Use `Cubing.repairIngredientCheck` instead + * @param {ItemUnit} item - Rune + */ + repairIngredientCheck: function (item) { + return Cubing.repairIngredientCheck(item); + }, + + /** + * @deprecated Use `Cubing.doRepairs` instead + * @returns {boolean} + */ + cubeRepair: function () { + return Cubing.doRepairs(); + }, + + /** + * @deprecated Use `Cubing.repairItem` instead + * @param {ItemUnit} item + * @returns {boolean} + */ + cubeRepairItem: function (item) { + return Cubing.repairItem(item); + }, + + /** + * @param {boolean} [force=false] + */ + repair: function (force = false) { + if (Cubing.doRepairs()) return true; + + let npc; + let repairAction = me.needRepair(); + force && repairAction.indexOf("repair") === -1 && repairAction.push("repair"); + + if (!repairAction || !repairAction.length) return true; + + for (let action of repairAction) { + switch (action) { + case "repair": + me.act === 3 && Town.goToTown(me.accessToAct(4) ? 4 : 2); + npc = Town.initNPC("Repair", "repair"); + if (!npc) return false; + me.repair(); + + break; + case "buyQuiver": + let bowCheck = Attack.usingBow(); + + if (bowCheck) { + let quiver = bowCheck === "bow" ? "aqv" : "cqv"; + let myQuiver = me.getItem(quiver, sdk.items.mode.Equipped); + !!myQuiver && myQuiver.drop(); + + npc = Town.initNPC("Repair", "repair"); + if (!npc) return false; + + quiver = npc.getItem(quiver); + !!quiver && quiver.buy(); + } + + break; + } + } + + return true; + }, + + /** @deprecated Use `me.needRepair` instead */ + needRepair: function () { + console.debug("me.needRepair is deprecated. Use me.needRepair instead."); + return me.needRepair(); + }, + + /** + * @deprecated Use `me.getItemsForRepair` instead + * @param {number} repairPercent + * @param {boolean} chargedItems + * @returns {ItemUnit[]} + */ + getItemsForRepair: function (repairPercent, chargedItems) { + console.debug("Town.getItemsForRepair is deprecated. Use me.getItemsForRepair instead."); + + return me.getItemsForRepair(repairPercent, chargedItems); + }, + + reviveMerc: function () { + if (!me.needMerc()) return true; + let preArea = me.area; + + // avoid Aheara + me.act === 3 && Town.goToTown(me.accessToAct(4) ? 4 : 2); + + let npc = Town.initNPC("Merc", "reviveMerc"); + if (!npc) return false; + + MainLoop: + for (let i = 0; i < 3; i += 1) { + let dialog = getDialogLines(); + + for (let lines = 0; lines < dialog.length; lines += 1) { + if (dialog[lines].text.match(":", "gi")) { + dialog[lines].handler(); + delay(Math.max(750, me.ping * 2)); + } + + // "You do not have enough gold for that." + if (dialog[lines].text.match(getLocaleString(sdk.locale.dialog.youDoNotHaveEnoughGoldForThat), "gi")) { + return false; + } + } + + let tick = getTickCount(); + + while (getTickCount() - tick < 2000) { + if (!!me.getMerc()) { + delay(Math.max(750, me.ping * 2)); + + break MainLoop; + } + + delay(200); + } + } + + Attack.checkInfinity(); + + if (!!me.getMerc()) { + // Cast BO on merc so he doesn't just die again. Only do this is you are a barb or actually have a cta. Otherwise its just a waste of time. + if (Config.MercWatch && Precast.needOutOfTownCast()) { + console.log("MercWatch precast"); + Precast.doRandomPrecast(true, preArea); + } + + return true; + } + + return false; + }, + + /** @deprecated Use `me.needMerc` instead */ + needMerc: function () { + console.debug("Town.needMerc is deprecated. Use me.needMerc instead."); + return me.needMerc(); + }, + + /** + * @param {ItemUnit} item + */ + canStash: function (item) { + if (Town.ignoreType(item.itemType) + || [sdk.items.quest.HoradricStaff, sdk.items.quest.KhalimsWill].includes(item.classid) + || !Town.canStashGem(item)) { + return false; + } + /** + * @todo add sorting here first if we can't fit the item + */ + return Storage.Stash.CanFit(item); + }, + + /** + * get ordered list of gems in inventory to use with Gem Hunter Script + * @returns {ItemUnit[]} ordered list of relevant gems + */ + getGemsInInv: function () { + let GemList = Config.GemHunter.GemList; + return me.getItemsEx() + .filter((p) => p.isInInventory && GemList.includes(p.classid)) + .sort((a, b) => GemList.indexOf(a.classid) - GemList.indexOf(b.classid)); + }, + + /** + * get ordered list of gems in stash to use with Gem Hunter Script + * @returns {ItemUnit[]} ordered list of relevant gems + */ + getGemsInStash: function () { + let GemList = Config.GemHunter.GemList; + return me.getItemsEx() + .filter((p) => p.isInStash && GemList.includes(p.classid)) + .sort((a, b) => GemList.indexOf(a.classid) - GemList.indexOf(b.classid)); + }, + + /** + * gem check for use with Gem Hunter Script + * @param {ItemUnit} item + * @returns {boolean} if we should stash this gem + */ + canStashGem: function (item) { + // we aren't using the gem hunter script or we aren't scanning for the gem shrines while moving + // for now we are only going to keep a gem in our invo while the script is active + if (Loader.scriptName() !== "GemHunter") return true; + // not in our list + if (Config.GemHunter.GemList.indexOf(item.classid) === -1) return true; + + let GemList = Config.GemHunter.GemList; + let gemsInStash = Town.getGemsInStash(); + let bestGeminStash = gemsInStash.length > 0 ? gemsInStash.first().classid : -1; + let gemsInInvo = Town.getGemsInInv(); + let bestGeminInv = gemsInInvo.length > 0 ? gemsInInvo.first().classid : -1; + + return ( + (GemList.indexOf(bestGeminStash) < GemList.indexOf(bestGeminInv)) // better one in stash + || (GemList.indexOf(bestGeminInv) < GemList.indexOf(item.classid)) // better one in inv + || (gemsInInvo.filter((p) => p.classid === item.classid).length > 1)); // another one in inv + }, + + /** + * move best gem from stash to inventory, if none in inventrory + * to use with Gem Hunter Script + * @returns {boolean} if any gem has been moved + */ + getGem: function () { + if (Loader.scriptName() === "GemHunter") { + if (Town.getGemsInInv().length === 0 && Town.getGemsInStash().length > 0) { + let gem = Town.getGemsInStash().first(); + Storage.Inventory.MoveTo(gem) && Item.logger("Inventoried", gem); + return true; + } + } + return false; + }, + + /** + * @param {boolean} [stashGold=true] + * @returns {boolean} + */ + stash: function (stashGold = true) { + if (!me.needStash()) return true; + + me.cancelUIFlags(); + + /** @type {ItemUnit[]} */ + let items = (Storage.Inventory.Compare(Config.Inventory) || []) + .filter(function (item) { + return !Town.ignoreType(item.itemType); + }); + + if (items && items.length) { + Config.SortSettings.SortStash && Storage.Stash.SortItems(); + + for (let item of items) { + if (Town.canStash(item)) { + let result = false; + let pickResult = Pickit.checkItem(item).result; + + switch (true) { + case pickResult > Pickit.Result.UNWANTED && pickResult < Pickit.Result.TRASH: + case Cubing.keepItem(item): + case Runewords.keepItem(item): + case CraftingSystem.keepItem(item): + result = true; + + break; + default: + break; + } + + if (result) { + Storage.Stash.MoveTo(item) && Item.logger("Stashed", item); + } + } + } + } + + // Stash gold + if (stashGold) { + if (me.getStat(sdk.stats.Gold) >= Config.StashGold + && me.getStat(sdk.stats.GoldBank) < 25e5 && Town.openStash()) { + gold(me.getStat(sdk.stats.Gold), 3); + delay(1000); // allow UI to initialize + me.cancel(); + } + } + + return true; + }, + + /** @deprecated Use `me.needStash` instead */ + needStash: function () { + console.debug("Town.needStash is deprecated, use me.needStash instead"); + return me.needStash(); + }, + + openStash: function () { + if (getUIFlag(sdk.uiflags.Cube) && !Cubing.closeCube(true)) return false; + if (getUIFlag(sdk.uiflags.Stash)) return true; + const stashOpened = function () { + return getUIFlag(sdk.uiflags.Stash); + }; + + for (let i = 0; i < 5 && !stashOpened(); i += 1) { + me.itemoncursor && Cubing.cursorCheck(); + me.cancel(); + + if (Town.move("stash")) { + let stash = Game.getObject(sdk.objects.Stash); + + if (stash) { + let pingDelay = me.getPingDelay(); + + if (Skill.useTK(stash)) { + // Fix for out of range telek + if (i > 0 && stash.distance > (23 - (i * 2))) { + Pather.walkTo(stash.x, stash.y, (23 - (i * 2))); + } + Packet.telekinesis(stash); + } else { + Misc.click(0, 0, stash); + } + + if (Misc.poll(stashOpened, Time.seconds(5), 100)) { + // allow UI to initialize + delay(100 + pingDelay * (i + 1)); + + return true; + } + } + } + + Packet.flash(me.gid); + } + + return false; + }, + + getCorpse: function () { + let corpse, corpseList = []; + let timer = getTickCount(); + + // No equipped items - high chance of dying in last game, force retries + if (!me.getItem(-1, sdk.items.mode.Equipped)) { + corpse = Misc.poll(() => Game.getPlayer(me.name, sdk.player.mode.Dead), 2500, 500); + } else { + corpse = Game.getPlayer(me.name, sdk.player.mode.Dead); + } + + if (!corpse) return true; + + do { + if (corpse.dead && corpse.name === me.name + && (getDistance(me.x, me.y, corpse.x, corpse.y) <= 20 || me.inTown)) { + corpseList.push(copyUnit(corpse)); + } + } while (corpse.getNext()); + + while (corpseList.length > 0) { + if (me.dead) return false; + + let gid = corpseList[0].gid; + + Pather.moveToUnit(corpseList[0]); + Misc.click(0, 0, corpseList[0]); + delay(500); + + if (getTickCount() - timer > 3000) { + let coord = CollMap.getRandCoordinate(me.x, -1, 1, me.y, -1, 1, 4); + !!coord && Pather.moveTo(coord.x, coord.y); + } + + if (getTickCount() - timer > 30000) { + D2Bot.console.logToConsole("Failed to get corpse, stopping.", sdk.colors.D2Bot.Red); + D2Bot.stop(); + } + + !Game.getPlayer(-1, -1, gid) && corpseList.shift(); + } + + me.classic && me.checkShard(); + // re-init skills since we started off without our body + Skill.init(); + + return true; + }, + + /** + * @todo Whats the point of this? + * @deprecated Use `me.checkShard` instead + * @returns {boolean} + */ + checkShard: function () { + return me.checkShard(); + }, + + /** @deprecated Use `me.clearBelt` */ + clearBelt: function () { + console.debug("Town.clearBelt is deprecated, use me.clearBelt instead"); + + return me.clearBelt(); + }, + + clearScrolls: function () { + const scrolls = me.getItemsEx() + .filter(function (scroll) { + return scroll.isInInventory && scroll.itemType === sdk.items.type.Scroll; + }); + if (!scrolls.length) return false; + const tpTome = scrolls.some(function (scroll) { + return scroll.classid === sdk.items.ScrollofTownPortal; + }) ? me.getTome(sdk.items.TomeofTownPortal) : false; + const idTome = scrolls.some(function (scroll) { + return scroll.classid === sdk.items.ScrollofIdentify; + }) ? me.getTome(sdk.items.TomeofIdentify) : false; + let currQuantity; + + for (let i = 0; !!scrolls && i < scrolls.length; i++) { + switch (scrolls[i].classid) { + case sdk.items.ScrollofTownPortal: + if (tpTome && tpTome.getStat(sdk.stats.Quantity) < 20) { + currQuantity = tpTome.getStat(sdk.stats.Quantity); + if (scrolls[i].toCursor()) { + clickItemAndWait(sdk.clicktypes.click.item.Left, tpTome.x, tpTome.y, tpTome.location); + + if (tpTome.getStat(sdk.stats.Quantity) > currQuantity) { + console.info(null, "Placed scroll in tp tome"); + + continue; + } + } + } + + break; + case sdk.items.ScrollofIdentify: + if (idTome && idTome.getStat(sdk.stats.Quantity) < 20) { + currQuantity = idTome.getStat(sdk.stats.Quantity); + if (scrolls[i].toCursor()) { + clickItemAndWait(sdk.clicktypes.click.item.Left, idTome.x, idTome.y, idTome.location); + + if (idTome.getStat(sdk.stats.Quantity) > currQuantity) { + console.info(null, "Placed scroll in id tome"); + + continue; + } + } + } + + if (Config.FieldID.Enabled && !idTome) { + // don't toss scrolls if we need them for field id but don't have a tome yet - low level chars + continue; + } + + break; + } + + // Might as well sell the item if already in shop + if (getUIFlag(sdk.uiflags.Shop) + || (Config.PacketShopping && getInteractedNPC() && getInteractedNPC().itemcount > 0)) { + console.info(null, "Sell " + scrolls[i].name); + Item.logger("Sold", scrolls[i]); + scrolls[i].sell(); + } else { + Item.logger("Dropped", scrolls[i], "clearScrolls"); + scrolls[i].drop(); + } + } + + return true; + }, + + clearInventory: function () { + console.info(true, null, "clearInventory"); + + // If we are at an npc already, open the window otherwise moving potions around fails + if (getUIFlag(sdk.uiflags.NPCMenu) && !getUIFlag(sdk.uiflags.Shop)) { + try { + console.debug("Open npc menu"); + !!getInteractedNPC() && Misc.useMenu(sdk.menu.Trade); + } catch (e) { + console.error(e); + me.cancelUIFlags(); + } + } + + // Remove potions in the wrong slot of our belt + me.clearBelt(); + + // Return potions from inventory to belt + me.cleanUpInvoPotions(); + + // Cleanup remaining potions + Config.DebugMode.Town && console.debug("clearInventory: start clean-up remaining pots"); + let sellOrDrop = []; + let potsInInventory = me.getItemsEx() + .filter(function (p) { + return p.isInInventory && [ + sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, + sdk.items.type.RejuvPotion, sdk.items.type.ThawingPotion, + sdk.items.type.AntidotePotion, sdk.items.type.StaminaPotion + ].includes(p.itemType); + }); + + if (potsInInventory.length > 0) { + let [hp, mp, rv, specials] = [[], [], [], []]; + while (potsInInventory.length) { + (function (p) { + switch (p.itemType) { + case sdk.items.type.HealingPotion: + return (hp.push(p)); + case sdk.items.type.ManaPotion: + return (mp.push(p)); + case sdk.items.type.RejuvPotion: + return (rv.push(p)); + case sdk.items.type.ThawingPotion: + case sdk.items.type.AntidotePotion: + case sdk.items.type.StaminaPotion: + default: // shuts d2bs up + return (specials.push(p)); + } + })(potsInInventory.shift()); + } + + /** + * @param {ItemUnit} a + * @param {ItemUnit} b + * @returns {number} + */ + let sortPots = function (a, b) { + return a.classid - b.classid; + }; + // ensures when clearing invo we don't sell high pots before low pots + hp.sort(sortPots); + mp.sort(sortPots); + rv.sort(sortPots); + + // Cleanup healing potions + while (hp.length > Config.HPBuffer) { + sellOrDrop.push(hp.shift()); + } + + // Cleanup mana potions + while (mp.length > Config.MPBuffer) { + sellOrDrop.push(mp.shift()); + } + + // Cleanup rejuv potions + while (rv.length > Config.RejuvBuffer) { + sellOrDrop.push(rv.shift()); + } + + // Clean up special pots + while (specials.length) { + specials.shift().interact(); + delay(200); + } + } + + // Any leftover items from a failed ID (crashed game, disconnect etc.) + Config.DebugMode.Town && console.debug("clearInventory: start invo clean-up"); + const ignoreTypes = [ + sdk.items.type.Book, sdk.items.type.Key, + sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion + ]; + let items = (Storage.Inventory.Compare(Config.Inventory) || []) + .filter(function (item) { + if (!item) return false; + // Don't drop tomes, keys or potions or quest-items + // Don't throw cubing/runeword/crafting ingredients + if (ignoreTypes.indexOf(item.itemType) === -1 && item.sellable + && !Cubing.keepItem(item) && !Runewords.keepItem(item) && !CraftingSystem.keepItem(item)) { + return true; + } + return false; + }); + + // add leftovers from potion cleanup + items = (items.length > 0 + ? items.concat(sellOrDrop) + : sellOrDrop.slice(0) + ); + + if (items.length > 0) { + /** @type {ItemUnit[]} */ + let sell = []; + /** @type {ItemUnit[]} */ + let drop = []; + // lets see if we have any items to sell + items.forEach(function (item) { + let result = Pickit.checkItem(item).result; + switch (result) { + case Pickit.Result.UNWANTED: + return drop.push(item); + case Pickit.Result.TRASH: + return sell.push(item); + } + return false; + }); + // we have items to sell, might as well sell the dropable items as well + if (sell.length) { + // should there be multiple attempts to interact with npc or if we fail should we move everything from the sell list to the drop list? + let npc; + for (let i = 0; i < 3 && !npc; i++) { + npc = Town.initNPC("Shop", "clearInventory"); + } + + // now lets sell them items + if (npc) { + [].concat(sell, drop.filter((item) => item.sellable)) + .forEach(function (item) { + let sold = false; // so we know to delay or not + try { + console.info(null, "Sell :: " + item.name); + Item.logger("Sold", item); + item.sell() && (sold = true); + } catch (e) { + console.error(e); + } + sold && delay(250); // would a rand delay be better? + }); + } + // now lets see if we need to drop anything, so lets exit the shop + me.cancelUIFlags(); + drop = drop.filter(function (item) { + return !!item && me.getItem(-1, sdk.items.mode.inStorage, item.gid); + }); + } + + if (drop.length) { + drop.forEach(function (item) { + let drop = false; // so we know to delay or not + try { + console.info(null, "Drop :: " + item.name); + Item.logger("Dropped", item, "clearInventory"); + item.drop() && (drop = true); + } catch (e) { + console.error(e); + } + drop && delay(50); + }); + } + } + + console.info(false, null, "clearInventory"); + + return true; + }, + + initialize: function () { + // console.log("Initialize town " + me.act); + if (!Town.act[me.act].spot.initialized && me.act === 1) { + // act 1 is the only act that needs to be initialized + let wp = Game.getPresetObject(sdk.areas.RogueEncampment, sdk.objects.A1Waypoint); + let fireUnit = Game.getPresetObject(sdk.areas.RogueEncampment, sdk.objects.A1TownFire); + if (!fireUnit) return false; + + const fire = fireUnit.realCoords(); + Town.act[1].spot.stash = [fire.x - 7, fire.y - 12]; + Town.act[1].spot.fire = [fire.x, fire.y]; + Town.act[1].spot[NPC.Warriv] = [fire.x - 5, fire.y - 2]; + Town.act[1].spot[NPC.Cain] = [fire.x + 6, fire.y - 5]; + Town.act[1].spot[NPC.Kashya] = [fire.x + 14, fire.y - 4]; + Town.act[1].spot[NPC.Akara] = [fire.x + 56, fire.y - 30]; + Town.act[1].spot[NPC.Charsi] = [fire.x - 39, fire.y - 25]; + Town.act[1].spot[NPC.Gheed] = [fire.x - 34, fire.y + 36]; + Town.act[1].spot.portalspot = [fire.x + 10, fire.y + 18]; + Town.act[1].spot.waypoint = [wp.roomx * 5 + wp.x, wp.roomy * 5 + wp.y]; + Town.act[1].initialized = true; + } + + return true; + }, + + /** + * @param {string} spot + * @returns {number} distance to town location + */ + getDistance: function (spot = "") { + !me.inTown && Town.goToTown(); + !Town.act[me.act].initialized && Town.initialize(); + + // Act 5 wp->portalspot override - ActMap.cpp crash + if (me.act === 5 && spot === "portalspot" + && getDistance(me.x, me.y, 5113, 5068) <= 8) { + return [5098, 5018].distance; + } + + if (typeof (Town.act[me.act].spot[spot]) === "object") { + return Town.act[me.act].spot[spot].distance; + } else { + return Infinity; + } + }, + + /** + * @param {string} spot + * @param {boolean} [allowTK] + * @returns {boolean} + */ + move: function (spot = "", allowTK = true) { + !me.inTown && Town.goToTown(); + !Town.act[me.act].initialized && Town.initialize(); + + // act 5 static paths, ActMap.cpp seems to have issues with A5 + // should other towns have static paths? + if (me.act === 5) { + /** @type {Array<[number, number]>} */ + let path = []; + let returnWhenDone = false; + + // Act 5 wp->portalspot override - ActMap.cpp crash + if (spot === "portalspot" && getDistance(me.x, me.y, 5113, 5068) <= 8) { + path = [[5113, 5068], [5108, 5051], [5106, 5046], [5104, 5041], [5102, 5027], [5098, 5018]]; + returnWhenDone = true; + } + + if (["stash", "waypoint"].includes(spot)) { + // malah -> stash/wp + if (getDistance(me.x, me.y, 5081, 5031) <= 10) { + path = [[5089, 5029], [5093, 5021], [5101, 5027], [5107, 5043], [5108, 5052]]; + } else if (getDistance(me.x, me.y, 5099, 5020) <= 13) { + // portalspot -> stash/wp + path = [[5102, 5031], [5107, 5042], [5108, 5052]]; + } + } + + if (path.length) { + path.forEach(function (node) { + Pather.walkTo(node[0], node[1]); + }); + + if (returnWhenDone) return true; + } + } else if (me.act === 2 + && me.x > 5122 && me.y <= 5049 + && !String.isEqual(spot, NPC.Atma)) { + // we are inside the building, if Atma is blocking the entrance we need the side door + let atma = Game.getNPC(NPC.Atma); + console.debug(" me { x: " + me.x + ", y: " + me.y + " } atma { x: " + atma.x + ", y: " + atma.y + " }"); + // todo - might need to consider her targetx/y coords as well + if (atma && (atma.x === 5136 || atma.x === 5137) + && (atma.y >= 5048 && atma.y <= 5051)) { + // yup dumb lady is blocking the door, take side door + [[5140, 5038], [5148, 5031], [5154, 5025], [5161, 5030]].forEach(function (node) { + Pather.walkTo(node[0], node[1]); + }); + } + } + + for (let i = 0; i < 3; i += 1) { + i === 2 && (allowTK = false); + if (Town.moveToSpot(spot, allowTK)) { + return true; + } + + Packet.flash(me.gid); + } + + return false; + }, + + /** + * @param {string} spot + * @param {boolean} [allowTK] + * @returns {boolean} + */ + moveToSpot: function (spot = "", allowTK = true) { + if (!Town.act[me.act].hasOwnProperty("spot") + || !Town.act[me.act].spot.hasOwnProperty(spot) + || typeof (Town.act[me.act].spot[spot]) !== "object") { + return false; + } + if (Town.getDistance(spot) < 5) return true; + + const npcSpot = Object.values(NPC).includes(spot.toLowerCase()); + const longRange = (!Skill.haveTK && spot === "waypoint"); + const tkRange = (Skill.haveTK && allowTK && ["stash", "portalspot", "waypoint"].includes(spot)); + let townSpot = Town.act[me.act].spot[spot]; + + if (longRange) { + let path = getPath(me.area, townSpot[0], townSpot[1], me.x, me.y, 1, 8); + + if (path && path[1]) { + townSpot = [path[1].x, path[1].y]; + } + } + + for (let i = 0; i < townSpot.length; i += 2) { + /** @type {PathNode} */ + const node = { x: townSpot[i], y: townSpot[i + 1] }; + // console.debug("moveToSpot: " + spot + " " + node.x + "/" + node.y + " from " + me.x + "/" + me.y); + + if (tkRange) { + Pather.moveNear(townSpot[0], townSpot[1], 19); + } else if (node.distance > 2) { + if (npcSpot) { + let npc = Game.getNPC(spot); + if (npc && npc.distance < 5) return true; + Pather.move(node, { callback: function () { + let npc = Game.getNPC(spot); + return npc && npc.distance < 5; + } }); + } else { + Pather.move(node, { retry: 3 }); + } + } + + switch (spot) { + case "stash": + if (Game.getObject(sdk.objects.Stash)) { + return true; + } + + break; + case "palace": + if (Game.getNPC(NPC.Jerhyn)) { + return true; + } + + break; + case "portalspot": + case "sewers": + if (tkRange && spot === "portalspot" + && getDistance(me, townSpot[0], townSpot[1]) < 21) { + return true; + } + + if (node.distance < 10) { + return true; + } + + break; + case "waypoint": + let wp = Game.getObject("waypoint"); + if (wp) { + !Skill.haveTK && wp.distance > 5 && Pather.moveToUnit(wp); + return true; + } + + break; + default: + if (Game.getNPC(spot)) { + return true; + } + + break; + } + } + + return false; + }, + + /** + * @param {Act} act + * @param {boolean} [wpmenu=false] + * @returns {boolean} + */ + goToTown: function (act = 0, wpmenu = false) { + if (!me.inTown) { + try { + // this can save us spamming portals + let oldPortal = Pather.getPortal(sdk.areas.townOf(me.area), me.name); + if ((oldPortal && !Pather.usePortal(null, me.name, oldPortal)) + || !Pather.makePortal(true)) { + console.warn("Town.goToTown: Failed to make TP"); + } + if (!me.inTown && !Pather.usePortal(null, me.name)) { + console.warn("Town.goToTown: Failed to take TP"); + if (!me.inTown && !Pather.usePortal(sdk.areas.townOf(me.area))) { + throw new Error("Town.goToTown: Failed to take TP"); + } + } + } catch (e) { + let tpTool = me.getTpTool(); + if (!tpTool && Misc.getPlayerCount() <= 1) { + Misc.errorReport(new Error("Town.goToTown: Failed to go to town and no tps available. Restart.")); + scriptBroadcast("quit"); + } else { + if (!Misc.poll(() => { + if (me.inTown || me.dead) return true; + let p = Game.getObject("portal"); + !!p && Misc.click(0, 0, p) && delay(100); + Misc.poll(() => me.idle, 1000, 100); + return me.inTown; + }, 700, 100)) { + // don't quit if this is a character that is allowed to die + if (Config.LifeChicken > 0) { + Misc.errorReport(new Error("Town.goToTown: Failed to go to town. Quiting.")); + scriptBroadcast("quit"); + } + } + } + } + } + + if (!act) return true; + if (act < 1 || act > 5) throw new Error("Town.goToTown: Invalid act"); + if (act > me.highestAct) return false; + + if (act !== me.act) { + try { + Pather.useWaypoint(sdk.areas.townOfAct(act), wpmenu); + } catch (WPError) { + throw new Error("Town.goToTown: Failed use WP"); + } + } + + return true; + }, + + /** + * @param {boolean} [repair] + * @returns {boolean} + */ + visitTown: function (repair = false) { + console.info(true); + + if (me.inTown) { + Town.doChores(); + Town.move("stash"); + + return true; + } + + if (!me.canTpToTown()) { + console.warn("Unable to visit town"); + return false; + } + + const preArea = me.area; + const preAct = me.act; + + // not an essential function -> handle thrown errors + try { + Town.goToTown(); + } catch (e) { + return false; + } + + Town.doChores(repair); + + me.act !== preAct && Town.goToTown(preAct); + Town.move("portalspot"); + + if (!Pather.usePortal(null, me.name)) { + try { + Pather.usePortal(preArea, me.name); + } catch (e) { + throw new Error("Town.visitTown: Failed to go back from town"); + } + } + + Config.PublicMode && Pather.makePortal(); + console.info(false, "CurrentArea: " + getAreaName(me.area)); + + return true; + } +}; diff --git a/d2bs/kolbot/libs/core/Util.js b/d2bs/kolbot/libs/core/Util.js new file mode 100644 index 000000000..30f48b059 --- /dev/null +++ b/d2bs/kolbot/libs/core/Util.js @@ -0,0 +1,634 @@ +/** + * @filename Util.js + * @author Jaenster, theBGuy + * @desc utility functions for kolbot + * + */ + +!isIncluded("Polyfill.js") && include("Polyfill.js"); + +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + const v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define([], factory); + } else { + let temp = factory(); + Object.keys(temp).forEach(key => { + if (typeof root[key] === "undefined") { + root[key] = temp[key]; + } + }); + } +}(this, function () { + function ScriptError (message) { + this.name = "ScriptError"; + this.message = message || ""; + this.stack = (new Error()).stack; + } + + ScriptError.prototype = Object.create(Error.prototype); + ScriptError.prototype.constructor = ScriptError; + + /** + * @param {...args} + * @returns {Unit[]} + */ + const getUnits = function (...args) { + let units = [], unit = getUnit.apply(null, args); + + if (!unit) { + return []; + } + do { + units.push(copyUnit(unit)); + } while (unit.getNext()); + return units; + }; + + /** + * @typedef {Object} Args + * @property {0 | 1 | 2} arg1 - where + * @property {number | ItemUnit} arg2 - bodyLoc to click, item to click, x coord + * @property {number} [arg3] - y coord + * @property {number} [arg4] - location + * + * @param {...Args} args + */ + const clickItemAndWait = function (...args) { + let timeout = getTickCount(), timedOut; + let before = !me.itemoncursor; + + clickItem.apply(undefined, args); + delay(Math.max(me.ping * 2, 250)); + + + while (true) { // Wait until item is picked up. + delay(3); + + if (before !== !!me.itemoncursor || (timedOut = getTickCount() - timeout > Math.min(1000, 100 + (me.ping * 4)))) { + break; // quit the loop of item on cursor has changed + } + } + + delay(Math.max(me.ping, 50)); + + // return item if we didnt timeout + return !timedOut; + }; + + /** + * @description clickMap doesn't return if we sucessfully clicked a unit just that a click was sent, this checks and returns that a units mode has changed + * as a result of us clicking it. + * @param {number} button + * @param {0 | 1} shift + * @param {Unit} unit + * @returns {boolean} If a units mode has changed as a result of clicking it + */ + const clickUnitAndWait = function (button, shift, unit) { + if (typeof (unit) !== "object") throw new Error("clickUnitAndWait: Third arg must be a Unit."); + + let before = unit.mode; + + me.blockMouse = true; + clickMap(button, shift, unit); + delay(Math.max(me.ping * 2, 250)); + clickMap(button + 2, shift, unit); + me.blockMouse = false; + + let waitTick = getTickCount(); + let timeOut = Math.min(1000, 100 + (me.ping * 4)); + + while (getTickCount() - waitTick < timeOut) { + delay(30); + + // quit the loop if mode has changed + if (before !== unit.mode) { + break; + } + } + + delay(Math.max(me.ping + 1, 50)); + + return (before !== unit.mode); + }; + + /** + * @class + * @description new PacketBuilder() - create new packet object + * @example (Spoof 'reassign player' packet to client): + * new PacketBuilder().byte(sdk.packets.recv.ReassignPlayer).byte(0).dword(me.gid).word(x).word(y).byte(1).get(); + * @example (Spoof 'player move' packet to server): + * new PacketBuilder().byte(sdk.packets.send.RunToLocation).word(x).word(y).send(); + * @todo pass the inital byte into the constructor so we don't always have to do `new PacketBuilder().byte(sdk.packets.recv.ReassignPlayer)...` + * it would just be `new PacketBuilder(sdk.packets.recv.ReassignPlayer)...` + */ + function PacketBuilder () { + // globals DataView ArrayBuffer + if (this.__proto__.constructor !== PacketBuilder) { + throw new Error("PacketBuilder must be called with 'new' operator!"); + } + + let queue = []; + let count = 0; + + // accepts any number of arguments + let enqueue = (type, size) => (...args) => { + args.forEach(arg => { + if (type === "String") { + arg = stringToEUC(arg); + size = arg.length + 1; + } + + queue.push({ type: type, size: size, data: arg }); + count += size; + }); + + return this; + }; + + /** @description size = 4 */ + this.float = enqueue("Float32", 4); + /** @description size = 4 */ + this.dword = enqueue("Uint32", 4); + /** @description size = 2 */ + this.word = enqueue("Uint16", 2); + /** @description size = 1 */ + this.byte = enqueue("Uint8", 1); + this.string = enqueue("String"); + + this.buildDataView = () => { + let dv = new DataView(new ArrayBuffer(count)), i = 0; + queue.forEach(field => { + if (field.type === "String") { + for (let l = 0; l < field.data.length; l++) { + dv.setUint8(i++, field.data.charCodeAt(l), true); + } + + i += field.size - field.data.length; // fix index for field.size !== field.data.length + } else { + dv["set" + field.type](i, field.data, true); + i += field.size; + } + }); + + return dv; + }; + + this.send = () => (sendPacket(this.buildDataView().buffer), this); + this.spoof = () => (getPacket(this.buildDataView().buffer), this); + this.get = this.spoof; // same thing but spoof has clearer intent than get + } + + const areaNames = [ + "None", + "Rogue Encampment", + "Blood Moor", + "Cold Plains", + "Stony Field", + "Dark Wood", + "Black Marsh", + "Tamoe Highland", + "Den Of Evil", + "Cave Level 1", + "Underground Passage Level 1", + "Hole Level 1", + "Pit Level 1", + "Cave Level 2", + "Underground Passage Level 2", + "Hole Level 2", + "Pit Level 2", + "Burial Grounds", + "Crypt", + "Mausoleum", + "Forgotten Tower", + "Tower Cellar Level 1", + "Tower Cellar Level 2", + "Tower Cellar Level 3", + "Tower Cellar Level 4", + "Tower Cellar Level 5", + "Monastery Gate", + "Outer Cloister", + "Barracks", + "Jail Level 1", + "Jail Level 2", + "Jail Level 3", + "Inner Cloister", + "Cathedral", + "Catacombs Level 1", + "Catacombs Level 2", + "Catacombs Level 3", + "Catacombs Level 4", + "Tristram", + "Moo Moo Farm", + "Lut Gholein", + "Rocky Waste", + "Dry Hills", + "Far Oasis", + "Lost City", + "Valley Of Snakes", + "Canyon Of The Magi", + "Sewers Level 1", + "Sewers Level 2", + "Sewers Level 3", + "Harem Level 1", + "Harem Level 2", + "Palace Cellar Level 1", + "Palace Cellar Level 2", + "Palace Cellar Level 3", + "Stony Tomb Level 1", + "Halls Of The Dead Level 1", + "Halls Of The Dead Level 2", + "Claw Viper Temple Level 1", + "Stony Tomb Level 2", + "Halls Of The Dead Level 3", + "Claw Viper Temple Level 2", + "Maggot Lair Level 1", + "Maggot Lair Level 2", + "Maggot Lair Level 3", + "Ancient Tunnels", + "Tal Rashas Tomb #1", + "Tal Rashas Tomb #2", + "Tal Rashas Tomb #3", + "Tal Rashas Tomb #4", + "Tal Rashas Tomb #5", + "Tal Rashas Tomb #6", + "Tal Rashas Tomb #7", + "Duriels Lair", + "Arcane Sanctuary", + "Kurast Docktown", + "Spider Forest", + "Great Marsh", + "Flayer Jungle", + "Lower Kurast", + "Kurast Bazaar", + "Upper Kurast", + "Kurast Causeway", + "Travincal", + "Spider Cave", + "Spider Cavern", + "Swampy Pit Level 1", + "Swampy Pit Level 2", + "Flayer Dungeon Level 1", + "Flayer Dungeon Level 2", + "Swampy Pit Level 3", + "Flayer Dungeon Level 3", + "Sewers Level 1", + "Sewers Level 2", + "Ruined Temple", + "Disused Fane", + "Forgotten Reliquary", + "Forgotten Temple", + "Ruined Fane", + "Disused Reliquary", + "Durance Of Hate Level 1", + "Durance Of Hate Level 2", + "Durance Of Hate Level 3", + "The Pandemonium Fortress", + "Outer Steppes", + "Plains Of Despair", + "City Of The Damned", + "River Of Flame", + "Chaos Sanctuary", + "Harrogath", + "Bloody Foothills", + "Frigid Highlands", + "Arreat Plateau", + "Crystalline Passage", + "Frozen River", + "Glacial Trail", + "Drifter Cavern", + "Frozen Tundra", + "Ancient's Way", + "Icy Cellar", + "Arreat Summit", + "Nihlathak's Temple", + "Halls Of Anguish", + "Halls Of Pain", + "Halls Of Vaught", + "Abaddon", + "Pit Of Acheron", + "Infernal Pit", + "Worldstone Keep Level 1", + "Worldstone Keep Level 2", + "Worldstone Keep Level 3", + "Throne Of Destruction", + "The Worldstone Chamber", + "Matron's Den", + "Forgotten Sands", + "Furnace of Pain", + "Tristram" + ]; + + /** @param {number} area */ + const getAreaName = function (area) { + if (["number", "string"].indexOf(typeof area) === -1) return "undefined"; + if (typeof area === "string") return area; + return (areaNames[area] || "undefined"); + }; + + /** + * Utility function to fix error tracing for getPresetUnit(s) + * @param {Error} err + * @param {number} area + * @param {number} id + */ + const rewriteStack = function (err, area, id) { + let stack = err.stack.match(/[^\r\n]+/g); + let fileNameAndLine = stack[1].substring(stack[1].lastIndexOf("\\") + 1); + let [fileName, line] = fileNameAndLine.split(":"); + err.message += " Area: " + area + " Id: " + id; + err.fileName = fileName; + err.lineNumber = line; + err.stack = err.stack.split("\n").slice(1).join("\n"); + }; + + const Game = { + getDistance: function (...args) { + switch (args.length) { + case 0: + return Infinity; + case 1: + // getDistance(unit) - returns distance that unit is from me + if (typeof args[0] !== "object") return Infinity; + if (!args[0].hasOwnProperty("x")) return Infinity; + return Math.sqrt(Math.pow((me.x - args[0].x), 2) + Math.pow((me.y - args[0].y), 2)); + case 2: + // getDistance(x, y) - returns distance x, y is from me + // getDistance(unitA, unitB) - returns distace unitA is from unitB + if (typeof args[0] === "number" && typeof args[1] === "number") { + return Math.sqrt(Math.pow((me.x - args[0]), 2) + Math.pow((me.y - args[1]), 2)); + } else if (typeof args[0] === "object" && typeof args[1] === "object") { + if (!args[1].hasOwnProperty("x")) return Infinity; + return Math.sqrt(Math.pow((args[0].x - args[1].x), 2) + Math.pow((args[0].y - args[1].y), 2)); + } + return Infinity; + case 3: + // getDistance(unit, x, y) - returns distance x, y is from unit + if (typeof args[2] !== "number") return Infinity; + if (!args[0].hasOwnProperty("x")) return Infinity; + return Math.sqrt(Math.pow((args[0].x - args[1]), 2) + Math.pow((args[0].y - args[2]), 2)); + case 4: + // getDistance(x1, y1, x2, y2) + if (typeof args[0] !== "number" || typeof args[3] !== "number") return Infinity; + return Math.sqrt(Math.pow((args[0] - args[2]), 2) + Math.pow((args[1] - args[3]), 2)); + default: + return Infinity; + } + }, + /** + * @returns {ItemUnit | undefined} item on cursor + */ + getCursorUnit: function () { + return getUnit(100); + }, + /** + * @returns {ItemUnit | undefined} item cursor is hovering over + */ + getSelectedUnit: function () { + return getUnit(101); + }, + /** + * @param {number | string} [id] + * @param {number} [mode] + * @param {number} [gid] + * @returns {Player} + */ + getPlayer: function (id, mode, gid) { + return getUnit(sdk.unittype.Player, id, mode, gid); + }, + /** + * @param {number | string} [id] + * @param {number} [mode] + * @param {number} [gid] + * @returns {Monster} + */ + getMonster: function (id, mode, gid) { + return getUnit(sdk.unittype.Monster, id, mode, gid); + }, + /** + * @param {number | string} [id] + * @param {number} [mode] + * @param {number} [gid] + * @returns {Monster} + */ + getNPC: function (id, mode, gid) { + return getUnit(sdk.unittype.NPC, id, mode, gid); + }, + /** + * @param {number | string} [id] + * @param {number} [mode] + * @param {number} [gid] + * @returns {ObjectUnit} + */ + getObject: function (id, mode, gid) { + return getUnit(sdk.unittype.Object, id, mode, gid); + }, + /** + * @param {number | string} [id] + * @param {number} [mode] + * @param {number} [gid] + * @returns {Missile} + */ + getMissile: function (id, mode, gid) { + return getUnit(sdk.unittype.Missile, id, mode, gid); + }, + /** + * @param {number | string} [id] + * @param {number} [mode] + * @param {number} [gid] + * @returns {ItemUnit} + */ + getItem: function (id, mode, gid) { + return getUnit(sdk.unittype.Item, id, mode, gid); + }, + /** + * @param {number | string} [id] + * @param {number} [mode] + * @param {number} [gid] + * @returns {Tile} + */ + getStairs: function (id, mode, gid) { + return getUnit(sdk.unittype.Stairs, id, mode, gid); + }, + /** + * @param {number} area + * @param {number} id + * @returns {PresetUnit} + */ + getPresetMonster: function (area, id) { + !area && (area = me.area); + try { + return getPresetUnit(area, sdk.unittype.Monster, id); + } catch (e) { + rewriteStack(e, area, id); + throw e; + } + }, + /** + * @param {number} area + * @param {number} id + * @returns {PresetUnit[]} + */ + getPresetMonsters: function (area, id) { + !area && (area = me.area); + try { + return getPresetUnits(area, sdk.unittype.Monster, id); + } catch (e) { + rewriteStack(e, area, id); + throw e; + } + }, + /** + * @param {number} area + * @param {number} id + * @returns {PresetUnit} + */ + getPresetObject: function (area, id) { + !area && (area = me.area); + try { + return getPresetUnit(area, sdk.unittype.Object, id); + } catch (e) { + rewriteStack(e, area, id); + throw e; + } + }, + /** + * @param {number} area + * @param {number} id + * @returns {PresetUnit[]} + */ + getPresetObjects: function (area, id) { + !area && (area = me.area); + try { + return getPresetUnits(area, sdk.unittype.Object, id); + } catch (e) { + rewriteStack(e, area, id); + throw e; + } + }, + /** + * @param {number} area + * @param {number} id + * @returns {PresetUnit} + */ + getPresetStair: function (area, id) { + !area && (area = me.area); + try { + return getPresetUnit(area, sdk.unittype.Stairs, id); + } catch (e) { + rewriteStack(e, area, id); + throw e; + } + }, + /** + * @param {number} area + * @param {number} id + * @returns {PresetUnit[]} + */ + getPresetStairs: function (area, id) { + !area && (area = me.area); + try { + return getPresetUnits(area, sdk.unittype.Stairs, id); + } catch (e) { + rewriteStack(e, area, id); + throw e; + } + }, + }; + + const Sort = { + // Sort units by comparing distance between the player + units: function (a, b) { + return Math.round(getDistance(me.x, me.y, a.x, a.y)) - Math.round(getDistance(me.x, me.y, b.x, b.y)); + }, + + // Sort preset units by comparing distance between the player (using preset x/y calculations) + presetUnits: function (a, b) { + return getDistance(me, a.roomx * 5 + a.x, a.roomy * 5 + a.y) + - getDistance(me, b.roomx * 5 + b.x, b.roomy * 5 + b.y); + }, + + // Sort arrays of x,y coords by comparing distance between the player + points: function (a, b) { + return getDistance(me, a[0], a[1]) - getDistance(me, b[0], b[1]); + }, + + numbers: function (a, b) { + return a - b; + } + }; + + const Messaging = { + sendToScript: function (name, msg) { + let script = getScript(name); + + if (script && script.running) { + script.send(msg); + + return true; + } + + return false; + }, + + sendToProfile: function (profileName, mode, message, getResponse = false) { + let response; + + function copyDataEvent (mode2, msg) { + if (mode2 === mode) { + let obj; + + try { + obj = JSON.parse(msg); + } catch (e) { + return false; + } + + if (obj.hasOwnProperty("sender") && obj.sender === profileName) { + response = copyObj(obj); + } + + return true; + } + + return false; + } + + getResponse && addEventListener("copydata", copyDataEvent); + + if (!sendCopyData(null, profileName, mode, JSON.stringify({ message: message, sender: me.profile }))) { + //console.log("sendToProfile: failed to get response from " + profileName); + getResponse && removeEventListener("copydata", copyDataEvent); + + return false; + } + + if (getResponse) { + delay(200); + removeEventListener("copydata", copyDataEvent); + + if (!!response) { + return response; + } + + return false; + } + + return true; + } + }; + + return { + getUnits: getUnits, + clickItemAndWait: clickItemAndWait, + clickUnitAndWait: clickUnitAndWait, + PacketBuilder: PacketBuilder, + getAreaName: getAreaName, + Game: Game, + Sort: Sort, + Messaging: Messaging, + ScriptError: ScriptError, + }; +})); diff --git a/d2bs/kolbot/libs/critical.js b/d2bs/kolbot/libs/critical.js new file mode 100644 index 000000000..86599aa3c --- /dev/null +++ b/d2bs/kolbot/libs/critical.js @@ -0,0 +1,11 @@ +/** +* @filename critical.js +* @author theBGuy +* @desc Simple loader file for the critical components of kolbot, without these we can't run +* +*/ + +include("json2.js"); // I don't know if this one is actually critical but including it +include("polyfill.js"); +include("globals.js"); +me.ingame ? include("oog/D2Bot.js") : include("OOG.js"); diff --git a/d2bs/kolbot/libs/globals.js b/d2bs/kolbot/libs/globals.js new file mode 100644 index 000000000..60cc3b846 --- /dev/null +++ b/d2bs/kolbot/libs/globals.js @@ -0,0 +1,299 @@ +/** +* @filename globals.js +* @author theBGuy +* @desc Globals that aren't polyfills just helpful utils that need to be accessable both in-game and out +* +*/ + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~ global d2bs helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - sdk - sdk object @see libs/modules/sdk.js + * - includeIfNotIncluded - include file if not already included + * - includeCoreLibs - include all core libs + * - includeSystemLibs - include all system driver files + * - clone - clone object + * - copyObj + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + + +if (!global.hasOwnProperty("sdk") && typeof require !== "undefined") { + Object.defineProperty(global, "sdk", { + value: require("../modules/sdk"), + enumerable: true, + }); +} + +if (!global.hasOwnProperty("includeIfNotIncluded")) { + Object.defineProperty(global, "includeIfNotIncluded", { + /** + * @param {string} file + */ + value: function (file = "") { + if (!isIncluded(file)) { + if (!include(file)) { + console.error("Failed to include " + file); + console.trace(); + return false; + } + } + return true; + }, + }); +} + +if (!global.hasOwnProperty("includeCoreLibs")) { + Object.defineProperty(global, "includeCoreLibs", { + /** + * @description includes all files from libs/core/ folder + * @param {string[]} ignoreFiles + */ + value: function (obj = { exclude: [] }) { + /** @type {string[]} */ + let files = dopen("libs/core/").getFiles(); + if (!files.length) throw new Error("Failed to find my files"); + if (!files.includes("Pather.js")) { + console.warn("Incorrect Files?", files); + // something went wrong? + while (!files.includes("Pather.js")) { + files = dopen("libs/core/").getFiles(); + delay(50); + } + } + // always include util first + includeIfNotIncluded("core/Util.js"); + files + .filter(function (file) { + return file.endsWith(".js") + && !obj.exclude.includes(file) + && !file.match("util.js", "gi"); + }) + .forEach(function (x) { + if (!includeIfNotIncluded("core/" + x)) { + throw new Error("Failed to include core/" + x); + } + }); + return true; + }, + }); +} + +if (!global.hasOwnProperty("includeSystemLibs")) { + Object.defineProperty(global, "includeSystemLibs", { + /** + * @description includes system driver files from libs/systems/ folder + */ + value: function () { + include("systems/automule/automule.js"); + include("systems/crafting/CraftingSystem.js"); + include("systems/gambling/Gambling.js"); + include("systems/torch/TorchSystem.js"); + return true; + }, + }); +} + +if (!global.hasOwnProperty("clone")) { + Object.defineProperty(global, "clone", { + /** + * @param {Date | any[] | object} obj + * @returns {ThisParameterType} deep copy of parameter + */ + value: function (obj) { + let copy; + + // Handle the 3 simple types, and null or undefined + if (null === obj || "object" !== typeof obj) { + return obj; + } + + // Handle Date + if (obj instanceof Date) { + copy = new Date(); + copy.setTime(obj.getTime()); + + return copy; + } + + // Handle Array + if (obj instanceof Array) { + copy = []; + + for (let i = 0; i < obj.length; i += 1) { + copy[i] = clone(obj[i]); + } + + return copy; + } + + // Handle Object + if (obj instanceof Object) { + copy = {}; + + for (let attr in obj) { + if (obj.hasOwnProperty(attr)) { + copy[attr] = clone(obj[attr]); + } + } + + return copy; + } + + throw new Error("Unable to copy obj! Its type isn't supported."); + }, + }); +} + +if (!global.hasOwnProperty("copyObj")) { + Object.defineProperty(global, "copyObj", { + /** + * @param {object} from + * @returns {object} deep copy + */ + value: function (from) { + let obj = {}; + + for (let i in from) { + if (from.hasOwnProperty(i)) { + obj[i] = clone(from[i]); + } + } + + return obj; + }, + }); +} + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Misc Utils ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Time - Namespace of Helper methods for dealing with time + * - isType - Method to peform simple type checks + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + +const Time = { + /** + * Converts seconds to milliseconds. + * + * @param {number} [seconds=0] - The number of seconds to convert. + * @returns {number} - The equivalent time in milliseconds. + */ + seconds: function (seconds = 0) { + if (!isType(seconds, "number")) return 0; + return (seconds * 1000); + }, + + /** + * Converts minutes to milliseconds. + * + * @param {number} [minutes=0] - The number of minutes to convert. + * @returns {number} - The equivalent time in milliseconds. + */ + minutes: function (minutes = 0) { + if (!isType(minutes, "number")) return 0; + return (minutes * 60000); + }, + + /** + * Formats milliseconds into a "HH:MM:SS" string. + * + * @param {number} [ms=0] - The time in milliseconds to format. + * @returns {string} - The formatted time string. + */ + format: function (ms = 0) { + const hours = Math.floor(ms / 3600000); + const minutes = Math.floor((ms % 3600000) / 60000); + const seconds = Math.floor((ms % 60000) / 1000); + + /** @param {number} num */ + const pad = function (num) { + return (num < 10 ? "0" + num : num); + }; + + return pad(hours) + ":" + pad(minutes) + ":" + pad(seconds); + // return (new Date(ms).toISOString().slice(11, -5)); + }, + + /** + * Converts milliseconds to seconds. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in seconds. + */ + toSeconds: function (ms = 0) { + return (ms / 1000); + }, + + /** + * Converts milliseconds to minutes. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in minutes. + */ + toMinutes: function (ms = 0) { + return (ms / 60000); + }, + + /** + * Converts milliseconds to hours. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in hours. + */ + toHours: function (ms = 0) { + return (ms / 3600000); + }, + + /** + * Converts milliseconds to days. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in days. + */ + toDays: function (ms = 0) { + return (ms / 86400000); + }, + + /** + * Calculates the elapsed time from a given timestamp. + * + * @param {number} [ms=0] - The starting time in milliseconds. + * @returns {number} - The elapsed time in milliseconds. + */ + elapsed: function (ms = 0) { + return (getTickCount() - ms); + } +}; + +/** + * @param {any} val + * @param {PrimitiveType} type + * @returns {boolean} + */ +const isType = function (val, type) { + if (type === "array") { + return Array.isArray(val); + } + return typeof val === type; +}; + +/** + * get all running threads and return them as an array + * @returns {Script[]} + */ +const getThreads = function () { + let threads = []; + let script = getScript(); + + if (script) { + do { + threads.push(copyObj(script)); + } while (script.getNext()); + } + + return threads; +}; diff --git a/d2bs/kolbot/libs/json2.js b/d2bs/kolbot/libs/json2.js index 6b87b5885..2d70be278 100644 --- a/d2bs/kolbot/libs/json2.js +++ b/d2bs/kolbot/libs/json2.js @@ -1,3 +1,4 @@ +/* eslint-disable */ /* http://www.JSON.org/json2.js 2011-10-19 @@ -161,329 +162,329 @@ var JSON; if (!JSON) { - JSON = {}; + JSON = {}; } (function () { - //'use strict'; + //'use strict'; - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - if (typeof Date.prototype.toJSON !== 'function') { + if (typeof Date.prototype.toJSON !== 'function') { - Date.prototype.toJSON = function (key) { + Date.prototype.toJSON = function (key) { - return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { - return this.valueOf(); + return this.valueOf(); }; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we must also replace the offending characters with safe escape + // sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + + // Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + + // If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. + value = value.toJSON(key); + } - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } + // If we were called with a replacer function, then call the replacer to + // obtain a replacement value. -// What happens next depends on the value's type. + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } - switch (typeof value) { - case 'string': - return quote(value); + // What happens next depends on the value's type. - case 'number': + switch (typeof value) { + case 'string': + return quote(value); -// JSON numbers must be finite. Encode non-finite numbers as null. + case 'number': - return isFinite(value) ? String(value) : 'null'; + // JSON numbers must be finite. Encode non-finite numbers as null. - case 'boolean': - case 'null': + return isFinite(value) ? String(value) : 'null'; -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. + case 'boolean': + case 'null': - return String(value); + // If the value is a boolean or null, convert it to a string. Note: + // typeof null does not produce 'null'. The case is included here in + // the remote chance that this gets fixed someday. -// If the type is 'object', we might be dealing with an object or an array or -// null. + return String(value); - case 'object': + // If the type is 'object', we might be dealing with an object or an array or + // null. -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. + case 'object': - if (!value) { - return 'null'; - } + // Due to a specification blunder in ECMAScript, typeof null is 'object', + // so watch out for that case. -// Make an array to hold the partial results of stringifying this object value. + if (!value) { + return 'null'; + } - gap += indent; - partial = []; + // Make an array to hold the partial results of stringifying this object value. -// Is the value an array? + gap += indent; + partial = []; - if (Object.prototype.toString.apply(value) === '[object Array]') { + // Is the value an array? -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. + if (Object.prototype.toString.apply(value) === '[object Array]') { - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } + // The value is an array. Stringify every element. Use null as a placeholder + // for non-JSON values. -// Join all of the elements together, separated with commas, and wrap them in -// brackets. + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } - v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; - gap = mind; - return v; + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + + // If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); } + } + } + } else { -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. + // Otherwise, iterate through all of the keys in the object. - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; - gap = mind; - return v; + } } - - return 'null'; + } + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; } -// If the JSON object does not yet have a stringify method, give it one. + return 'null'; + } - if (typeof JSON.stringify !== 'function') { - JSON.stringify = function (value, replacer, space) { + // If the JSON object does not yet have a stringify method, give it one. -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { - var i; - gap = ''; - indent = ''; + // The stringify method takes a value and an optional replacer, and an optional + // space parameter, and returns a JSON text. The replacer can be a function + // that can replace values, or an array of strings that will select the keys. + // A default replacer method can be provided. Use of the space parameter can + // produce text that is more easily readable. -// If the space parameter is a number, make an indent string containing that -// many spaces. + var i; + gap = ''; + indent = ''; - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } + // If the space parameter is a number, make an indent string containing that + // many spaces. -// If the space parameter is a string, it will be used as the indent string. + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } - } else if (typeof space === 'string') { - indent = space; - } + // If the space parameter is a string, it will be used as the indent string. -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. + } else if (typeof space === 'string') { + indent = space; + } - rep = replacer; - if (replacer && typeof replacer !== 'function' && + // If there is a replacer, it must be a function or an array. + // Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } + throw new Error('JSON.stringify'); + } -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. + // Make a fake root object containing our value under the key of ''. + // Return the result of stringifying the value. - return str('', {'': value}); - }; - } + return str('', {'': value}); + }; + } -// If the JSON object does not yet have a parse method, give it one. + // If the JSON object does not yet have a parse method, give it one. - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. + // The parse method takes a text and an optional reviver function, and returns + // a JavaScript value if the text is a valid JSON text. - var j; + var j; - function walk(holder, key) { + function walk(holder, key) { -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. + // The walk method is used to recursively walk the resulting structure so + // that modifications can be made. - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } } + } + } + return reviver.call(holder, key, value); + } -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. + // Parsing happens in four stages. In the first stage, we replace certain + // Unicode characters with escape sequences. JavaScript handles many characters + // incorrectly, either silently deleting them, or treating them as line endings. - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' - ? walk({'': j}, '') - : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } + }); + } + + // In the second stage, we run the text against regular expressions that look + // for non-JSON patterns. We are especially concerned with '()' and 'new' + // because they can cause invocation, and '=' because it can cause mutation. + // But just to be safe, we want to reject all unexpected forms. + + // We split the second stage into 4 regexp operations in order to work around + // crippling inefficiencies in IE's and Safari's regexp engines. First we + // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we + // replace all simple value tokens with ']' characters. Third, we delete all + // open brackets that follow a colon or comma or that begin the text. Finally, + // we look to see that the remaining characters are only whitespace or ']' or + // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + + // In the third stage we use the eval function to compile the text into a + // JavaScript structure. The '{' operator is subject to a syntactic ambiguity + // in JavaScript: it can begin a block or an object literal. We wrap the text + // in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + + // In the optional fourth stage, we recursively walk the new structure, passing + // each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + + // If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } }()); diff --git a/d2bs/kolbot/libs/manualplay/MapMode.js b/d2bs/kolbot/libs/manualplay/MapMode.js index 33f7103e4..56fa9e2fb 100644 --- a/d2bs/kolbot/libs/manualplay/MapMode.js +++ b/d2bs/kolbot/libs/manualplay/MapMode.js @@ -6,61 +6,61 @@ */ const MapMode = { - mapHelperFilePath: "libs/manualplay/threads/maphelper.js", - include: function () { - let files = dopen("libs/manualplay/libs/").getFiles(); - - Array.isArray(files) && files - .filter(file => file.endsWith(".js")) - .forEach(function (x) { - if (!isIncluded("manualplay/libs/" + x)) { - if (!include("manualplay/libs/" + x)) { - throw new Error("Failed to include " + "manualplay/libs/" + x); - } - } - }); - }, + mapHelperFilePath: "libs/manualplay/threads/maphelper.js", + include: function () { + let files = dopen("libs/manualplay/libs/").getFiles(); + + Array.isArray(files) && files + .filter(file => file.endsWith(".js")) + .forEach(function (x) { + if (!isIncluded("manualplay/libs/" + x)) { + if (!include("manualplay/libs/" + x)) { + throw new Error("Failed to include " + "manualplay/libs/" + x); + } + } + }); + }, - generalSettings: function () { - Config.MapMode.UseOwnItemFilter = false; // set to true if you want to start with your own nip files as the loot filter vs starting with default. - // General - Config.WaypointMenu = true; - Config.MiniShopBot = false; // Scan items in NPC shops. - Config.PacketShopping = true; // Use packets to shop. Improves shopping speed. - Config.TownCheck = false; // Go to town if out of potions - - // Additional item info log settings. All info goes to \logs\ItemLog.txt - Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. - Config.ItemInfoQuality = []; // The quality of sold items to log. See NTItemAlias.dbl for values. Example: Config.ItemInfoQuality = [6, 7, 8]; + generalSettings: function () { + Config.MapMode.UseOwnItemFilter = false; // set to true if you want to start with your own nip files as the loot filter vs starting with default. + // General + Config.WaypointMenu = true; + Config.MiniShopBot = false; // Scan items in NPC shops. + Config.PacketShopping = true; // Use packets to shop. Improves shopping speed. + Config.TownCheck = false; // Go to town if out of potions + + // Additional item info log settings. All info goes to \logs\ItemLog.txt + Config.ItemInfo = false; // Log stashed, skipped (due to no space) or sold items. + Config.ItemInfoQuality = []; // The quality of sold items to log. See core/GameData/NTItemAlias.js for values. Example: Config.ItemInfoQuality = [6, 7, 8]; - // Manager Item Log Screen - Config.LogKeys = false; // Log keys on item viewer - Config.LogOrgans = true; // Log organs on item viewer - Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer - Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer - Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer - Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer - Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer - Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. - Config.ShowCubingInfo = true; // Show cubing messages on console + // Manager Item Log Screen + Config.LogKeys = false; // Log keys on item viewer + Config.LogOrgans = true; // Log organs on item viewer + Config.LogLowRunes = false; // Log low runes (El - Dol) on item viewer + Config.LogMiddleRunes = false; // Log middle runes (Hel - Mal) on item viewer + Config.LogHighRunes = true; // Log high runes (Ist - Zod) on item viewer + Config.LogLowGems = false; // Log low gems (chipped, flawed, normal) on item viewer + Config.LogHighGems = false; // Log high gems (flawless, perfect) on item viewer + Config.SkipLogging = []; // Custom log skip list. Set as three digit item code or classid. Example: ["tes", "ceh", 656, 657] will ignore logging of essences. + Config.ShowCubingInfo = true; // Show cubing messages on console - // Gambling config - Config.Gamble = false; - Config.GambleGoldStart = 1000000; - Config.GambleGoldStop = 500000; + // Gambling config + Config.Gamble = false; + Config.GambleGoldStart = 1000000; + Config.GambleGoldStop = 500000; - // List of item names or classids for gambling. Check libs/NTItemAlias.dbl file for other item classids. - Config.GambleItems.push("Amulet"); - Config.GambleItems.push("Ring"); - Config.GambleItems.push("Circlet"); - Config.GambleItems.push("Coronet"); + // List of item names or classids for gambling. Check libs/core/GameData/NTItemAlias.js file for other item classids. + Config.GambleItems.push("Amulet"); + Config.GambleItems.push("Ring"); + Config.GambleItems.push("Circlet"); + Config.GambleItems.push("Coronet"); - // Party message settings. Each setting represents an array of messages that will be randomly chosen. - // $name, $level, $class and $killer are replaced by the player's name, level, class and killer - Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] - Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] - Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] - Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. - Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. - }, + // Party message settings. Each setting represents an array of messages that will be randomly chosen. + // $name, $level, $class and $killer are replaced by the player's name, level, class and killer + Config.Greetings = []; // Example: ["Hello, $name (level $level $class)"] + Config.DeathMessages = []; // Example: ["Watch out for that $killer, $name!"] + Config.Congratulations = []; // Example: ["Congrats on level $level, $name!"] + Config.ShitList = false; // Blacklist hostile players so they don't get invited to party. + Config.UnpartyShitlisted = false; // Leave party if someone invited a blacklisted player. + }, }; diff --git a/d2bs/kolbot/libs/manualplay/config/Amazon.js b/d2bs/kolbot/libs/manualplay/config/Amazon.js index 68ea43d45..9e35ceb1c 100644 --- a/d2bs/kolbot/libs/manualplay/config/Amazon.js +++ b/d2bs/kolbot/libs/manualplay/config/Amazon.js @@ -18,194 +18,194 @@ include("manualplay/MapMode.js"); function LoadConfig() { - MapMode.generalSettings(); - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - // Chicken settings - Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - - /* Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "mp", "mp", "rv"]; - - // Pickit config. Default folder is kolbot/pickit. - Config.PickitFiles.push("kolton.nip"); - Config.PickitFiles.push("LLD.nip"); - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - - // Public game options - - // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - // LocalChat messages will only be visible on clients running on the same PC - Config.LocalChat.Enabled = false; // enable the LocalChat system - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // MF Switch - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 35; BAD: Config.AttackSkill[1] = -35; - */ - - Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - // Class specific config - Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill. - Config.SummonValkyrie = true; // Summon Valkyrie - - /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + MapMode.generalSettings(); + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + // Chicken settings + Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + + /* Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "mp", "mp", "rv"]; + + // Pickit config. Default folder is kolbot/pickit. + Config.PickitFiles.push("kolton.nip"); + Config.PickitFiles.push("LLD.nip"); + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + + // Public game options + + // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + // LocalChat messages will only be visible on clients running on the same PC + Config.LocalChat.Enabled = false; // enable the LocalChat system + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // MF Switch + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 35; BAD: Config.AttackSkill[1] = -35; + */ + + Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /* Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + //"Monster Name": [-1, -1] + }; + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + // Class specific config + Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill. + Config.SummonValkyrie = true; // Summon Valkyrie + + /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/manualplay/config/Assassin.js b/d2bs/kolbot/libs/manualplay/config/Assassin.js index c1370b40e..2ab138f75 100644 --- a/d2bs/kolbot/libs/manualplay/config/Assassin.js +++ b/d2bs/kolbot/libs/manualplay/config/Assassin.js @@ -18,203 +18,203 @@ include("manualplay/MapMode.js"); function LoadConfig() { - MapMode.generalSettings(); - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - // Chicken settings - Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - - /* Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "mp", "mp", "rv"]; - - // Pickit config. Default folder is kolbot/pickit. - Config.PickitFiles.push("kolton.nip"); - Config.PickitFiles.push("LLD.nip"); - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - - // Public game options - - // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - // LocalChat messages will only be visible on clients running on the same PC - Config.LocalChat.Enabled = false; // enable the LocalChat system - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // MF Switch - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 251; BAD: Config.AttackSkill[1] = -251; - * Don't put LS/DS/WoF/WoI here! Use Config. UseTraps, Config.Traps and Config.BossTraps - */ - - Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - // Class specific config - Config.UseTraps = true; // Set to true to use traps - Config.Traps = [271, 271, 271, 276, 276]; // Skill IDs for traps to be cast on all mosters except act bosses. - Config.BossTraps = [271, 271, 271, 271, 271]; // Skill IDs for traps to be cast on act bosses. - - Config.SummonShadow = "Master"; // 0 = don't summon, 1 or "Warrior" = summon Shadow Warrior, 2 or "Master" = summon Shadow Master - Config.UseFade = true; // Set to true to use Fade prebuff. - Config.UseBoS = false; // Set to true to use Burst of Speed prebuff. TODO: Casting in town + UseFade compatibility - Config.UseVenom = false; // Set to true to use Venom prebuff. Set to false if you don't have the skill and have Arachnid Mesh - it will cause connection drop otherwise. - Config.UseCloakofShadows = true; // Set to true to use Cloak of Shadows while fighting. Useful for blinding regular monsters/minions. - Config.AggressiveCloak = false; // Move into Cloak range or cast if already close - - /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + MapMode.generalSettings(); + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + // Chicken settings + Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + + /* Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "mp", "mp", "rv"]; + + // Pickit config. Default folder is kolbot/pickit. + Config.PickitFiles.push("kolton.nip"); + Config.PickitFiles.push("LLD.nip"); + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + + // Public game options + + // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + // LocalChat messages will only be visible on clients running on the same PC + Config.LocalChat.Enabled = false; // enable the LocalChat system + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // MF Switch + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 251; BAD: Config.AttackSkill[1] = -251; + * Don't put LS/DS/WoF/WoI here! Use Config. UseTraps, Config.Traps and Config.BossTraps + */ + + Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /* Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + //"Monster Name": [-1, -1] + }; + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + // Class specific config + Config.UseTraps = true; // Set to true to use traps + Config.Traps = [271, 271, 271, 276, 276]; // Skill IDs for traps to be cast on all mosters except act bosses. + Config.BossTraps = [271, 271, 271, 271, 271]; // Skill IDs for traps to be cast on act bosses. + + Config.SummonShadow = "Master"; // 0 = don't summon, 1 or "Warrior" = summon Shadow Warrior, 2 or "Master" = summon Shadow Master + Config.UseFade = true; // Set to true to use Fade prebuff. + Config.UseBoS = false; // Set to true to use Burst of Speed prebuff. TODO: Casting in town + UseFade compatibility + Config.UseVenom = false; // Set to true to use Venom prebuff. Set to false if you don't have the skill and have Arachnid Mesh - it will cause connection drop otherwise. + Config.UseCloakofShadows = true; // Set to true to use Cloak of Shadows while fighting. Useful for blinding regular monsters/minions. + Config.AggressiveCloak = false; // Move into Cloak range or cast if already close + + /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/manualplay/config/Barbarian.js b/d2bs/kolbot/libs/manualplay/config/Barbarian.js index bcd41010e..58aac3409 100644 --- a/d2bs/kolbot/libs/manualplay/config/Barbarian.js +++ b/d2bs/kolbot/libs/manualplay/config/Barbarian.js @@ -18,192 +18,192 @@ include("manualplay/MapMode.js"); function LoadConfig() { - MapMode.generalSettings(); - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - // Chicken settings - Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - - /* Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "mp", "mp", "rv"]; - - // Pickit config. Default folder is kolbot/pickit. - Config.PickitFiles.push("kolton.nip"); - Config.PickitFiles.push("LLD.nip"); - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - - // Public game options - - // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - // LocalChat messages will only be visible on clients running on the same PC - Config.LocalChat.Enabled = false; // enable the LocalChat system - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // MF Switch - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 151; BAD: Config.AttackSkill[1] = -151; - */ - - Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill for bosses. - Config.AttackSkill[2] = -1; // Backup/Immune skill for bosses. - Config.AttackSkill[3] = -1; // Primary skill for others. - Config.AttackSkill[4] = -1; // Backup/Immune skill for others. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - // Class specific config - Config.FindItem = false; // Use Find Item skill on corpses after clearing. - Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills - Config.UseWarcries = true; // use battle orders, battle command, and shout if we have them - - /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + MapMode.generalSettings(); + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + // Chicken settings + Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + + /* Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "mp", "mp", "rv"]; + + // Pickit config. Default folder is kolbot/pickit. + Config.PickitFiles.push("kolton.nip"); + Config.PickitFiles.push("LLD.nip"); + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + + // Public game options + + // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + // LocalChat messages will only be visible on clients running on the same PC + Config.LocalChat.Enabled = false; // enable the LocalChat system + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 1; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // MF Switch + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 151; BAD: Config.AttackSkill[1] = -151; + */ + + Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill for bosses. + Config.AttackSkill[2] = -1; // Backup/Immune skill for bosses. + Config.AttackSkill[3] = -1; // Primary skill for others. + Config.AttackSkill[4] = -1; // Backup/Immune skill for others. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Low mana skill. + + /* Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + //"Monster Name": [-1, -1] + }; + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + // Class specific config + Config.FindItem = false; // Use Find Item skill on corpses after clearing. + Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills + Config.UseWarcries = true; // use battle orders, battle command, and shout if we have them + + /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/manualplay/config/Druid.js b/d2bs/kolbot/libs/manualplay/config/Druid.js index b8bb80272..c51606e04 100644 --- a/d2bs/kolbot/libs/manualplay/config/Druid.js +++ b/d2bs/kolbot/libs/manualplay/config/Druid.js @@ -18,196 +18,196 @@ include("manualplay/MapMode.js"); function LoadConfig() { - MapMode.generalSettings(); - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - // Chicken settings - Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - - /* Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "mp", "mp", "rv"]; - - // Pickit config. Default folder is kolbot/pickit. - Config.PickitFiles.push("kolton.nip"); - Config.PickitFiles.push("LLD.nip"); - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - - // Public game options - - // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - // LocalChat messages will only be visible on clients running on the same PC - Config.LocalChat.Enabled = false; // enable the LocalChat system - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // MF Switch - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 245; BAD: Config.AttackSkill[1] = -245; - */ - - Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - // Class specific config - Config.SummonRaven = false; - Config.SummonAnimal = "Grizzly"; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly - Config.SummonSpirit = "Oak Sage"; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs" - Config.SummonVine = "Poison Creeper"; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper" - - /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + MapMode.generalSettings(); + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + // Chicken settings + Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + + /* Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "mp", "mp", "rv"]; + + // Pickit config. Default folder is kolbot/pickit. + Config.PickitFiles.push("kolton.nip"); + Config.PickitFiles.push("LLD.nip"); + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + + // Public game options + + // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + // LocalChat messages will only be visible on clients running on the same PC + Config.LocalChat.Enabled = false; // enable the LocalChat system + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // MF Switch + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 245; BAD: Config.AttackSkill[1] = -245; + */ + + Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /* Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + //"Monster Name": [-1, -1] + }; + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + // Class specific config + Config.SummonRaven = false; + Config.SummonAnimal = "Grizzly"; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly + Config.SummonSpirit = "Oak Sage"; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs" + Config.SummonVine = "Poison Creeper"; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper" + + /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/manualplay/config/Necromancer.js b/d2bs/kolbot/libs/manualplay/config/Necromancer.js index 5f561b22d..727e69d5b 100644 --- a/d2bs/kolbot/libs/manualplay/config/Necromancer.js +++ b/d2bs/kolbot/libs/manualplay/config/Necromancer.js @@ -18,217 +18,217 @@ include("manualplay/MapMode.js"); function LoadConfig() { - MapMode.generalSettings(); - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - // Chicken settings - Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - - /* Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "mp", "mp", "rv"]; - - // Pickit config. Default folder is kolbot/pickit. - Config.PickitFiles.push("kolton.nip"); - Config.PickitFiles.push("LLD.nip"); - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - - // Public game options - - // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - // LocalChat messages will only be visible on clients running on the same PC - Config.LocalChat.Enabled = false; // enable the LocalChat system - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // MF Switch - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 84; BAD: Config.AttackSkill[1] = -84; - */ - - Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - // Class specific config - Config.Curse[0] = 0; // Boss curse. Use skill number or set to 0 to disable. - Config.Curse[1] = 0; // Other monsters curse. Use skill number or set to 0 to disable. - - /* Custom curses for monster - * Can use monster name or classid - * Format: Config.CustomCurse = [["monstername", skillid], [156, skillid]]; - * Optional 3rd parameter for spectype, leave blank to use on all - 0x00 Normal Monster - 0x01 Super Unique - 0x02 Champion - 0x04 Boss - 0x08 Minion - Example: Config.CustomCurse = [["HellBovine", 60], [571, 87], ["SkeletonArcher", 71, 0x00]]; - */ - Config.CustomCurse = []; - - Config.ExplodeCorpses = 0; // Explode corpses. Use skill number or 0 to disable. 74 = Corpse Explosion, 83 = Poison Explosion - Config.Golem = "None"; // Golem. 0 or "None" = don't summon, 1 or "Clay" = Clay Golem, 2 or "Blood" = Blood Golem, 3 or "Fire" = Fire Golem - Config.Skeletons = 0; // Number of skeletons to raise. Set to "max" to auto detect, set to 0 to disable. - Config.SkeletonMages = 0; // Number of skeleton mages to raise. Set to "max" to auto detect, set to 0 to disable. - Config.Revives = 0; // Number of revives to raise. Set to "max" to auto detect, set to 0 to disable. - Config.PoisonNovaDelay = 2; // Delay between two Poison Novas in seconds. - Config.ActiveSummon = false; // Raise dead between each attack. If false, it will raise after clearing a spot. - Config.ReviveUnstackable = true; // Revive monsters that can move freely after you teleport. - Config.IronGolemChicken = 30; // Exit game if Iron Golem's life is less or equal to designated percent. - - /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + MapMode.generalSettings(); + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + // Chicken settings + Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + + /* Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "mp", "mp", "rv"]; + + // Pickit config. Default folder is kolbot/pickit. + Config.PickitFiles.push("kolton.nip"); + Config.PickitFiles.push("LLD.nip"); + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + + // Public game options + + // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + // LocalChat messages will only be visible on clients running on the same PC + Config.LocalChat.Enabled = false; // enable the LocalChat system + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // MF Switch + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 84; BAD: Config.AttackSkill[1] = -84; + */ + + Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /* Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + //"Monster Name": [-1, -1] + }; + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + // Class specific config + Config.Curse[0] = 0; // Boss curse. Use skill number or set to 0 to disable. + Config.Curse[1] = 0; // Other monsters curse. Use skill number or set to 0 to disable. + + /* Custom curses for monster + * Can use monster name or classid + * Format: Config.CustomCurse = [["monstername", skillid], [156, skillid]]; + * Optional 3rd parameter for spectype, leave blank to use on all + 0x00 Normal Monster + 0x01 Super Unique + 0x02 Champion + 0x04 Boss + 0x08 Minion + Example: Config.CustomCurse = [["HellBovine", 60], [571, 87], ["SkeletonArcher", 71, 0x00]]; + */ + Config.CustomCurse = []; + + Config.ExplodeCorpses = 0; // Explode corpses. Use skill number or 0 to disable. 74 = Corpse Explosion, 83 = Poison Explosion + Config.Golem = "None"; // Golem. 0 or "None" = don't summon, 1 or "Clay" = Clay Golem, 2 or "Blood" = Blood Golem, 3 or "Fire" = Fire Golem + Config.Skeletons = 0; // Number of skeletons to raise. Set to "max" to auto detect, set to 0 to disable. + Config.SkeletonMages = 0; // Number of skeleton mages to raise. Set to "max" to auto detect, set to 0 to disable. + Config.Revives = 0; // Number of revives to raise. Set to "max" to auto detect, set to 0 to disable. + Config.PoisonNovaDelay = 2; // Delay between two Poison Novas in seconds. + Config.ActiveSummon = false; // Raise dead between each attack. If false, it will raise after clearing a spot. + Config.ReviveUnstackable = true; // Revive monsters that can move freely after you teleport. + Config.IronGolemChicken = 30; // Exit game if Iron Golem's life is less or equal to designated percent. + + /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/manualplay/config/Paladin.js b/d2bs/kolbot/libs/manualplay/config/Paladin.js index 7b5b887e1..cffec69f3 100644 --- a/d2bs/kolbot/libs/manualplay/config/Paladin.js +++ b/d2bs/kolbot/libs/manualplay/config/Paladin.js @@ -18,196 +18,196 @@ include("manualplay/MapMode.js"); function LoadConfig() { - MapMode.generalSettings(); - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - // Chicken settings - Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - - /* Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "mp", "mp", "rv"]; - - // Pickit config. Default folder is kolbot/pickit. - Config.PickitFiles.push("kolton.nip"); - Config.PickitFiles.push("LLD.nip"); - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - - // Public game options - - // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - // LocalChat messages will only be visible on clients running on the same PC - Config.LocalChat.Enabled = false; // enable the LocalChat system - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // MF Switch - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 96; BAD: Config.AttackSkill[1] = -96; - */ - - Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - // Class specific config - Config.AvoidDolls = false; // Try to attack dolls from a greater distance with hammerdins. - Config.Vigor = true; // Swith to Vigor when running - Config.Charge = true; // Use Charge when running - Config.Redemption = [50, 50]; // Switch to Redemption after clearing an area if under designated life or mana. Format: [lifepercent, manapercent] - - /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + MapMode.generalSettings(); + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + // Chicken settings + Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + + /* Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "mp", "mp", "rv"]; + + // Pickit config. Default folder is kolbot/pickit. + Config.PickitFiles.push("kolton.nip"); + Config.PickitFiles.push("LLD.nip"); + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + + // Public game options + + // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + // LocalChat messages will only be visible on clients running on the same PC + Config.LocalChat.Enabled = false; // enable the LocalChat system + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // MF Switch + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 96; BAD: Config.AttackSkill[1] = -96; + */ + + Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /* Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + //"Monster Name": [-1, -1] + }; + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + // Class specific config + Config.AvoidDolls = false; // Try to attack dolls from a greater distance with hammerdins. + Config.Vigor = true; // Swith to Vigor when running + Config.Charge = true; // Use Charge when running + Config.Redemption = [50, 50]; // Switch to Redemption after clearing an area if under designated life or mana. Format: [lifepercent, manapercent] + + /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/manualplay/config/Sorceress.js b/d2bs/kolbot/libs/manualplay/config/Sorceress.js index b8cd8c8ee..890cec220 100644 --- a/d2bs/kolbot/libs/manualplay/config/Sorceress.js +++ b/d2bs/kolbot/libs/manualplay/config/Sorceress.js @@ -18,195 +18,198 @@ include("manualplay/MapMode.js"); function LoadConfig() { - MapMode.generalSettings(); - - // Town settings - Config.HealHP = 50; // Go to a healer if under designated percent of life. - Config.HealMP = 0; // Go to a healer if under designated percent of mana. - Config.HealStatus = false; // Go to a healer if poisoned or cursed - Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. - Config.MercWatch = false; // Instant merc revive during battle. - - // Potion settings - Config.UseHP = 75; // Drink a healing potion if life is under designated percent. - Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. - Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. - Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. - Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. - Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. - Config.HPBuffer = 0; // Number of healing potions to keep in inventory. - Config.MPBuffer = 0; // Number of mana potions to keep in inventory. - Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. - - // Chicken settings - Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. - Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. - Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. - Config.TownHP = 0; // Go to town if life is under designated percent. - Config.TownMP = 0; // Go to town if mana is under designated percent. - - /* Inventory lock configuration. !!!READ CAREFULLY!!! - * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. - * Put 0s where your torch, annihilus and everything else you want to KEEP is. - * 1 = item is unlocked and will be dropped, stashed or sold. - * If you don't change the default values, the bot won't stash items. - */ - Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - /* Potion types for belt columns from left to right. - * Rejuvenation potions must always be rightmost. - * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") - */ - Config.BeltColumn = ["hp", "mp", "mp", "rv"]; - - // Pickit config. Default folder is kolbot/pickit. - Config.PickitFiles.push("kolton.nip"); - Config.PickitFiles.push("LLD.nip"); - Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. - - // Public game options - - // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET - // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET - // LocalChat messages will only be visible on clients running on the same PC - Config.LocalChat.Enabled = false; // enable the LocalChat system - Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 - Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) - - // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. - // If set on true, it simply parties. - Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. - - // General config - Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. - Config.OpenChests.Enabled = false; // Open chests. Controls key buying. - Config.PingQuit = [{Ping: 0, Duration: 0}]; // Quit if ping is over the given value for over the given time period in seconds. - - // Shrine Scanner - scan for shrines while moving. - // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/shrines.txt - Config.ScanShrines = []; - - // MF Switch - Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. - - // Anti-hostile config - Config.AntiHostile = false; // Enable anti-hostile - Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile - Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 - Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted - - // Monster skip config - // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". - // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" - Config.SkipImmune = []; - // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". - // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" - Config.SkipEnchant = []; - // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. - Config.SkipAura = []; - // Uncomment the following line to always attempt to kill these bosses despite immunities and mods - //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector - - /* Attack config - * To disable an attack, set it to -1 - * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt - * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 56; BAD: Config.AttackSkill[1] = -56; - */ - - Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II - Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. - - Config.AttackSkill[0] = -1; // Preattack skill. - Config.AttackSkill[1] = -1; // Primary skill to bosses. - Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. - Config.AttackSkill[3] = -1; // Primary skill to others. - Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. - Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. - Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. - - // Low mana skills - these will be used if main skills can't be cast. - Config.LowManaSkill[0] = -1; // Timed low mana skill. - Config.LowManaSkill[1] = -1; // Untimed low mana skill. - - /* Advanced Attack config. Allows custom skills to be used on custom monsters. - * Format: "Monster Name": [timed skill id, untimed skill id] - * Multiple entries are separated by commas - */ - Config.CustomAttack = { - //"Monster Name": [-1, -1] - }; - - Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars - Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. - Config.DodgeRange = 15; // Distance to keep from monsters. - Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. - Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing - Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all - Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage - - // Clear while traveling during bot scripts - // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 - // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, - // all areas will be cleared using the specified range and spectype - // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // Config.ClearPath = { - // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas - // Range: 30, // Range to clear while traveling - // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all - // }; - - // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. - Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear - - // Class specific config - Config.CastStatic = 60; // Cast static until the target is at designated life percent. 100 = disabled. - Config.StaticList = []; // List of monster NAMES or CLASSIDS to static. Example: Config.StaticList = ["Andariel", 243]; - Config.UseTelekinesis = true; // Use telekinesis on units that allow it. Example: Shrines, Waypoints, Chests, and Portals - - /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. - * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. - * - * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; - * skill - skill id number (see /sdk/skills.txt) - * count - maximum number of skill points to allocate for that skill - * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. - * - * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. - */ - Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system - Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved - Config.AutoSkill.Build = []; - - /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. - * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. - * - * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; - * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 - * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). - * You can also set stat to string value "all", and it will spend all the remaining points. - * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). - * - * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. - */ - Config.AutoStat.Enabled = false; // Enable or disable AutoStat system - Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. - Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. - Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. - Config.AutoStat.Build = []; - - // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) - Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system - - // The name of the build associated with an existing - // template filename located in libs/config/Builds/ - Config.AutoBuild.Template = "BuildName"; - // Allows script to print messages in console - Config.AutoBuild.Verbose = true; - // Debug mode prints a little more information to console and - // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log - // It automatically enables Config.AutoBuild.Verbose - Config.AutoBuild.DebugMode = true; + MapMode.generalSettings(); + + // Town settings + Config.HealHP = 50; // Go to a healer if under designated percent of life. + Config.HealMP = 0; // Go to a healer if under designated percent of mana. + Config.HealStatus = false; // Go to a healer if poisoned or cursed + Config.UseMerc = true; // Use merc. This is ignored and always false in d2classic. + Config.MercWatch = false; // Instant merc revive during battle. + + // Potion settings + Config.UseHP = 75; // Drink a healing potion if life is under designated percent. + Config.UseRejuvHP = 40; // Drink a rejuvenation potion if life is under designated percent. + Config.UseMP = 30; // Drink a mana potion if mana is under designated percent. + Config.UseRejuvMP = 0; // Drink a rejuvenation potion if mana is under designated percent. + Config.UseMercHP = 75; // Give a healing potion to your merc if his/her life is under designated percent. + Config.UseMercRejuv = 0; // Give a rejuvenation potion to your merc if his/her life is under designated percent. + Config.HPBuffer = 0; // Number of healing potions to keep in inventory. + Config.MPBuffer = 0; // Number of mana potions to keep in inventory. + Config.RejuvBuffer = 0; // Number of rejuvenation potions to keep in inventory. + + // Chicken settings + Config.LifeChicken = 0; // Exit game if life is less or equal to designated percent. + Config.ManaChicken = 0; // Exit game if mana is less or equal to designated percent. + Config.MercChicken = 0; // Exit game if merc's life is less or equal to designated percent. + Config.TownHP = 0; // Go to town if life is under designated percent. + Config.TownMP = 0; // Go to town if mana is under designated percent. + + /* Inventory lock configuration. !!!READ CAREFULLY!!! + * 0 = item is locked and won't be moved. If item occupies more than one slot, ALL of those slots must be set to 0 to lock it in place. + * Put 0s where your torch, annihilus and everything else you want to KEEP is. + * 1 = item is unlocked and will be dropped, stashed or sold. + * If you don't change the default values, the bot won't stash items. + */ + Config.Inventory[0] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[2] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + Config.Inventory[3] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + /* Potion types for belt columns from left to right. + * Rejuvenation potions must always be rightmost. + * Supported potions - Healing ("hp"), Mana ("mp") and Rejuvenation ("rv") + */ + Config.BeltColumn = ["hp", "mp", "mp", "rv"]; + + // Pickit config. Default folder is kolbot/pickit. + Config.PickitFiles.push("kolton.nip"); + Config.PickitFiles.push("LLD.nip"); + Config.ManualPlayPick = false; // If set to true and D2BotMap entry script is used, will enable picking in manual play. + + // Public game options + Config.QuitList = ["unfairsocks"]; + Config.QuitListMode = 0; // 0 = use character names; 1 = use profile names (all profiles must run on the same computer). + Config.QuitListDelay = [15, 30]; + + // If LocalChat is enabled, chat can be sent via 'sendCopyData' instead of BNET + // To allow 'say' to use BNET, use 'say("msg", true)', the 2nd parameter will force BNET + // LocalChat messages will only be visible on clients running on the same PC + Config.LocalChat.Enabled = false; // enable the LocalChat system + Config.LocalChat.Toggle = false; // optional, set to KEY value to toggle through modes 0, 1, 2 + Config.LocalChat.Mode = 0; // 0 = disabled, 1 = chat from 'say' (recommended), 2 = all chat (for manual play) + + // If Config.Leader is set, the bot will only accept invites from leader. If Config.PublicMode is not 0, Baal and Diablo script will open Town Portals. + // If set on true, it simply parties. + Config.PublicMode = 0; // 1 = invite and accept, 2 = accept only, 3 = invite only, 0 = disable. + + // General config + Config.TeleSwitch = false; // Switch to secondary (non-primary) slot when teleporting more than 5 nodes. + Config.OpenChests.Enabled = false; // Open chests. Controls key buying. + Config.PingQuit = [{ Ping: 0, Duration: 0 }]; // Quit if ping is over the given value for over the given time period in seconds. + + // Shrine Scanner - scan for shrines while moving. + // Put the shrine types in order of priority (from highest to lowest). For a list of types, see sdk/txt/shrines.txt + Config.ScanShrines = []; + + // MF Switch + Config.MFSwitchPercent = 0; // Boss life % to switch to non-primary weapon slot. Set to 0 to disable. + + // Anti-hostile config + Config.AntiHostile = false; // Enable anti-hostile + Config.HostileAction = 0; // 0 - quit immediately, 1 - quit when hostile player is sighted, 2 - attack hostile + Config.TownOnHostile = false; // Go to town instead of quitting when HostileAction is 0 or 1 + Config.ViperCheck = false; // Quit if revived Tomb Vipers are sighted + + // Monster skip config + // Skip immune monsters. Possible options: "fire", "cold", "lightning", "poison", "physical", "magic". + // You can combine multiple resists with "and", for example - "fire and cold", "physical and cold and poison" + Config.SkipImmune = []; + // Skip enchanted monsters. Possible options: "extra strong", "extra fast", "cursed", "magic resistant", "fire enchanted", "lightning enchanted", "cold enchanted", "mana burn", "teleportation", "spectral hit", "stone skin", "multiple shots". + // You can combine multiple enchantments with "and", for example - "cursed and extra fast", "mana burn and extra strong and lightning enchanted" + Config.SkipEnchant = []; + // Skip monsters with auras. Possible options: "fanaticism", "might", "holy fire", "blessed aim", "holy freeze", "holy shock". Conviction is bugged, don't use it. + Config.SkipAura = []; + // Uncomment the following line to always attempt to kill these bosses despite immunities and mods + //Config.SkipException = [getLocaleString(sdk.locale.monsters.GrandVizierofChaos), getLocaleString(sdk.locale.monsters.LordDeSeis), getLocaleString(sdk.locale.monsters.InfectorofSouls)]; // vizier, de seis, infector + + /* Attack config + * To disable an attack, set it to -1 + * Skills MUST be POSITIVE numbers. For reference see ...\kolbot\sdk\skills.txt + * DO NOT LEAVE THE NEGATIVE SIGN IN FRONT OF THE SKILLID. GOOD: Config.AttackSkill[1] = 56; BAD: Config.AttackSkill[1] = -56; + */ + + Config.PrimarySlot = -1; // Set to use specific weapon slot as primary weapon slot: -1 = disabled, 0 = slot I, 1 = slot II + Config.PacketCasting = 0; // 0 = disable, 1 = packet teleport, 2 = full packet casting. + + Config.AttackSkill[0] = -1; // Preattack skill. + Config.AttackSkill[1] = -1; // Primary skill to bosses. + Config.AttackSkill[2] = -1; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. + Config.AttackSkill[3] = -1; // Primary skill to others. + Config.AttackSkill[4] = -1; // Primary untimed skill to others. Keep at -1 if Config.AttackSkill[3] is untimed skill. + Config.AttackSkill[5] = -1; // Secondary skill if monster is immune to primary. + Config.AttackSkill[6] = -1; // Secondary untimed skill if monster is immune to primary untimed. + + // Low mana skills - these will be used if main skills can't be cast. + Config.LowManaSkill[0] = -1; // Timed low mana skill. + Config.LowManaSkill[1] = -1; // Untimed low mana skill. + + /* Advanced Attack config. Allows custom skills to be used on custom monsters. + * Format: "Monster Name": [timed skill id, untimed skill id] + * Multiple entries are separated by commas + */ + Config.CustomAttack = { + //"Monster Name": [-1, -1] + }; + + Config.NoTele = false; // Restrict char from teleporting. Useful for low level/low mana chars + Config.Dodge = false; // Move away from monsters that get too close. Don't use with short-ranged attacks like Poison Dagger. + Config.DodgeRange = 15; // Distance to keep from monsters. + Config.DodgeHP = 100; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge. + Config.BossPriority = false; // Set to true to attack Unique/SuperUnique monsters first when clearing + Config.ClearType = 0xF; // Monster spectype to kill in level clear scripts (ie. Mausoleum). 0xF = skip normal, 0x7 = champions/bosses, 0 = all + Config.TeleStomp = false; // Use merc to attack bosses if they're immune to attacks, but not to physical damage + + // Clear while traveling during bot scripts + // You have two methods to configure clearing. First is simply a spectype to always clear, in any area, with a default range of 30 + // The second method allows you to specify the areas in which to clear while traveling, a range, and a spectype. If area is excluded from this method, + // all areas will be cleared using the specified range and spectype + // Config.ClearPath = 0; // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // Config.ClearPath = { + // Areas: [74], // Specific areas to clear while traveling in. Comment out to clear in all areas + // Range: 30, // Range to clear while traveling + // Spectype: 0, // Monster spectype to kill while traveling. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + // }; + + // Wereform setup. Make sure you read Templates/Attacks.txt for attack skill format. + Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear + + // Class specific config + Config.CastStatic = 60; // Cast static until the target is at designated life percent. 100 = disabled. + Config.StaticList = []; // List of monster NAMES or CLASSIDS to static. Example: Config.StaticList = ["Andariel", 243]; + Config.UseTelekinesis = true; // Use telekinesis on units that allow it. Example: Shrines, Waypoints, Chests, and Portals + + /* AutoSkill builds character based on array defined by the user and it replaces AutoBuild's skill system. + * AutoSkill will automatically spend skill points and it can also allocate any prerequisite skills as required. + * + * Format: Config.AutoSkill.Build = [[skillID, count, satisfy], [skillID, count, satisfy], ... [skillID, count, satisfy]]; + * skill - skill id number (see /sdk/txt/skills.txt) + * count - maximum number of skill points to allocate for that skill + * satisfy - boolean value to stop(true) or continue(false) further allocation until count is met. Defaults to true if not specified. + * + * See libs/config/Templates/AutoSkillExampleBuilds.txt for Config.AutoSkill.Build examples. + */ + Config.AutoSkill.Enabled = false; // Enable or disable AutoSkill system + Config.AutoSkill.Save = 0; // Number of skill points that will not be spent and saved + Config.AutoSkill.Build = []; + + /* AutoStat builds character based on array defined by the user and this will replace AutoBuild's stat system. + * AutoStat will stat Build array order. You may want to stat strength or dexterity first to meet item requirements. + * + * Format: Config.AutoStat.Build = [[statType, stat], [statType, stat], ... [statType, stat]]; + * statType - defined as string, or as corresponding stat integer. "strength" or 0, "dexterity" or 2, "vitality" or 3, "energy" or 1 + * stat - set to an integer value, and it will spend stat points until it reaches desired *hard stat value (*+stats from items are ignored). + * You can also set stat to string value "all", and it will spend all the remaining points. + * Dexterity can be set to "block" and it will stat dexterity up the the desired block value specified in arguemnt (ignored in classic). + * + * See libs/config/Templates/AutoStatExampleBuilds.txt for Config.AutoStat.Build examples. + */ + Config.AutoStat.Enabled = false; // Enable or disable AutoStat system + Config.AutoStat.Save = 0; // Number stat points that will not be spent and saved. + Config.AutoStat.BlockChance = 0; // An integer value set to desired block chance. This is ignored in classic. + Config.AutoStat.UseBulk = true; // Set true to spend multiple stat points at once (up to 100), or false to spend singe point at a time. + Config.AutoStat.Build = []; + + // AutoBuild System ( See /d2bs/kolbot/libs/config/Builds/README.txt for instructions ) + Config.AutoBuild.Enabled = false; // This will enable or disable the AutoBuild system + + // The name of the build associated with an existing + // template filename located in libs/config/Builds/ + Config.AutoBuild.Template = "BuildName"; + // Allows script to print messages in console + Config.AutoBuild.Verbose = true; + // Debug mode prints a little more information to console and + // logs activity to /logs/AutoBuild.CharacterName._MM_DD_YYYY.log + // It automatically enables Config.AutoBuild.Verbose + Config.AutoBuild.DebugMode = true; } diff --git a/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js b/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js index a5938161b..8ce173d23 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * @filename ActionHooks.js * @author theBGuy @@ -6,775 +7,790 @@ */ const ActionHooks = { - hooks: [], - portals: [], - frame: [], - action: null, - currArea: 0, - enabled: true, - prevAreas: [ - sdk.areas.None, sdk.areas.None, sdk.areas.RogueEncampment, sdk.areas.BloodMoor, sdk.areas.ColdPlains, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, sdk.areas.BlackMarsh, - sdk.areas.BloodMoor, sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.BlackMarsh, sdk.areas.TamoeHighland, sdk.areas.CaveLvl1, sdk.areas.UndergroundPassageLvl1, sdk.areas.HoleLvl1, - sdk.areas.PitLvl1, sdk.areas.ColdPlains, sdk.areas.BurialGrounds, sdk.areas.BurialGrounds, sdk.areas.BlackMarsh, sdk.areas.ForgottenTower, sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, - sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TamoeHighland, sdk.areas.MonasteryGate, sdk.areas.OuterCloister, sdk.areas.Barracks, sdk.areas.JailLvl1, sdk.areas.JailLvl2, - sdk.areas.JailLvl3, sdk.areas.InnerCloister, sdk.areas.Cathedral, sdk.areas.CatacombsLvl1, sdk.areas.CatacombsLvl2, sdk.areas.CatacombsLvl3, sdk.areas.StonyField, sdk.areas.RogueEncampment, - sdk.areas.RogueEncampment, sdk.areas.LutGholein, sdk.areas.RockyWaste, sdk.areas.DryHills, sdk.areas.FarOasis, sdk.areas.LostCity, sdk.areas.ArcaneSanctuary, sdk.areas.LutGholein, - sdk.areas.A2SewersLvl1, sdk.areas.A2SewersLvl2, sdk.areas.LutGholein, sdk.areas.HaremLvl1, sdk.areas.HaremLvl2, sdk.areas.PalaceCellarLvl1, sdk.areas.PalaceCellarLvl2, sdk.areas.RockyWaste, - sdk.areas.DryHills, sdk.areas.HallsoftheDeadLvl1, sdk.areas.ValleyofSnakes, sdk.areas.StonyTombLvl1, sdk.areas.HallsoftheDeadLvl2, sdk.areas.ClawViperTempleLvl1, sdk.areas.FarOasis, - sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.LostCity, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, - sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.RogueEncampment, sdk.areas.PalaceCellarLvl3, sdk.areas.RogueEncampment, sdk.areas.KurastDocktown, sdk.areas.SpiderForest, - sdk.areas.SpiderForest, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.KurastCauseway, - sdk.areas.SpiderForest, sdk.areas.SpiderForest, sdk.areas.FlayerJungle, sdk.areas.SwampyPitLvl1, sdk.areas.FlayerJungle, sdk.areas.FlayerDungeonLvl1, sdk.areas.SwampyPitLvl2, sdk.areas.FlayerDungeonLvl2, - sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.KurastBazaar, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.UpperKurast, sdk.areas.KurastCauseway, sdk.areas.KurastCauseway, - sdk.areas.Travincal, sdk.areas.DuranceofHateLvl1, sdk.areas.DuranceofHateLvl2, sdk.areas.DuranceofHateLvl3, sdk.areas.PandemoniumFortress, sdk.areas.OuterSteppes, sdk.areas.PlainsofDespair, - sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, sdk.areas.PandemoniumFortress, sdk.areas.Harrogath, sdk.areas.BloodyFoothills, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, - sdk.areas.CrystalizedPassage, sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, sdk.areas.GlacialTrail, sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.AncientsWay, sdk.areas.Harrogath, - sdk.areas.NihlathaksTemple, sdk.areas.HallsofAnguish, sdk.areas.HallsofPain, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.FrozenTundra, sdk.areas.ArreatSummit, sdk.areas.WorldstoneLvl1, - sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction, sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath - ], - areaInfo: {}, - ctrlObj: { - 0: { - 3: "moveItemFromInvoToTrade", - 5: "moveItemFromTradeToInvo" - }, - 1: { - 3: "moveItemFromInvoToStash", - 7: "moveItemFromStashToInvo" - }, - 2: { - 3: "moveItemFromInvoToCube", - 6: "moveItemFromCubeToInvo" - }, - 3: "sellItem" - }, - blockKeyEvent: () => [ - sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.ChatBox, sdk.uiflags.EscMenu, sdk.uiflags.Shop, sdk.uiflags.Quest, sdk.uiflags.Waypoint, - sdk.uiflags.TradePrompt, sdk.uiflags.Msgs, sdk.uiflags.Stash, sdk.uiflags.Cube, sdk.uiflags.Help, sdk.uiflags.MercScreen - ].some((flag) => getUIFlag(flag)), - - event: function (keycode) { - if ([sdk.keys.Shift, sdk.keys.Alt].some(k => k === keycode)) { - return; - } - - ActionHooks.action = keycode; - }, - - getOnScreenLocation: function () { - let possibleLocs = [sdk.uiflags.TradePrompt, sdk.uiflags.Stash, sdk.uiflags.Cube, sdk.uiflags.Shop]; - - for (let i = 0; i < possibleLocs.length; i++) { - if (getUIFlag(possibleLocs[i])) { - return possibleLocs.indexOf(possibleLocs[i]); - } - } - - return -1; - }, - - checkAction: function () { - let hook; - let unit, screenLoc; - let obj = { type: false, dest: false, action: false }; - let qolObj = { type: "qol", dest: false, action: false }; - - if (this.action) { - try { - // quick ones first - ends checkAction if one of these was true - if ([sdk.keys.Seven, sdk.keys.Eight, sdk.keys.Nine, sdk.keys.NumpadDash].includes(this.action)) { - if (this.blockKeyEvent()) return; - switch (this.action) { - case sdk.keys.Seven: - if (TextHooks.displaySettings) { - TextHooks.getHook("itemStatus", TextHooks.statusHooks).hook.text = "ÿc4Key 7ÿc0: " + (ItemHooks.enabled ? "Enable" : "Disable") + " Item Filter"; - } - ItemHooks.enabled = !ItemHooks.enabled; - - break; - case sdk.keys.Eight: - if (TextHooks.displaySettings) { - TextHooks.getHook("monsterStatus", TextHooks.statusHooks).hook.text = "ÿc4Key 8ÿc0: " + (MonsterHooks.enabled ? "Enable" : "Disable") + " Monsters"; - } - MonsterHooks.enabled = !MonsterHooks.enabled; - - break; - case sdk.keys.Nine: - if (TextHooks.displaySettings) { - TextHooks.getHook("vectorStatus", TextHooks.statusHooks).hook.text = "ÿc4Key 9ÿc0: " + (VectorHooks.enabled ? "Enable" : "Disable") + " Vectors"; - } - VectorHooks.enabled = !VectorHooks.enabled; - - break; - case sdk.keys.NumpadDash: - if (ItemHooks.pickitEnabled) { - ItemHooks.pickitEnabled = false; - } else { - ItemHooks.pickitEnabled = true; - ItemHooks.flush(); - - if (!Hooks.saidMessage) { - showConsole(); - print("ÿc - hook = TextHooks.getHook("Next Act", TextHooks.qolHooks); - - break; - case sdk.keys.Ctrl: - unit = Game.getSelectedUnit(); - - if (!!unit) { - screenLoc = this.getOnScreenLocation(); - - switch (screenLoc) { - case 0: // Trade screen - case 1: // Stash - case 2: // Cube - qolObj.action = this.ctrlObj[screenLoc][unit.location]; - - break; - case 3: // Shop - qolObj.action = "sellItem"; - - break; - default: - break; - } - } - - break; - case sdk.keys.Five: - if (!me.inTown) { - me.getTpTool() && (qolObj.action = "makePortal"); - } else if (me.inTown) { - if (!getUIFlag(sdk.uiflags.Stash) && !getUIFlag(sdk.uiflags.TradePrompt) && !getUIFlag(sdk.uiflags.Inventory)) { - qolObj.action = "heal"; - } - } - - break; - case sdk.keys.Six: - if (!me.inTown) { - me.getTpTool() && (qolObj.action = "takePortal"); - } else if (me.inTown) { - if (!getUIFlag(sdk.uiflags.Stash) && !getUIFlag(sdk.uiflags.TradePrompt) && !getUIFlag(sdk.uiflags.Inventory)) { - qolObj.action = "openStash"; - } - } - - break; - case sdk.keys.Insert: - if (me.inTown) { - break; - } - - qolObj.action = "clear"; - - break; - } - } - - if (hook) { - Object.assign(obj, hook); - Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(obj)); - } else if (qolObj.action) { - Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(qolObj)); - } - } catch (e) { - console.error(e); - } finally { - this.action = null; - } - } - }, - - check: function () { - if (!this.enabled) return; - - this.checkAction(); - - if (me.area !== this.currArea) { - this.flush(); - - while (!me.area || !me.gameReady) { - delay(150); - } - - this.add(me.area); - TextHooks.update(this.hooks.length); - this.currArea = me.area; - } - }, - - yHookLoc: function () { - return 545 - (this.hooks.length * 10) + Hooks.resfix.y; - }, - - newHook: function (name = "", type = "", dest = null) { - let hookTxt = (() => { - switch (name) { - case "Next Area": - return "Num 0: "; - case "Previous Area": - return "ÿc1Num 1: "; - case "Side Area": - return "ÿc3Num 4: "; - case "POI2": - return "ÿc ex.target !== correctTomb) - .sort(function(a, b) { - return a.target - b.target; - }).reverse(); - - let curr; - for (let i = 8; i > 4; i--) { - curr = currExits.shift(); - this.hooks.push({ - name: "POI" + (i - 3), - type: "area", - dest: curr.target, - hook: new Text("ÿc [sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain, sdk.areas.UberTristram].includes(portal.objtype))) { - TextHooks.displaySettings = false; - this.frame.push({ - name: "portalbox", - hook: new Box (Hooks.portalBoard.x - 8, Hooks.portalBoard.y + Hooks.resfix.y - 17, 190, 70, 0x0, 1, 0) - }); - - this.frame.push({ - name: "portalframe", - hook: new Frame(Hooks.portalBoard.x - 8, Hooks.portalBoard.y + Hooks.resfix.y - 17, 190, 70, 0) - }); - - Pather.getPortal(sdk.areas.MatronsDen) && this.portals.push({ - name: "Matron's Den", - type: "portal", - dest: sdk.areas.MatronsDen, - hook: new Text("ÿc1Num 5: Matron's Den", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y) - }); - - Pather.getPortal(sdk.areas.ForgottenSands) && this.portals.push({ - name: "Sands", - type: "portal", - dest: sdk.areas.ForgottenSands, - hook: new Text("ÿc1Num 6: Forgotten Sands", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y + 15) - }); - - Pather.getPortal(sdk.areas.FurnaceofPain) && this.portals.push({ - name: "Furnace", - type: "portal", - dest: sdk.areas.FurnaceofPain, - hook: new Text("ÿc1Num 7: Furnace of Pain", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y + 30) - }); - - Pather.getPortal(sdk.areas.UberTristram) && this.portals.push({ - name: "Uber Tristam", - type: "portal", - dest: sdk.areas.UberTristram, - hook: new Text("ÿc1Num 8: Uber Tristam", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y + 45) - }); - } - - let entrance = {x: 0, y: 0}; - - switch (me.area) { - case sdk.areas.Tristram: - this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.StonyField)); - - break; - case sdk.areas.MooMooFarm: - this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.RogueEncampment)); - - break; - case sdk.areas.CanyonofMagic: - this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.ArcaneSanctuary)); - - break; - case sdk.areas.ArcaneSanctuary: - this.hooks.push(this.newHook("Previous Area", "area", sdk.areas.PalaceCellarLvl3)); - this.hooks.push(this.newHook("Next Area", "area", sdk.areas.CanyonofMagic)); - - break; - case sdk.areas.NihlathaksTemple: - this.hooks.push({ - name: "Previous Area", - type: "unit", - action: {do: "usePortal", id: sdk.areas.Harrogath}, - dest: {x: 10071, y: 13305}, - hook: new Text("ÿc1Num 1: " + Pather.getAreaName(sdk.areas.Harrogath), Hooks.dashBoard.x + 5, this.yHookLoc()) - }); - - break; - case sdk.areas.Abaddon: - this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.FrigidHighlands)); - - break; - case sdk.areas.PitofAcheron: - this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.ArreatPlateau)); - - break; - case sdk.areas.InfernalPit: - this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.FrozenTundra)); - - break; - case sdk.areas.ForgottenSands: - me.inArea(sdk.areas.ForgottenSands) && (entrance = {x: 20193, y: 8693}); - // eslint-disable-next-line no-fallthrough - case sdk.areas.MatronsDen: - case sdk.areas.FurnaceofPain: - bossX = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); - bossX && (entrance = this.areaInfo[me.area][bossX.x]); - // eslint-disable-next-line no-fallthrough - case sdk.areas.UberTristram: - me.inArea(sdk.areas.UberTristram) && (entrance = {x: 25105, y: 5140}); - - this.hooks.push({ - name: "Previous Area", - type: "unit", - action: {do: "usePortal", id: sdk.areas.Harrogath}, - dest: entrance, - hook: new Text("ÿc1Num 1: " + Pather.getAreaName(sdk.areas.Harrogath), Hooks.dashBoard.x + 5, this.yHookLoc()) - }); - - break; - } - - exits = getArea(area).exits; - - if (exits) { - for (i = 0; i < exits.length; i += 1) { - if (exits[i].target === this.prevAreas[me.area]) { - this.hooks.push(this.newHook("Previous Area", "area", this.prevAreas[me.area])); - - break; - } - } - - // Check nextAreas first - for (i = 0; i < exits.length; i += 1) { - if (exits[i].target === nextAreas[me.area]) { - this.hooks.push(this.newHook("Next Area", "area", nextAreas[me.area])); - nextCheck = true; - - break; - } - } - - // In case the area isn't in nextAreas array, use this.prevAreas array - if (!nextCheck) { - for (i = 0; i < exits.length; i += 1) { - if (exits[i].target === this.prevAreas.indexOf(me.area)) { - this.hooks.push(this.newHook("Next Area", "area", this.prevAreas.indexOf(me.area))); - - break; - } - } - } - } - - if (poi && poi.name === "Orifice") { - this.hooks.push(this.newHook("Next Area", "area", sdk.areas.DurielsLair)); - } - - if (me.inArea(sdk.areas.DuranceofHateLvl3)) { - this.hooks.push(this.newHook("Next Area", "area", sdk.areas.PandemoniumFortress)); - } - - if (me.inArea(sdk.areas.ThroneofDestruction)) { - this.hooks.push(this.newHook("Next Area", "area", sdk.areas.WorldstoneChamber)); - } - }, - - getDiabloSeals: function (seal) { - let unit = Game.getPresetObject(sdk.areas.ChaosSanctuary, seal); - - if (unit) { - if (unit instanceof PresetUnit) { - return { - x: unit.roomx * 5 + unit.x, - y: unit.roomy * 5 + unit.y, - }; - } - - return { - x: unit.x, - y: unit.y, - }; - } - - return false; - }, - - getHook: function (name) { - for (let i = 0; i < this.hooks.length; i += 1) { - if (this.hooks[i].name === name) { - return this.hooks[i]; - } - } - - return false; - }, - - getPortalHook: function (name) { - for (let i = 0; i < this.portals.length; i += 1) { - if (this.portals[i].name === name) { - return this.portals[i]; - } - } - - return false; - }, - - flush: function () { - while (this.hooks.length) { - this.hooks.shift().hook.remove(); - } - - while (this.portals.length) { - this.portals.shift().hook.remove(); - } - - while (this.frame.length) { - this.frame.shift().hook.remove(); - } - - this.currArea = 0; - } + hooks: [], + portals: [], + frame: [], + action: null, + currArea: 0, + enabled: true, + prevAreas: [ + sdk.areas.None, sdk.areas.None, sdk.areas.RogueEncampment, sdk.areas.BloodMoor, sdk.areas.ColdPlains, sdk.areas.UndergroundPassageLvl1, sdk.areas.DarkWood, sdk.areas.BlackMarsh, + sdk.areas.BloodMoor, sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.BlackMarsh, sdk.areas.TamoeHighland, sdk.areas.CaveLvl1, sdk.areas.UndergroundPassageLvl1, sdk.areas.HoleLvl1, + sdk.areas.PitLvl1, sdk.areas.ColdPlains, sdk.areas.BurialGrounds, sdk.areas.BurialGrounds, sdk.areas.BlackMarsh, sdk.areas.ForgottenTower, sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, + sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TamoeHighland, sdk.areas.MonasteryGate, sdk.areas.OuterCloister, sdk.areas.Barracks, sdk.areas.JailLvl1, sdk.areas.JailLvl2, + sdk.areas.JailLvl3, sdk.areas.InnerCloister, sdk.areas.Cathedral, sdk.areas.CatacombsLvl1, sdk.areas.CatacombsLvl2, sdk.areas.CatacombsLvl3, sdk.areas.StonyField, sdk.areas.RogueEncampment, + sdk.areas.RogueEncampment, sdk.areas.LutGholein, sdk.areas.RockyWaste, sdk.areas.DryHills, sdk.areas.FarOasis, sdk.areas.LostCity, sdk.areas.ArcaneSanctuary, sdk.areas.LutGholein, + sdk.areas.A2SewersLvl1, sdk.areas.A2SewersLvl2, sdk.areas.LutGholein, sdk.areas.HaremLvl1, sdk.areas.HaremLvl2, sdk.areas.PalaceCellarLvl1, sdk.areas.PalaceCellarLvl2, sdk.areas.RockyWaste, + sdk.areas.DryHills, sdk.areas.HallsoftheDeadLvl1, sdk.areas.ValleyofSnakes, sdk.areas.StonyTombLvl1, sdk.areas.HallsoftheDeadLvl2, sdk.areas.ClawViperTempleLvl1, sdk.areas.FarOasis, + sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.LostCity, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, + sdk.areas.CanyonofMagic, sdk.areas.CanyonofMagic, sdk.areas.RogueEncampment, sdk.areas.PalaceCellarLvl3, sdk.areas.RogueEncampment, sdk.areas.KurastDocktown, sdk.areas.SpiderForest, + sdk.areas.SpiderForest, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.KurastCauseway, + sdk.areas.SpiderForest, sdk.areas.SpiderForest, sdk.areas.FlayerJungle, sdk.areas.SwampyPitLvl1, sdk.areas.FlayerJungle, sdk.areas.FlayerDungeonLvl1, sdk.areas.SwampyPitLvl2, sdk.areas.FlayerDungeonLvl2, + sdk.areas.UpperKurast, sdk.areas.A3SewersLvl1, sdk.areas.KurastBazaar, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.UpperKurast, sdk.areas.KurastCauseway, sdk.areas.KurastCauseway, + sdk.areas.Travincal, sdk.areas.DuranceofHateLvl1, sdk.areas.DuranceofHateLvl2, sdk.areas.DuranceofHateLvl3, sdk.areas.PandemoniumFortress, sdk.areas.OuterSteppes, sdk.areas.PlainsofDespair, + sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, sdk.areas.PandemoniumFortress, sdk.areas.Harrogath, sdk.areas.BloodyFoothills, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, + sdk.areas.CrystalizedPassage, sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, sdk.areas.GlacialTrail, sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.AncientsWay, sdk.areas.Harrogath, + sdk.areas.NihlathaksTemple, sdk.areas.HallsofAnguish, sdk.areas.HallsofPain, sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, sdk.areas.FrozenTundra, sdk.areas.ArreatSummit, sdk.areas.WorldstoneLvl1, + sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction, sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath, sdk.areas.Harrogath + ], + areaInfo: {}, + ctrlObj: { + 0: { + 3: "moveItemFromInvoToTrade", + 5: "moveItemFromTradeToInvo" + }, + 1: { + 3: "moveItemFromInvoToStash", + 7: "moveItemFromStashToInvo" + }, + 2: { + 3: "moveItemFromInvoToCube", + 6: "moveItemFromCubeToInvo" + }, + 3: "sellItem" + }, + blockKeyEvent: () => [ + sdk.uiflags.Inventory, + sdk.uiflags.StatsWindow, + sdk.uiflags.ChatBox, + sdk.uiflags.EscMenu, + sdk.uiflags.Shop, + sdk.uiflags.Quest, + sdk.uiflags.Waypoint, + sdk.uiflags.TradePrompt, + sdk.uiflags.Msgs, + sdk.uiflags.Stash, + sdk.uiflags.Cube, + sdk.uiflags.Help, + sdk.uiflags.MercScreen + ].some((flag) => getUIFlag(flag)), + + /** + * Set action based on key input + * @param {number} keycode + * @returns {void} + * @todo this would probably be better as pushing to an action stack and implementing a timeout to prevent spamming the same action + */ + event: function (keycode) { + if ([sdk.keys.Shift, sdk.keys.Alt].some(k => k === keycode)) { + return; + } + + ActionHooks.action = keycode; + }, + + getOnScreenLocation: function () { + let possibleLocs = [sdk.uiflags.TradePrompt, sdk.uiflags.Stash, sdk.uiflags.Cube, sdk.uiflags.Shop]; + + for (let i = 0; i < possibleLocs.length; i++) { + if (getUIFlag(possibleLocs[i])) { + return possibleLocs.indexOf(possibleLocs[i]); + } + } + + return -1; + }, + + checkAction: function () { + let hook; + let unit, screenLoc; + let obj = { type: false, dest: false, action: false }; + let qolObj = { type: "qol", dest: false, action: false }; + + if (this.action) { + try { + // quick ones first - ends checkAction if one of these was true + if ([sdk.keys.Seven, sdk.keys.Eight, sdk.keys.Nine, sdk.keys.NumpadDash].includes(this.action)) { + if (this.blockKeyEvent()) return; + switch (this.action) { + case sdk.keys.Seven: + if (TextHooks.displaySettings) { + TextHooks.getHook("itemStatus", TextHooks.statusHooks).hook.text = "ÿc4Key 7ÿc0: " + (ItemHooks.enabled ? "Enable" : "Disable") + " Item Filter"; + } + ItemHooks.enabled = !ItemHooks.enabled; + + break; + case sdk.keys.Eight: + if (TextHooks.displaySettings) { + TextHooks.getHook("monsterStatus", TextHooks.statusHooks).hook.text = "ÿc4Key 8ÿc0: " + (MonsterHooks.enabled ? "Enable" : "Disable") + " Monsters"; + } + MonsterHooks.enabled = !MonsterHooks.enabled; + + break; + case sdk.keys.Nine: + if (TextHooks.displaySettings) { + TextHooks.getHook("vectorStatus", TextHooks.statusHooks).hook.text = "ÿc4Key 9ÿc0: " + (VectorHooks.enabled ? "Enable" : "Disable") + " Vectors"; + } + VectorHooks.enabled = !VectorHooks.enabled; + + break; + case sdk.keys.NumpadDash: + if (ItemHooks.pickitEnabled) { + ItemHooks.pickitEnabled = false; + } else { + ItemHooks.pickitEnabled = true; + ItemHooks.flush(); + + if (!Hooks.saidMessage) { + showConsole(); + print("ÿc + hook = TextHooks.getHook("Next Act", TextHooks.qolHooks); + + break; + case sdk.keys.Ctrl: + unit = Game.getSelectedUnit(); + + if (!!unit) { + screenLoc = this.getOnScreenLocation(); + + switch (screenLoc) { + case 0: // Trade screen + case 1: // Stash + case 2: // Cube + qolObj.action = this.ctrlObj[screenLoc][unit.location]; + + break; + case 3: // Shop + // qolObj.action = "sellItem"; + + break; + default: + break; + } + } + + break; + case sdk.keys.Five: + if (!me.inTown) { + me.getTpTool() && (qolObj.action = "makePortal"); + } else if (me.inTown) { + if (!getUIFlag(sdk.uiflags.Stash) && !getUIFlag(sdk.uiflags.TradePrompt) && !getUIFlag(sdk.uiflags.Inventory)) { + qolObj.action = "heal"; + } + } + + break; + case sdk.keys.Six: + if (!me.inTown) { + me.getTpTool() && (qolObj.action = "takePortal"); + } else if (me.inTown) { + if (!getUIFlag(sdk.uiflags.Stash) && !getUIFlag(sdk.uiflags.TradePrompt) && !getUIFlag(sdk.uiflags.Inventory)) { + qolObj.action = "openStash"; + } + } + + break; + case sdk.keys.Insert: + if (me.inTown) { + break; + } + + qolObj.action = "clear"; + + break; + } + } + + if (hook) { + Object.assign(obj, hook); + Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(obj)); + } else if (qolObj.action) { + Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(qolObj)); + } + } catch (e) { + console.error(e); + } finally { + ActionHooks.action = null; + } + } + }, + + check: function () { + if (!this.enabled) return; + + this.checkAction(); + + if (me.area !== this.currArea) { + this.flush(); + + while (!me.area || !me.gameReady) { + delay(150); + } + + this.add(me.area); + TextHooks.update(this.hooks.length); + ActionHooks.currArea = me.area; + } + }, + + yHookLoc: function () { + return 545 - (this.hooks.length * 10) + Hooks.resfix.y; + }, + + newHook: function (name = "", type = "", dest = null) { + let hookTxt = (() => { + switch (name) { + case "Next Area": + return "Num 0: "; + case "Previous Area": + return "ÿc1Num 1: "; + case "Side Area": + return "ÿc3Num 4: "; + case "POI2": + return "ÿc ex.target !== correctTomb) + .sort(function(a, b) { + return a.target - b.target; + }).reverse(); + + let curr; + for (let i = 8; i > 4; i--) { + curr = currExits.shift(); + this.hooks.push({ + name: "POI" + (i - 3), + type: "area", + dest: curr.target, + hook: new Text("ÿc [sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain, sdk.areas.UberTristram].includes(portal.objtype))) { + TextHooks.displaySettings = false; + this.frame.push({ + name: "portalbox", + hook: new Box (Hooks.portalBoard.x - 8, Hooks.portalBoard.y + Hooks.resfix.y - 17, 190, 70, 0x0, 1, 0) + }); + + this.frame.push({ + name: "portalframe", + hook: new Frame(Hooks.portalBoard.x - 8, Hooks.portalBoard.y + Hooks.resfix.y - 17, 190, 70, 0) + }); + + Pather.getPortal(sdk.areas.MatronsDen) && this.portals.push({ + name: "Matron's Den", + type: "portal", + dest: sdk.areas.MatronsDen, + hook: new Text("ÿc1Num 5: Matron's Den", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y) + }); + + Pather.getPortal(sdk.areas.ForgottenSands) && this.portals.push({ + name: "Sands", + type: "portal", + dest: sdk.areas.ForgottenSands, + hook: new Text("ÿc1Num 6: Forgotten Sands", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y + 15) + }); + + Pather.getPortal(sdk.areas.FurnaceofPain) && this.portals.push({ + name: "Furnace", + type: "portal", + dest: sdk.areas.FurnaceofPain, + hook: new Text("ÿc1Num 7: Furnace of Pain", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y + 30) + }); + + Pather.getPortal(sdk.areas.UberTristram) && this.portals.push({ + name: "Uber Tristam", + type: "portal", + dest: sdk.areas.UberTristram, + hook: new Text("ÿc1Num 8: Uber Tristam", Hooks.portalBoard.x, Hooks.portalBoard.y + Hooks.resfix.y + 45) + }); + } + + let entrance = { x: 0, y: 0 }; + + switch (me.area) { + case sdk.areas.Tristram: + this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.StonyField)); + + break; + case sdk.areas.MooMooFarm: + this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.RogueEncampment)); + + break; + case sdk.areas.CanyonofMagic: + this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.ArcaneSanctuary)); + + break; + case sdk.areas.ArcaneSanctuary: + this.hooks.push(this.newHook("Previous Area", "area", sdk.areas.PalaceCellarLvl3)); + this.hooks.push(this.newHook("Next Area", "area", sdk.areas.CanyonofMagic)); + + break; + case sdk.areas.NihlathaksTemple: + this.hooks.push({ + name: "Previous Area", + type: "unit", + action: { do: "usePortal", id: sdk.areas.Harrogath }, + dest: { x: 10071, y: 13305 }, + hook: new Text("ÿc1Num 1: " + getAreaName(sdk.areas.Harrogath), Hooks.dashBoard.x + 5, this.yHookLoc()) + }); + + break; + case sdk.areas.Abaddon: + this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.FrigidHighlands)); + + break; + case sdk.areas.PitofAcheron: + this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.ArreatPlateau)); + + break; + case sdk.areas.InfernalPit: + this.hooks.push(this.newHook("Previous Area", "portal", sdk.areas.FrozenTundra)); + + break; + case sdk.areas.ForgottenSands: + me.inArea(sdk.areas.ForgottenSands) && (entrance = { x: 20193, y: 8693 }); + // eslint-disable-next-line no-fallthrough + case sdk.areas.MatronsDen: + case sdk.areas.FurnaceofPain: + bossX = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); + bossX && (entrance = this.areaInfo[me.area][bossX.x]); + // eslint-disable-next-line no-fallthrough + case sdk.areas.UberTristram: + me.inArea(sdk.areas.UberTristram) && (entrance = { x: 25105, y: 5140 }); + + this.hooks.push({ + name: "Previous Area", + type: "unit", + action: { do: "usePortal", id: sdk.areas.Harrogath }, + dest: entrance, + hook: new Text("ÿc1Num 1: " + getAreaName(sdk.areas.Harrogath), Hooks.dashBoard.x + 5, this.yHookLoc()) + }); + + break; + } + + exits = getArea(area).exits; + + if (exits) { + for (i = 0; i < exits.length; i += 1) { + if (exits[i].target === this.prevAreas[me.area]) { + this.hooks.push(this.newHook("Previous Area", "area", this.prevAreas[me.area])); + + break; + } + } + + // Check nextAreas first + for (i = 0; i < exits.length; i += 1) { + if (exits[i].target === nextAreas[me.area]) { + this.hooks.push(this.newHook("Next Area", "area", nextAreas[me.area])); + nextCheck = true; + + break; + } + } + + // In case the area isn't in nextAreas array, use this.prevAreas array + if (!nextCheck) { + for (i = 0; i < exits.length; i += 1) { + if (exits[i].target === this.prevAreas.indexOf(me.area)) { + this.hooks.push(this.newHook("Next Area", "area", this.prevAreas.indexOf(me.area))); + + break; + } + } + } + } + + if (poi && poi.name === "Orifice") { + this.hooks.push(this.newHook("Next Area", "area", sdk.areas.DurielsLair)); + } + + if (me.inArea(sdk.areas.DuranceofHateLvl3)) { + this.hooks.push(this.newHook("Next Area", "area", sdk.areas.PandemoniumFortress)); + } + + if (me.inArea(sdk.areas.ThroneofDestruction)) { + this.hooks.push(this.newHook("Next Area", "area", sdk.areas.WorldstoneChamber)); + } + }, + + /** + * @param {number} seal + * @returns {{ x: number, y: number, area: number }} + */ + getDiabloSeals: function (seal) { + try { + let unit = Game.getPresetObject(sdk.areas.ChaosSanctuary, seal); + return unit.realCoords(); + } catch (e) { + return false; + } + }, + + getHook: function (name) { + for (let i = 0; i < this.hooks.length; i += 1) { + if (this.hooks[i].name === name) { + return this.hooks[i]; + } + } + + return false; + }, + + getPortalHook: function (name) { + for (let i = 0; i < this.portals.length; i += 1) { + if (this.portals[i].name === name) { + return this.portals[i]; + } + } + + return false; + }, + + flush: function () { + while (this.hooks.length) { + this.hooks.shift().hook.remove(); + } + + while (this.portals.length) { + this.portals.shift().hook.remove(); + } + + while (this.frame.length) { + this.frame.shift().hook.remove(); + } + + this.currArea = 0; + } }; ActionHooks.areaInfo[sdk.areas.MatronsDen] = { - 11: {x: 20023, y: 7643}, - 20: {x: 20303, y: 7803}, - 21: {x: 20263, y: 7683}, + 11: { x: 20023, y: 7643 }, + 20: { x: 20303, y: 7803 }, + 21: { x: 20263, y: 7683 }, }; ActionHooks.areaInfo[sdk.areas.FurnaceofPain] = { - 14: {x: 20138, y: 14873}, - 15: {x: 20138, y: 14563}, + 14: { x: 20138, y: 14873 }, + 15: { x: 20138, y: 14563 }, }; diff --git a/d2bs/kolbot/libs/manualplay/hooks/HelpMenu.js b/d2bs/kolbot/libs/manualplay/hooks/HelpMenu.js index 85a20ada0..c2a5e805f 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/HelpMenu.js +++ b/d2bs/kolbot/libs/manualplay/hooks/HelpMenu.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * @filename HelpMenu.js * @author theBGuy @@ -6,157 +7,159 @@ */ const HelpMenu = new function () { - this.hooks = []; - this.box = []; - this.cleared = true; - this.helpBoxX = 715 + (me.screensize ? 0 : -160); - this.helpBoxY = 88 + 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename) + Number(!!me.gameserverip && !me.realm)); - this.helpBoxTextX = 647 + (me.screensize ? 0 : -160); - this.helpBoxTextY = 88 + 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename) + Number(!!me.gameserverip && !me.realm)) + 15; - this.action = []; - this.actionY = -1; - this.tick = 0; - this.chatCommands = { - "me": "Displays Character level, Area, and x/y coordinates", - "pick": "Pick items from the ground to inventory", - "hide": "Hide this menu", - "make": "create config file with current characters name", - "stash": "Stash items/gold from inventory", - "filltps": "Fill tp tome", - "cowportal": "Make cow portal as long as bot already has leg", - "uberportal": "Make uber portal(s) as long as bot already has key", - "ubertrist": "Make uber tristam portal as long as bot already has organs", - "useraddon": "Toggles useraddon mode", - "Ctrl": "Hover over an item then press Ctrl to move that item from one area to the next. In example: Stash to Inventory, Cube to Inventory, Inventory to TradeScreen, or Inventory to Shop (sellItem)", - "drop": { - "invo": "Drop all items in the inventory", - "stash": "Drop all items in the stash excluding the cube" - }, - "stack": { - "antidote": "Buy and Stack 10 antidote potions for 5 minutes of boosted poison resistance", - "thawing": "Buy and Stack 10 thawing potions for 5 minutes of boosted cold resistance", - "stamina": "Buy and Stack 10 stamina potions for 5 minutes of boosted stamina", - }, - "Num": { - "9:": "Stops current pathing action", - }, - }; - - this.hookHandler = function (click, x, y) { - // Left click - if (click === 0) { - // give a small timeout between clicks - if (getTickCount() - HelpMenu.tick > 1000) { - HelpMenu.action.push([x, y]); - } - // Block click - return true; - } - - return false; - }; - - this.addHook = function (text) { - this.hooks.push(new Text("ÿc4." + text, this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false, this.hookHandler)); - }; - - this.showMenu = function () { - this.cleared = false; - - let chatCommands = [ - "me", - "pick", - "hide", - "make", - "stash", - "filltps", - "cowportal", - "uberportal", - "ubertrist", - "useraddon", - "drop invo", - "drop stash", - "stack antidote", - "stack thawing", - "stack stamina", - ]; - - this.hooks.push(new Text("ÿc2Chat Commands:", this.helpBoxTextX, this.helpBoxTextY, 0, 0, 0)); - - for (let i = 0; i < chatCommands.length; i++) { - this.addHook(chatCommands[i]); - } - - this.hooks.push(new Text("ÿc2Key Commands:", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0)); - this.hooks.push(new Text("ÿc4 Ctrl : ÿc0Move Item", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false, this.hookHandler)); - this.hooks.push(new Text("ÿc4 Pause : ÿc1Pause Map", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); - this.hooks.push(new Text("ÿc4 Delete: ÿc1Quick Exit", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); - this.hooks.push(new Text("ÿc4 End : ÿc1Stop Profile", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); - this.hooks.push(new Text("ÿc4 Num 9: ÿc1Stop Action", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false, this.hookHandler)); - this.hooks.push(new Text("ÿc4 Num / : ÿc1Reload", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); - this.hooks.push(new Text("ÿc4 Num + : ÿc0Show Stats", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); - this.hooks.push(new Text("ÿc4 Num * : ÿc0Precast", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); - this.hooks.push(new Text("ÿc4 Num . : ÿc0Log Character", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); - - this.box.push(new Box(this.helpBoxX, this.helpBoxY, 150, 8 + (this.hooks.length * 13), 0x0, 1, 2)); - this.box.push(new Frame(this.helpBoxX, this.helpBoxY, 150, 8 + (this.hooks.length * 13), 2)); - this.box[this.box.length - 2].zorder = 0; - }; - - this.hideMenu = function () { - this.cleared = true; - - while (this.hooks.length > 0) { - this.hooks.shift().remove(); - } - - while (this.box.length > 0) { - this.box.shift().remove(); - } - - return; - }; - - this.sortHooks = function (h1, h2) { - return Math.abs(h1.y - HelpMenu.actionY) - Math.abs(h2.y - HelpMenu.actionY); - }; + this.hooks = []; + this.box = []; + this.cleared = true; + this.helpBoxX = 715 + (me.screensize ? 0 : -160); + this.helpBoxY = 88 + 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename) + Number(!!me.gameserverip && !me.realm)); + this.helpBoxTextX = 647 + (me.screensize ? 0 : -160); + this.helpBoxTextY = 88 + 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename) + Number(!!me.gameserverip && !me.realm)) + 15; + this.action = []; + this.actionY = -1; + this.tick = 0; + this.chatCommands = { + "me": "Displays Character level, Area, and x/y coordinates", + "pick": "Pick items from the ground to inventory", + "hide": "Hide this menu", + "make": "create config file with current characters name", + "stash": "Stash items/gold from inventory", + "gamble": "Start gambling", + "filltps": "Fill tp tome", + "cowportal": "Make cow portal as long as bot already has leg", + "uberportal": "Make uber portal(s) as long as bot already has key", + "ubertrist": "Make uber tristam portal as long as bot already has organs", + "useraddon": "Toggles useraddon mode", + "Ctrl": "Hover over an item then press Ctrl to move that item from one area to the next. In example: Stash to Inventory, Cube to Inventory, Inventory to TradeScreen, or Inventory to Shop (sellItem)", + "drop": { + "invo": "Drop all items in the inventory", + "stash": "Drop all items in the stash excluding the cube" + }, + "stack": { + "antidote": "Buy and Stack 10 antidote potions for 5 minutes of boosted poison resistance", + "thawing": "Buy and Stack 10 thawing potions for 5 minutes of boosted cold resistance", + "stamina": "Buy and Stack 10 stamina potions for 5 minutes of boosted stamina", + }, + "Num": { + "9:": "Stops current pathing action", + }, + }; + + this.hookHandler = function (click, x, y) { + // Left click + if (click === 0) { + // give a small timeout between clicks + if (getTickCount() - HelpMenu.tick > 1000) { + HelpMenu.action.push([x, y]); + } + // Block click + return true; + } + + return false; + }; + + this.addHook = function (text) { + this.hooks.push(new Text("ÿc4." + text, this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false, this.hookHandler)); + }; + + this.showMenu = function () { + this.cleared = false; + + let chatCommands = [ + "me", + "pick", + "hide", + "make", + "stash", + "gamble", + "filltps", + "cowportal", + "uberportal", + "ubertrist", + "useraddon", + "drop invo", + "drop stash", + "stack antidote", + "stack thawing", + "stack stamina", + ]; + + this.hooks.push(new Text("ÿc2Chat Commands:", this.helpBoxTextX, this.helpBoxTextY, 0, 0, 0)); + + for (let i = 0; i < chatCommands.length; i++) { + this.addHook(chatCommands[i]); + } + + this.hooks.push(new Text("ÿc2Key Commands:", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0)); + this.hooks.push(new Text("ÿc4 Ctrl : ÿc0Move Item", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false, this.hookHandler)); + this.hooks.push(new Text("ÿc4 Pause : ÿc1Pause Map", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); + this.hooks.push(new Text("ÿc4 Delete: ÿc1Quick Exit", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); + this.hooks.push(new Text("ÿc4 End : ÿc1Stop Profile", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); + this.hooks.push(new Text("ÿc4 Num 9: ÿc1Stop Action", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false, this.hookHandler)); + this.hooks.push(new Text("ÿc4 Num / : ÿc1Reload", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); + this.hooks.push(new Text("ÿc4 Num + : ÿc0Show Stats", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); + this.hooks.push(new Text("ÿc4 Num * : ÿc0Precast", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); + this.hooks.push(new Text("ÿc4 Num . : ÿc0Log Character", this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false)); + + this.box.push(new Box(this.helpBoxX, this.helpBoxY, 150, 8 + (this.hooks.length * 13), 0x0, 1, 2)); + this.box.push(new Frame(this.helpBoxX, this.helpBoxY, 150, 8 + (this.hooks.length * 13), 2)); + this.box[this.box.length - 2].zorder = 0; + }; + + this.hideMenu = function () { + this.cleared = true; + + while (this.hooks.length > 0) { + this.hooks.shift().remove(); + } + + while (this.box.length > 0) { + this.box.shift().remove(); + } + + return; + }; + + this.sortHooks = function (h1, h2) { + return Math.abs(h1.y - HelpMenu.actionY) - Math.abs(h2.y - HelpMenu.actionY); + }; }; let Worker = require("../../modules/Worker"); Worker.runInBackground.helpAction = function () { - while (HelpMenu.action.length > 0) { - HelpMenu.tick = getTickCount(); - HelpMenu.actionY = HelpMenu.action.shift()[1]; - // Sort hooks - HelpMenu.hooks.sort(HelpMenu.sortHooks); - - let cmd = HelpMenu.hooks[0].text.split(" ")[0].split(".")[1]; - let msgList = HelpMenu.hooks[0].text.split(" "); - - if (!HelpMenu.hooks[0].text.includes(".")) { - cmd = HelpMenu.hooks[0].text.split(" ")[1]; - } - - try { - let str = ""; - - if (msgList.length === 2 && typeof HelpMenu.chatCommands[cmd] === "object") { - str = HelpMenu.chatCommands[cmd][msgList[1]]; - } else if (msgList.length > 2 && typeof HelpMenu.chatCommands[cmd] === "object") { - str = HelpMenu.chatCommands[cmd][msgList[2]]; - } else { - str = HelpMenu.chatCommands[cmd]; - } - - !!str && me.overhead(str); - } catch (e) { - print(e); - me.overhead(cmd); - } - - delay(150); - } - - return true; + while (HelpMenu.action.length > 0) { + HelpMenu.tick = getTickCount(); + HelpMenu.actionY = HelpMenu.action.shift()[1]; + // Sort hooks + HelpMenu.hooks.sort(HelpMenu.sortHooks); + + let cmd = HelpMenu.hooks[0].text.split(" ")[0].split(".")[1]; + let msgList = HelpMenu.hooks[0].text.split(" "); + + if (!HelpMenu.hooks[0].text.includes(".")) { + cmd = HelpMenu.hooks[0].text.split(" ")[1]; + } + + try { + let str = ""; + + if (msgList.length === 2 && typeof HelpMenu.chatCommands[cmd] === "object") { + str = HelpMenu.chatCommands[cmd][msgList[1]]; + } else if (msgList.length > 2 && typeof HelpMenu.chatCommands[cmd] === "object") { + str = HelpMenu.chatCommands[cmd][msgList[2]]; + } else { + str = HelpMenu.chatCommands[cmd]; + } + + !!str && me.overhead(str); + } catch (e) { + console.error(e); + me.overhead(cmd); + } + + delay(150); + } + + return true; }; diff --git a/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js b/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js index 78cba8200..30e79a74d 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * @filename ItemHooks.js * @author theBGuy @@ -6,398 +7,443 @@ */ // todo - clean up all the map stuff -const ItemHooks = { - enabled: true, - pickitEnabled: false, - modifier: 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename)), - hooks: [], - ignoreItemTypes: [ - sdk.items.type.Gold, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver, sdk.items.type.Book, sdk.items.type.Gem, sdk.items.type.Scroll, - sdk.items.type.MissilePotion, sdk.items.type.Key, sdk.items.type.Boots, sdk.items.type.Gloves, sdk.items.type.ThrowingKnife, sdk.items.type.ThrowingAxe, - sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion, sdk.items.type.StaminaPotion, sdk.items.type.AntidotePotion, - sdk.items.type.ThawingPotion, sdk.items.type.ChippedGem, sdk.items.type.FlawedGem, sdk.items.type.StandardGem, sdk.items.type.FlawlessGem, sdk.items.type.PerfectgGem, - sdk.items.type.Amethyst, sdk.items.type.Diamond, sdk.items.type.Emerald, sdk.items.type.Ruby, sdk.items.type.Sapphire, sdk.items.type.Topaz, sdk.items.type.Skull - ], - itemCodeByClassId: [], - itemCodeByClassIdAndQuality: [], - itemColorCode: [], - - addToCodeByClassIdAndQuality: function (id, setName = "", uniqueName = "") { - if (!id) return; - if (!this.itemCodeByClassIdAndQuality[id]) this.itemCodeByClassIdAndQuality[id] = []; - if (setName) { - this.itemCodeByClassIdAndQuality[id][sdk.items.quality.Set] = setName; - } - if (uniqueName) { - this.itemCodeByClassIdAndQuality[id][sdk.items.quality.Unique] = uniqueName; - } - }, - - check: function () { - if (!this.enabled) { - this.flush(); - - return; - } - - for (let i = 0; i < this.hooks.length; i++) { - if (!copyUnit(this.hooks[i].item).x) { - for (let j = 0; j < this.hooks[i].hook.length; j++) { - this.hooks[i].hook[j].remove(); - } - - this.hooks[i].name[0] && this.hooks[i].name[0].remove(); - this.hooks[i].vector[0] && this.hooks[i].vector[0].remove(); - this.hooks.splice(i, 1); - i -= 1; - this.flush(); - } - } - - let item = Game.getItem(); - - if (item) { - try { - do { - if (item.area === ActionHooks.currArea && item.onGroundOrDropping - && (item.quality >= sdk.items.quality.Magic || ((item.normal || item.superior) && !this.ignoreItemTypes.includes(item.itemType)))) { - if (this.pickitEnabled) { - if ([Pickit.Result.UNWANTED, Pickit.Result.TRASH].indexOf(Pickit.checkItem(item).result) === -1) { - !this.getHook(item) && this.add(item); - } - } else { - !this.getHook(item) && this.add(item); - } - - this.getHook(item) && this.update(); - } else { - this.remove(item); - } - } while (item.getNext()); - } catch (e) { - console.error(e); - this.flush(); - } - } - }, - - update: function () { - for (let i = 0; i < this.hooks.length; i++) { - this.hooks[i].vector[0].x = me.x; - this.hooks[i].vector[0].y = me.y; - } - }, - - getName: function (item) { - let abbr = item.name.split(" "); - let abbrName = ""; - - if (abbr[1]) { - abbrName += abbr[0] + "-"; - - for (let i = 1; i < abbr.length; i++) { - abbrName += abbr[i].substring(0, 1); - } - } - - return !!abbrName ? abbrName : item.name; - }, - - newHook: function (item) { - let color = 0, code = "", arr = [], name = [], vector = []; - let eth = (item.ethereal ? "Eth: " : ""); - - switch (item.quality) { - case sdk.items.quality.Normal: - case sdk.items.quality.Superior: - switch (item.itemType) { - case sdk.items.type.Quest: - color = 0x9A; - code += (!!this.itemCodeByClassId[item.classid] ? this.itemCodeByClassId[item.classid] : "ÿc8" + item.fname); - - break; - case sdk.items.type.Rune: - if (item.classid >= sdk.items.runes.Vex) { - color = 0x9B; - code = "ÿc;" + item.fname; - } else if (item.classid >= sdk.items.runes.Lum) { - color = 0x9A; - code = "ÿc8" + item.fname; - } else { - color = 0xA1; - code = item.fname; - } - - break; - default: - if (item.name) { - if (item.sockets === 1) { - break; - } - - color = 0x20; - - if (item.runeword) { - code = item.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, ""); - } else { - code = "ÿc0" + (item.sockets > 0 ? "[" + item.sockets + "]" : ""); - code += this.getName(item); - item.itemType === sdk.items.type.AuricShields && (code += "[R: " + item.getStat(sdk.stats.FireResist) + "]"); - code += "(" + item.ilvl + ")"; - } - } - - break; - } - - break; - case sdk.items.quality.Set: - case sdk.items.quality.Unique: - ({color, code} = this.itemColorCode[item.quality]); - - if (!this.itemCodeByClassId[item.classid]) { - switch (item.classid) { - case sdk.items.Ring: - case sdk.items.Amulet: - code += item.name + "(" + item.ilvl + ")"; - - break; - default: - code += ((!!this.itemCodeByClassIdAndQuality[item.classid] && !!this.itemCodeByClassIdAndQuality[item.classid][item.quality]) ? this.itemCodeByClassIdAndQuality[item.classid][item.quality] : item.name); - - break; - } - } else { - code += this.itemCodeByClassId[item.classid]; - } - - break; - case sdk.items.quality.Magic: - case sdk.items.quality.Rare: - if (item.name) { - ({color, code} = this.itemColorCode[item.quality]); - - code += (item.sockets > 0 ? "[" + item.sockets + "]" : ""); - code += this.getName(item); - code += "(" + item.ilvl + ")"; - } - - break; - } - - !!code && name.push(new Text(eth + code, 665 + Hooks.resfix.x, 104 + this.modifier + (this.hooks.length * 14), color, 0, 0)); - vector.push(new Line(me.x, me.y, item.x, item.y, color, true)); - arr.push(new Line(item.x - 3, item.y, item.x + 3, item.y, color, true)); - arr.push(new Line(item.x, item.y - 3, item.x, item.y + 3, color, true)); - - return { - itemLoc: arr, - itemName: name, - vector: vector, - }; - }, - - add: function (item) { - if (item === undefined || !item.classid) { - return; - } - - let temp = this.newHook(item); - - this.hooks.push({ - item: copyUnit(item), - area: item.area, - hook: temp.itemLoc, - name: temp.itemName, - vector: temp.vector, - }); - }, - - getHook: function (item) { - for (let i = 0; i < this.hooks.length; i++) { - if (this.hooks[i].item.gid === item.gid) { - return this.hooks[i].hook; - } - } - - return false; - }, - - remove: function (item) { - for (let i = 0; i < this.hooks.length; i++) { - if (this.hooks[i].item.gid === item.gid) { - for (let j = 0; j < this.hooks[i].hook.length; j++) { - this.hooks[i].hook[j].remove(); - } - - this.hooks[i].name[0] && this.hooks[i].name[0].remove(); - this.hooks[i].vector[0] && this.hooks[i].vector[0].remove(); - this.hooks.splice(i, 1); - - return true; - } - } - - return false; - }, - - flush: function () { - while (this.hooks.length) { - for (let j = 0; j < this.hooks[0].hook.length; j++) { - this.hooks[0].hook[j].remove(); - } - - this.hooks[0].name[0] && this.hooks[0].name[0].remove(); - this.hooks[0].vector[0] && this.hooks[0].vector[0].remove(); - this.hooks.shift(); - } - } -}; - -// have to be set after ItemHooks is created -ItemHooks.itemCodeByClassId[sdk.items.BattleAxe] = "The Chieftain"; -ItemHooks.itemCodeByClassId[sdk.items.Falchion] = "Gleamscythe"; -ItemHooks.itemCodeByClassId[sdk.items.BurntWand] = "Suicide Branch"; -ItemHooks.itemCodeByClassId[sdk.items.PetrifiedWand] = "Carin Shard"; -ItemHooks.itemCodeByClassId[sdk.items.TombWand] = "King Leoric's Arm"; -ItemHooks.itemCodeByClassId[sdk.items.Quarterstaff] = "Ribcracker"; -ItemHooks.itemCodeByClassId[sdk.items.EdgeBow] = "Skystrike"; -ItemHooks.itemCodeByClassId[sdk.items.GreaterTalons] = "Bartuc's"; -ItemHooks.itemCodeByClassId[sdk.items.WristSword] = "Jade Talon"; -ItemHooks.itemCodeByClassId[sdk.items.BattleCestus] = "Shadow Killer"; -ItemHooks.itemCodeByClassId[sdk.items.FeralClaws] = "Firelizard's"; -ItemHooks.itemCodeByClassId[sdk.items.EttinAxe] = "Rune Master"; -ItemHooks.itemCodeByClassId[sdk.items.LichWand] = "Boneshade"; -ItemHooks.itemCodeByClassId[sdk.items.UnearthedWand] = "Death's Web"; -ItemHooks.itemCodeByClassId[sdk.items.FlyingAxe] = "Gimmershred"; -ItemHooks.itemCodeByClassId[sdk.items.WingedKnife] = "Warshrike"; -ItemHooks.itemCodeByClassId[sdk.items.WingedAxe] = "Lacerator"; -ItemHooks.itemCodeByClassId[sdk.items.Thresher] = "Reaper's Toll"; -ItemHooks.itemCodeByClassId[sdk.items.CrypticAxe] = "Tomb Reaver"; -ItemHooks.itemCodeByClassId[sdk.items.GiantThresher] = "Stormspire"; -ItemHooks.itemCodeByClassId[sdk.items.ArchonStaff] = "Mang Song's"; -ItemHooks.itemCodeByClassId[sdk.items.CrusaderBow] = "Eaglehorn"; -ItemHooks.itemCodeByClassId[sdk.items.WardBow] = "Ward Bow"; -ItemHooks.itemCodeByClassId[sdk.items.HydraBow] = "Windforce"; -ItemHooks.itemCodeByClassId[sdk.items.CeremonialBow] = "Lycander's Aim"; -ItemHooks.itemCodeByClassId[sdk.items.CeremonialPike] = "Lycander's Pike"; -ItemHooks.itemCodeByClassId[sdk.items.CeremonialJavelin] = "Titan's Revenge"; -ItemHooks.itemCodeByClassId[sdk.items.EldritchOrb] = "Eschuta's"; -ItemHooks.itemCodeByClassId[sdk.items.DimensionalShard] = "Death's Fathom"; -ItemHooks.itemCodeByClassId[sdk.items.MatriarchalBow] = "Bloodraven's"; -ItemHooks.itemCodeByClassId[sdk.items.MatriarchalSpear] = "Stoneraven"; -ItemHooks.itemCodeByClassId[sdk.items.MatriarchalJavelin] = "Thunder Stroke"; -ItemHooks.itemCodeByClassId[sdk.items.LightPlatedBoots] = "Goblin Toe"; -ItemHooks.itemCodeByClassId[sdk.items.Sallet] = "Rockstopper"; -ItemHooks.itemCodeByClassId[sdk.items.GhostArmor] = "Spirit Shroud"; -ItemHooks.itemCodeByClassId[sdk.items.SerpentskinArmor] = "Vipermagi's"; -ItemHooks.itemCodeByClassId[sdk.items.MeshArmor] = "Shaftstop"; -ItemHooks.itemCodeByClassId[sdk.items.RussetArmor] = "Skullder's"; -ItemHooks.itemCodeByClassId[sdk.items.MagePlate] = "Que-Hegan's"; -ItemHooks.itemCodeByClassId[sdk.items.SharkskinBoots] = "Waterwalk"; -ItemHooks.itemCodeByClassId[sdk.items.DemonHead] = "Andariel's Vis"; -ItemHooks.itemCodeByClassId[sdk.items.Tiara] = "Kira's"; -ItemHooks.itemCodeByClassId[sdk.items.Shako] = "Harlequin Crest"; -ItemHooks.itemCodeByClassId[sdk.items.WireFleece] = "Gladiator's Bane"; -ItemHooks.itemCodeByClassId[sdk.items.ScarabshellBoots] = "Sandstorm Trek's"; -ItemHooks.itemCodeByClassId[sdk.items.BoneweaveBoots] = "Marrowwalk"; -ItemHooks.itemCodeByClassId[sdk.items.MyrmidonGreaves] = "Shadow Dancer"; -ItemHooks.itemCodeByClassId[sdk.items.TotemicMask] = "Jalal's"; -ItemHooks.itemCodeByClassId[sdk.items.SlayerGuard] = "Arreat's Face"; -ItemHooks.itemCodeByClassId[sdk.items.GildedShield] = "HoZ"; -ItemHooks.itemCodeByClassId[sdk.items.HierophantTrophy] = "Homunculus"; -ItemHooks.itemCodeByClassId[sdk.items.BloodSpirit] = "Cerebus"; -ItemHooks.itemCodeByClassId[sdk.items.EarthSpirit] = "Spirit Keeper"; -ItemHooks.itemCodeByClassId[sdk.items.FuryVisor] = "Wolfhowl"; -ItemHooks.itemCodeByClassId[sdk.items.DestroyerHelm] = "Demonhorn's"; -ItemHooks.itemCodeByClassId[sdk.items.ConquerorCrown] = "Halaberd's"; -ItemHooks.itemCodeByClassId[sdk.items.SacredRondache] = "Alma Negra"; -ItemHooks.itemCodeByClassId[sdk.items.ZakarumShield] = "Dragonscale"; -ItemHooks.itemCodeByClassId[sdk.items.BloodlordSkull] = "Darkforce"; -ItemHooks.itemCodeByClassId[sdk.items.SuccubusSkull] = "Boneflame"; -ItemHooks.itemCodeByClassId[sdk.items.SmallCharm] = "Annihilus"; -ItemHooks.itemCodeByClassId[sdk.items.LargeCharm] = "Hellfire Torch"; -ItemHooks.itemCodeByClassId[sdk.items.GrandCharm] = "Gheed's"; -ItemHooks.itemCodeByClassId[sdk.items.Jewel] = "Facet"; -ItemHooks.itemCodeByClassId[sdk.items.quest.TokenofAbsolution] = "ÿc8Token"; -ItemHooks.itemCodeByClassId[sdk.items.quest.TwistedEssenceofSuffering] = "ÿc3Ess-Of-Suffering"; -ItemHooks.itemCodeByClassId[sdk.items.quest.ChargedEssenceofHatred] = "ÿc7Ess-Of-Hatred"; -ItemHooks.itemCodeByClassId[sdk.items.quest.BurningEssenceofTerror] = "ÿc1Ess-Of-Terror"; -ItemHooks.itemCodeByClassId[sdk.items.quest.FesteringEssenceofDestruction] = "ÿc3Ess-Of-Destruction"; - -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.JaggedStar, "Aldur's Wep", "Moonfall"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.HuntersGuise, "Aldur's Helm"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.ShadowPlate, "Aldur's Armor", "Steel Carapace"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.BattleBoots, "Aldur's Boots", "War Trav's"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Caduceus, "Griswold's Wep", "Moonfall"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Corona, "Griswold's Helm", "Crown of Ages"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.OrnatePlate, "Griswold's Armor", "Corpsemourn"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.VortexShield, "Griswold's Shield"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.OgreMaul, "IK Maul", "Windhammer"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.AvengerGuard, "IK Helm"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.SacredArmor, "IK Armor"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.WarGauntlets, "IK Gloves", "HellMouth"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.WarBoots, "IK Boots", "Gore Rider"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.GrandMatronBow, "Mavina's Bow"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.KrakenShell, "Mavina's Armor", "Leviathan"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Diadem, "Mavina's Helm", "Griffon's Eye"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.SharkskinBelt, "Mavina's Belt", "Razortail"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.BattleGauntlets, "Mavina's Gloves", "Lava Gout"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.ScissorsKatar, "Natalya's Wep"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.LoricatedMail, "Natalya's Armor"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.GrimHelm, "Natalya's Helm", "Vamp Gaze"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.MeshBoots, "Natalya's Boots", "Silkweave"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.SwirlingCrystal, "Tal Orb", "Occulus"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.LacqueredPlate, "Tal Armor"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.DeathMask, "Tal Helm", "Blackhorn's"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.MeshBelt, "Tal Belt", "Gloom's Trap"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.BoneVisage, "Trang Helm", "Giant Skull"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.ChaosArmor, "Trang Armor", "Black Hades"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.TrollBelt, "Trang Belt"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.HeavyBracers, "Trang Gloves", "Ghoulhide"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.CantorTrophy, "Trang Shield"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.ColossusBlade, "Bul-Kathos Blade", "Grandfather"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.MythicalSword, "Bul-Kathos Sword"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.WarHat, "Cow King's Helm", "Peasant Crown"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.StuddedLeather, "Cow King's Armor", "Twitchthroe"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.HeavyBoots, null, "Gorefoot"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Mace, "Heavens's Wep", "Crushflange"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.SpiredHelm, "Heavens's Helm", "Nightwing's"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Cuirass, "Heavens's Armor", "Duriel's Shell"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Ward, "Heavens's Shield"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Bill, "Hwanin's Bill", "Blackleach"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.TigulatedMail, "Hwanin's Armor", "Crow Caw"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.GrandCrown, "Hwanin's Helm", "Crown of Thieves"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Belt, null, "Nightsmoke"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.HellforgePlate, "Naj's Armor"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.ElderStaff, "Naj's Staff", "Ondal's Wisdom"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Circlet, "Naj's Helm", "Moonfall"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.SmallShield, null, "Umbral Disk"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.WingedHelm, "G-Face", "Valk Wing"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.HeavyBelt, "Orphan's Belt"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.HeavyGloves, null, "Bloodfist"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.CrypticSword, "Sazabi's Wep", "Frostwind"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.BalrogSkin, "Sazabi's Armor", "Arkaine's"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.BreastPlate, null, "Venom Ward"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.GothicShield, null, "The Ward"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.DuskShroud, "Disciple's Armor", "Ormus Robe's"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.MithrilCoil, "Disciple's Belt", "Verdungo's"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.BrambleMitts, "Disciple's Gloves"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.DemonhideBoots, "Disciple's Boots", "Infernostride"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.RingMail, "Angelic's Armor", "Darkglow"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Sabre, "Angelic's Wep", "Krintiz"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.SkullCap, "Arcanna's Helm", "Tarnhelm"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.LightPlate, "Arcanna's Armor", "Heavenly Garb"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.WarStaff, "Arcanna's Staff", "Iron Jang Bong"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.LightGauntlets, "Artic's Gloves", "Magefist"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.LightBelt, "Artic's Belt", "Snakecord"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.QuiltedArmor, "Artic's Armor", "Greyform"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.ShortWarBow, "Artic's Bow", "Hellclap"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.DoubleAxe, "Beserker's Axe", "Bladebone"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.SplintMail, "Beserker's Armor", "Iceblink"); -ItemHooks.addToCodeByClassIdAndQuality(sdk.items.Helm, "Beserker's Helm", "Coif of Glory"); - -ItemHooks.itemColorCode[sdk.items.quality.Magic] = { color: 0x97, code: "ÿc3" }; -ItemHooks.itemColorCode[sdk.items.quality.Set] = { color: 0x84, code: "ÿc2" }; -ItemHooks.itemColorCode[sdk.items.quality.Rare] = { color: 0x6F, code: "ÿc9" }; -ItemHooks.itemColorCode[sdk.items.quality.Unique] = { color: 0xA8, code: "ÿc4" }; +const ItemHooks = (function () { + const modifier = ( + 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename)) + ); + const ignoreItemTypes = [ + sdk.items.type.Gold, sdk.items.type.BowQuiver, + sdk.items.type.CrossbowQuiver, sdk.items.type.Book, + sdk.items.type.Gem, sdk.items.type.Scroll, + sdk.items.type.MissilePotion, sdk.items.type.Key, + sdk.items.type.Boots, sdk.items.type.Gloves, + sdk.items.type.ThrowingKnife, sdk.items.type.ThrowingAxe, + sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, + sdk.items.type.RejuvPotion, sdk.items.type.StaminaPotion, + sdk.items.type.AntidotePotion, sdk.items.type.ThawingPotion, + sdk.items.type.ChippedGem, sdk.items.type.FlawedGem, + sdk.items.type.StandardGem, sdk.items.type.FlawlessGem, + sdk.items.type.PerfectgGem, sdk.items.type.Amethyst, + sdk.items.type.Diamond, sdk.items.type.Emerald, + sdk.items.type.Ruby, sdk.items.type.Sapphire, + sdk.items.type.Topaz, sdk.items.type.Skull + ]; + /** + * Unique Items + */ + const codeById = new Map([ + [sdk.items.BattleAxe, "The Chieftain"], + [sdk.items.Falchion, "Gleamscythe"], + [sdk.items.BurntWand, "Suicide Branch"], + [sdk.items.PetrifiedWand, "Carin Shard"], + [sdk.items.TombWand, "King Leoric's Arm"], + [sdk.items.Quarterstaff, "Ribcracker"], + [sdk.items.EdgeBow, "Skystrike"], + [sdk.items.GreaterTalons, "Bartuc's"], + [sdk.items.WristSword, "Jade Talon"], + [sdk.items.BattleCestus, "Shadow Killer"], + [sdk.items.FeralClaws, "Firelizard's"], + [sdk.items.EttinAxe, "Rune Master"], + [sdk.items.LichWand, "Boneshade"], + [sdk.items.UnearthedWand, "Death's Web"], + [sdk.items.FlyingAxe, "Gimmershred"], + [sdk.items.WingedKnife, "Warshrike"], + [sdk.items.WingedAxe, "Lacerator"], + [sdk.items.Thresher, "Reaper's Toll"], + [sdk.items.CrypticAxe, "Tomb Reaver"], + [sdk.items.GiantThresher, "Stormspire"], + [sdk.items.ArchonStaff, "Mang Song's"], + [sdk.items.CrusaderBow, "Eaglehorn"], + [sdk.items.WardBow, "Ward Bow"], + [sdk.items.HydraBow, "Windforce"], + [sdk.items.CeremonialBow, "Lycander's Aim"], + [sdk.items.CeremonialPike, "Lycander's Pike"], + [sdk.items.CeremonialJavelin, "Titan's Revenge"], + [sdk.items.EldritchOrb, "Eschuta's"], + [sdk.items.DimensionalShard, "Death's Fathom"], + [sdk.items.MatriarchalBow, "Bloodraven's"], + [sdk.items.MatriarchalSpear, "Stoneraven"], + [sdk.items.MatriarchalJavelin, "Thunder Stroke"], + [sdk.items.LightPlatedBoots, "Goblin Toe"], + [sdk.items.Sallet, "Rockstopper"], + [sdk.items.GhostArmor, "Spirit Shroud"], + [sdk.items.SerpentskinArmor, "Vipermagi's"], + [sdk.items.MeshArmor, "Shaftstop"], + [sdk.items.RussetArmor, "Skullder's"], + [sdk.items.MagePlate, "Que-Hegan's"], + [sdk.items.SharkskinBoots, "Waterwalk"], + [sdk.items.DemonHead, "Andariel's Vis"], + [sdk.items.Tiara, "Kira's"], + [sdk.items.Shako, "Harlequin Crest"], + [sdk.items.WireFleece, "Gladiator's Bane"], + [sdk.items.ScarabshellBoots, "Sandstorm Trek's"], + [sdk.items.BoneweaveBoots, "Marrowwalk"], + [sdk.items.MyrmidonGreaves, "Shadow Dancer"], + [sdk.items.TotemicMask, "Jalal's"], + [sdk.items.SlayerGuard, "Arreat's Face"], + [sdk.items.GildedShield, "HoZ"], + [sdk.items.HierophantTrophy, "Homunculus"], + [sdk.items.BloodSpirit, "Cerebus"], + [sdk.items.EarthSpirit, "Spirit Keeper"], + [sdk.items.FuryVisor, "Wolfhowl"], + [sdk.items.DestroyerHelm, "Demonhorn's"], + [sdk.items.ConquerorCrown, "Halaberd's"], + [sdk.items.SacredRondache, "Alma Negra"], + [sdk.items.ZakarumShield, "Dragonscale"], + [sdk.items.BloodlordSkull, "Darkforce"], + [sdk.items.SuccubusSkull, "Boneflame"], + [sdk.items.SmallCharm, "Annihilus"], + [sdk.items.LargeCharm, "Hellfire Torch"], + [sdk.items.GrandCharm, "Gheed's"], + [sdk.items.Jewel, "Facet"], + /** Misc Items */ + [sdk.items.quest.TokenofAbsolution, "ÿc8Token"], + [sdk.items.quest.TwistedEssenceofSuffering, "ÿc3Ess-Of-Suffering"], + [sdk.items.quest.ChargedEssenceofHatred, "ÿc7Ess-Of-Hatred"], + [sdk.items.quest.BurningEssenceofTerror, "ÿc1Ess-Of-Terror"], + [sdk.items.quest.FesteringEssenceofDestruction, "ÿc3Ess-Of-Destruction"], + ]); + /** + * @param {string} setName + * @param {string} uniqueName + * @returns {Map} + */ + const buildClassIdAndQuality = function (setName = "", uniqueName = "") { + let temp = new Map(); + setName && temp.set(sdk.items.quality.Set, setName); + uniqueName && temp.set(sdk.items.quality.Unique, uniqueName); + return temp; + }; + /** + * Set/Unique Items + */ + const codeByIdAndQuality = new Map([ + [sdk.items.JaggedStar, buildClassIdAndQuality("Aldur's Wep", "Moonfall")], + [sdk.items.HuntersGuise, buildClassIdAndQuality("Aldur's Helm")], + [sdk.items.ShadowPlate, buildClassIdAndQuality("Aldur's Armor", "Steel Carapace")], + [sdk.items.BattleBoots, buildClassIdAndQuality("Aldur's Boots", "War Trav's")], + [sdk.items.Caduceus, buildClassIdAndQuality("Griswold's Wep", "Astreon's")], + [sdk.items.Crown, buildClassIdAndQuality("Griswold's Helm", "Crown of Ages")], + [sdk.items.OrnatePlate, buildClassIdAndQuality("Griswold's Armor", "Corpsemourn")], + [sdk.items.VortexShield, buildClassIdAndQuality("Griswold's Shield")], + [sdk.items.OgreMaul, buildClassIdAndQuality("IK Maul", "Windhammer")], + [sdk.items.AvengerGuard, buildClassIdAndQuality("IK Helm")], + [sdk.items.SacredArmor, buildClassIdAndQuality("IK Armor")], + [sdk.items.WarGauntlets, buildClassIdAndQuality("IK Gloves", "HellMouth")], + [sdk.items.WarBoots, buildClassIdAndQuality("IK Boots", "Gore Rider")], + [sdk.items.GrandMatronBow, buildClassIdAndQuality("Mavina's Bow")], + [sdk.items.KrakenShell, buildClassIdAndQuality("Mavina's Armor", "Leviathan")], + [sdk.items.Diadem, buildClassIdAndQuality("Mavina's Helm", "Griffon's Eye")], + [sdk.items.SharkskinBelt, buildClassIdAndQuality("Mavina's Belt", "Razortail")], + [sdk.items.BattleGauntlets, buildClassIdAndQuality("Mavina's Gloves", "Lava Gout")], + [sdk.items.ScissorsSuwayyah, buildClassIdAndQuality("Natalya's Wep")], + [sdk.items.LoricatedMail, buildClassIdAndQuality("Natalya's Armor")], + [sdk.items.GrimHelm, buildClassIdAndQuality("Natalya's Helm", "Vamp Gaze")], + [sdk.items.MeshBoots, buildClassIdAndQuality("Natalya's Boots", "Silkweave")], + [sdk.items.SwirlingCrystal, buildClassIdAndQuality("Tal Orb", "Occulus")], + [sdk.items.LacqueredPlate, buildClassIdAndQuality("Tal Armor")], + [sdk.items.DeathMask, buildClassIdAndQuality("Tal Helm", "Blackhorn's")], + [sdk.items.MeshBelt, buildClassIdAndQuality("Tal Belt", "Gloom's Trap")], + [sdk.items.BoneVisage, buildClassIdAndQuality("Trang Helm", "Giant Skull")], + [sdk.items.ChaosArmor, buildClassIdAndQuality("Trang Armor", "Black Hades")], + [sdk.items.TrollBelt, buildClassIdAndQuality("Trang Belt")], + [sdk.items.HeavyBracers, buildClassIdAndQuality("Trang Gloves", "Ghoulhide")], + [sdk.items.CantorTrophy, buildClassIdAndQuality("Trang Shield")], + [sdk.items.ColossusBlade, buildClassIdAndQuality("Bul-Kathos Blade", "Grandfather")], + [sdk.items.MythicalSword, buildClassIdAndQuality("Bul-Kathos Sword")], + [sdk.items.WarHat, buildClassIdAndQuality("Cow King's Helm", "Peasant Crown")], + [sdk.items.StuddedLeather, buildClassIdAndQuality("Cow King's Armor", "Twitchthroe")], + [sdk.items.HeavyBoots, buildClassIdAndQuality(null, "Gorefoot")], + [sdk.items.Mace, buildClassIdAndQuality("Heavens's Wep", "Crushflange")], + [sdk.items.SpiredHelm, buildClassIdAndQuality("Heavens's Helm", "Nightwing's")], + [sdk.items.Cuirass, buildClassIdAndQuality("Heavens's Armor", "Duriel's Shell")], + [sdk.items.Ward, buildClassIdAndQuality("Heavens's Shield")], + [sdk.items.Bill, buildClassIdAndQuality("Hwanin's Bill", "Blackleach")], + [sdk.items.TigulatedMail, buildClassIdAndQuality("Hwanin's Armor", "Crow Caw")], + [sdk.items.GrandCrown, buildClassIdAndQuality("Hwanin's Helm", "Crown of Thieves")], + [sdk.items.Belt, buildClassIdAndQuality(null, "Nightsmoke")], + [sdk.items.HellforgePlate, buildClassIdAndQuality("Naj's Armor")], + [sdk.items.ElderStaff, buildClassIdAndQuality("Naj's Staff", "Ondal's Wisdom")], + [sdk.items.Circlet, buildClassIdAndQuality("Naj's Helm")], + [sdk.items.SmallShield, buildClassIdAndQuality(null, "Umbral Disk")], + [sdk.items.WingedHelm, buildClassIdAndQuality("G-Face", "Valk Wing")], + [sdk.items.HeavyBelt, buildClassIdAndQuality("Orphan's Belt")], + [sdk.items.HeavyGloves, buildClassIdAndQuality(null, "Bloodfist")], + [sdk.items.CrypticSword, buildClassIdAndQuality("Sazabi's Wep", "Frostwind")], + [sdk.items.BalrogSkin, buildClassIdAndQuality("Sazabi's Armor", "Arkaine's")], + [sdk.items.BreastPlate, buildClassIdAndQuality(null, "Venom Ward")], + [sdk.items.GothicShield, buildClassIdAndQuality(null, "The Ward")], + [sdk.items.DuskShroud, buildClassIdAndQuality("Disciple's Armor", "Ormus Robe's")], + [sdk.items.MithrilCoil, buildClassIdAndQuality("Disciple's Belt", "Verdungo's")], + [sdk.items.BrambleMitts, buildClassIdAndQuality("Disciple's Gloves")], + [sdk.items.DemonhideBoots, buildClassIdAndQuality("Disciple's Boots", "Infernostride")], + [sdk.items.RingMail, buildClassIdAndQuality("Angelic's Armor", "Darkglow")], + [sdk.items.Sabre, buildClassIdAndQuality("Angelic's Wep", "Krintiz")], + [sdk.items.SkullCap, buildClassIdAndQuality("Arcanna's Helm", "Tarnhelm")], + [sdk.items.LightPlate, buildClassIdAndQuality("Arcanna's Armor", "Heavenly Garb")], + [sdk.items.WarStaff, buildClassIdAndQuality("Arcanna's Staff", "Iron Jang Bong")], + [sdk.items.LightGauntlets, buildClassIdAndQuality("Artic's Gloves", "Magefist")], + [sdk.items.LightBelt, buildClassIdAndQuality("Artic's Belt", "Snakecord")], + [sdk.items.QuiltedArmor, buildClassIdAndQuality("Artic's Armor", "Greyform")], + [sdk.items.ShortWarBow, buildClassIdAndQuality("Artic's Bow", "Hellclap")], + /** Berserker's */ + [sdk.items.DoubleAxe, buildClassIdAndQuality("Beserker's Axe", "Bladebone")], + [sdk.items.SplintMail, buildClassIdAndQuality("Beserker's Armor", "Iceblink")], + [sdk.items.Helm, buildClassIdAndQuality("Beserker's Helm", "Coif of Glory")], + /** Tancred's */ + [sdk.items.BoneHelm, buildClassIdAndQuality("Tancred's Skull", "Wormskull")], + [sdk.items.FullPlateMail, buildClassIdAndQuality("Tancred's Spine", "Goldskin")], + [sdk.items.MilitaryPick, buildClassIdAndQuality("Tancred's Crowbill", "Skull Splitter")], + [sdk.items.Boots, buildClassIdAndQuality("Tancred's Hobnails", "Hotspur")], + ]); + const itemColorCode = {}; + itemColorCode[sdk.items.quality.Magic] = { color: 0x97, code: "ÿc3" }; + itemColorCode[sdk.items.quality.Set] = { color: 0x84, code: "ÿc2" }; + itemColorCode[sdk.items.quality.Rare] = { color: 0x6F, code: "ÿc9" }; + itemColorCode[sdk.items.quality.Unique] = { color: 0xA8, code: "ÿc4" }; + + + return { + enabled: true, + pickitEnabled: false, + hooks: [], + + check: function () { + if (!this.enabled) { + this.flush(); + + return; + } + + for (let i = 0; i < this.hooks.length; i++) { + if (!copyUnit(this.hooks[i].item).x) { + for (let j = 0; j < this.hooks[i].hook.length; j++) { + this.hooks[i].hook[j].remove(); + } + + this.hooks[i].name[0] && this.hooks[i].name[0].remove(); + this.hooks[i].vector[0] && this.hooks[i].vector[0].remove(); + this.hooks.splice(i, 1); + i -= 1; + this.flush(); + } + } + + let item = Game.getItem(); + + if (item) { + try { + do { + if (item.area === ActionHooks.currArea && item.onGroundOrDropping + && (item.quality >= sdk.items.quality.Magic || ((item.normal || item.superior) && !ignoreItemTypes.includes(item.itemType)))) { + if (this.pickitEnabled) { + if ([Pickit.Result.UNWANTED, Pickit.Result.TRASH].indexOf(Pickit.checkItem(item).result) === -1) { + !this.getHook(item) && this.add(item); + } + } else { + !this.getHook(item) && this.add(item); + } + + this.getHook(item) && this.update(); + } else { + this.remove(item); + } + } while (item.getNext()); + } catch (e) { + console.error(e); + this.flush(); + } + } + }, + + update: function () { + for (let i = 0; i < this.hooks.length; i++) { + this.hooks[i].vector[0].x = me.x; + this.hooks[i].vector[0].y = me.y; + } + }, + + /** + * @param {ItemUnit} item + * @returns {string} + */ + getName: function (item) { + let abbr = item.name.split(" "); + let abbrName = ""; + + if (abbr[1]) { + abbrName += abbr[0] + "-"; + + for (let i = 1; i < abbr.length; i++) { + abbrName += abbr[i].substring(0, 2); + } + } + + return !!abbrName ? abbrName : item.name; + }, + + /** + * @description Create a new hook for a item with custom color and code based on type/quality/classid + * @param {ItemUnit} item + * @todo maybe make class wrappers for hooks and turn the hook array into a map? + */ + newHook: function (item) { + let color = 0, code = "", arr = [], name = [], vector = []; + let eth = (item.ethereal ? "Eth: " : ""); + + switch (item.quality) { + case sdk.items.quality.Normal: + case sdk.items.quality.Superior: + switch (item.itemType) { + case sdk.items.type.Quest: + color = 0x9A; + code += (codeById.get(item.classid) || "ÿc8" + item.fname); + + break; + case sdk.items.type.Rune: + if (item.classid >= sdk.items.runes.Vex) { + [color, code] = [0x9B, "ÿc;" + item.fname]; + } else if (item.classid >= sdk.items.runes.Lum) { + [color, code] = [0x9A, "ÿc8" + item.fname]; + } else { + [color, code] = [0xA1, item.fname]; + } + + break; + default: + if (item.name && item.sockets !== 1) { + color = 0x20; + + if (item.runeword) { + code = item.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, ""); + } else { + code = "ÿc0" + (item.sockets > 0 ? "[" + item.sockets + "]" : ""); + code += this.getName(item); + item.itemType === sdk.items.type.AuricShields && (code += "[R: " + item.getStat(sdk.stats.FireResist) + "]"); + code += "(" + item.ilvl + ")"; + } + } + + break; + } + + break; + case sdk.items.quality.Set: + case sdk.items.quality.Unique: + ({ color, code } = itemColorCode[item.quality]); + + if (codeById.has(item.classid)) { + code += codeById.get(item.classid); + } + + switch (item.classid) { + case sdk.items.Ring: + case sdk.items.Amulet: + code += item.name + "(" + item.ilvl + ")"; + + break; + default: + { + let check = codeByIdAndQuality.get(item.classid); + code += ((check && check.get(item.quality)) || item.name); + } + + break; + } + + break; + case sdk.items.quality.Magic: + case sdk.items.quality.Rare: + if (item.name) { + ({ color, code } = itemColorCode[item.quality]); + + code += (item.sockets > 0 ? "[" + item.sockets + "]" : ""); + code += this.getName(item); + code += "(" + item.ilvl + ")"; + } + + break; + } + + !!code && name.push(new Text(eth + code, 665 + Hooks.resfix.x, 104 + modifier + (this.hooks.length * 14), color, 0, 0)); + vector.push(new Line(me.x, me.y, item.x, item.y, color, true)); + arr.push(new Line(item.x - 3, item.y, item.x + 3, item.y, color, true)); + arr.push(new Line(item.x, item.y - 3, item.x, item.y + 3, color, true)); + + return { + itemLoc: arr, + itemName: name, + vector: vector, + }; + }, + + /** + * Add new item hook to our hook array + * @param {ItemUnit} item + */ + add: function (item) { + if (item === undefined || !item.classid) { + return; + } + + let temp = this.newHook(item); + + this.hooks.push({ + item: copyUnit(item), + area: item.area, + hook: temp.itemLoc, + name: temp.itemName, + vector: temp.vector, + }); + }, + + /** + * Get item hook if it exists based on item parameters gid + * @param {ItemUnit} item + * @returns {{ item: ItemUnit, area: number, hook: Line, name: Text, vector: Line} | false} + */ + getHook: function (item) { + for (let i = 0; i < this.hooks.length; i++) { + if (this.hooks[i].item.gid === item.gid) { + return this.hooks[i].hook; + } + } + + return false; + }, + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + remove: function (item) { + for (let i = 0; i < this.hooks.length; i++) { + if (this.hooks[i].item.gid === item.gid) { + for (let j = 0; j < this.hooks[i].hook.length; j++) { + this.hooks[i].hook[j].remove(); + } + + this.hooks[i].name[0] && this.hooks[i].name[0].remove(); + this.hooks[i].vector[0] && this.hooks[i].vector[0].remove(); + this.hooks.splice(i, 1); + + return true; + } + } + + return false; + }, + + flush: function () { + while (this.hooks.length) { + for (let j = 0; j < this.hooks[0].hook.length; j++) { + this.hooks[0].hook[j].remove(); + } + + this.hooks[0].name[0] && this.hooks[0].name[0].remove(); + this.hooks[0].vector[0] && this.hooks[0].vector[0].remove(); + this.hooks.shift(); + } + } + }; +})(); diff --git a/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js b/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js index 3a35e33c0..9721c415e 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js @@ -6,102 +6,109 @@ * */ -const MonsterHooks = { - hooks: [], - enabled: true, - - check: function () { - if (!this.enabled || me.inTown) { - this.flush(); - - return; - } - - for (let i = 0; i < this.hooks.length; i += 1) { - if (!copyUnit(this.hooks[i].unit).x) { - this.hooks[i].hook.remove(); - this.hooks.splice(i, 1); - - i -= 1; - } - } - - let unit = Game.getMonster(); - - if (unit) { - do { - if (unit.attackable) { - !this.getHook(unit) ? this.add(unit) : this.updateCoords(unit); - } else { - this.remove(unit); - } - } while (unit.getNext()); - } - }, - - // credit DetectiveSquirrel from his maphack https://github.com/DetectiveSquirrel/Kolbot-MapThread/blob/9c721a72a934518cfca1d1a05211b5e03b5b624f/kolbot/tools/MapThread.js#L2353 - specTypeColor: function (unit) { - switch (unit.spectype) { - case sdk.monsters.spectype.Minion: - return 3; - case sdk.monsters.spectype.Magic: - return 9; - case sdk.monsters.spectype.Unique: - return 11; - case sdk.monsters.spectype.SuperUnique: - return 2; - default: - return 8; - } - }, - - add: function (unit) { - this.hooks.push({ - unit: copyUnit(unit), - hook: new Text((unit.spectype & 0xF ? "O" : "X"), unit.x, unit.y, this.specTypeColor(unit), 1, null, true) - }); - }, - - updateCoords: function (unit) { - let hook = this.getHook(unit); - - if (!hook) { - return false; - } - - hook.x = unit.x; - hook.y = unit.y; - - return true; - }, - - getHook: function (unit) { - for (let i = 0; i < this.hooks.length; i += 1) { - if (this.hooks[i].unit.gid === unit.gid) { - return this.hooks[i].hook; - } - } - - return false; - }, - - remove: function (unit) { - for (let i = 0; i < this.hooks.length; i += 1) { - if (this.hooks[i].unit.gid === unit.gid) { - this.hooks[i].hook.remove(); - this.hooks.splice(i, 1); - - return true; - } - } - - return false; - }, - - flush: function () { - while (this.hooks.length) { - this.hooks[0].hook.remove(); - this.hooks.shift(); - } - } -}; +const MonsterHooks = (function () { + /** + * @author DetectiveSquirrel from his maphack + * https://github.com/DetectiveSquirrel/Kolbot-MapThread/blob/9c721a72a934518cfca1d1a05211b5e03b5b624f/kolbot/threads/MapThread.js#L2353 + * @param {Monster} unit + * @returns {number} + */ + function specTypeColor (unit) { + switch (unit.spectype) { + case sdk.monsters.spectype.Minion: + return 3; + case sdk.monsters.spectype.Magic: + return 9; + case sdk.monsters.spectype.Unique: + return 11; + case sdk.monsters.spectype.SuperUnique: + return 2; + default: + return 8; + } + } + + /** + * @constructor + * @param {Monster} unit + */ + function MonsterHook (unit) { + this.unit = copyUnit(unit); + this.hook = new Text((unit.spectype & 0xF ? "O" : "X"), unit.x, unit.y, specTypeColor(unit), 1, null, true); + } + + MonsterHook.prototype.update = function () { + if (!this.unit || !this.unit.x || !this.unit.attackable) { + this.hook.remove(); + return; + } + this.hook.x = this.unit.x; + this.hook.y = this.unit.y; + }; + + return { + /** @type {Object.} */ + hooks: {}, + enabled: true, + + check: function () { + if (!this.enabled || me.inTown) { + this.flush(); + + return; + } + + for (let m in this.hooks) { + if (!this.hooks.hasOwnProperty(m)) { + continue; + } + + if (!copyUnit(this.hooks[m].unit).x) { + this.hooks[m].hook.remove(); + delete this.hooks[m]; + } + } + + let unit = Game.getMonster(); + + if (unit) { + do { + if (unit.attackable) { + if (!this.hooks[unit.gid]) { + this.hooks[unit.gid] = new MonsterHook(unit); + } else { + this.hooks[unit.gid].update(); + } + } else { + if (this.hooks[unit.gid]) { + this.hooks[unit.gid].hook.remove(); + delete this.hooks[unit.gid]; + } + } + } while (unit.getNext()); + } + }, + + /** @param {Monster} unit */ + remove: function (unit) { + if (this.hooks.hasOwnProperty(unit.gid)) { + this.hooks[unit.gid].hook.remove(); + delete this.hooks[unit.gid]; + + return true; + } + + return false; + }, + + flush: function () { + for (let m in this.hooks) { + if (!this.hooks.hasOwnProperty(m)) { + continue; + } + this.hooks[m].hook.remove(); + delete this.hooks[m]; + } + } + }; +})(); diff --git a/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js b/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js index 826f0cac2..6876b4a6c 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js @@ -6,110 +6,107 @@ */ const ShrineHooks = { - enabled: true, - hooks: [], - shrines: {}, - - check: function () { - if (!this.enabled || me.inTown) { - this.flush(); - - return; - } - - for (let i = 0; i < this.hooks.length; i++) { - if (!copyUnit(this.hooks[i].shrine).objtype) { - this.hooks[i].hook[0].remove(); - this.hooks.splice(i, 1); - - i -= 1; - } - } - - let shrine = Game.getObject("shrine"); - - if (shrine) { - do { - if (shrine.mode === sdk.objects.mode.Inactive) { - if (!this.getHook(shrine)) { - this.add(shrine); - } - } else { - this.remove(shrine); - } - } while (shrine.getNext()); - } - }, - - newHook: function (shrine) { - let arr = []; - let typeName; - - typeName = this.shrines[shrine.objtype]; - typeName && arr.push(new Text(typeName, shrine.x, shrine.y, 4, 6, 2, true)); - - return arr; - }, - - add: function (shrine) { - if (!shrine.objtype) return; - - this.hooks.push({ - shrine: copyUnit(shrine), - hook: this.newHook(shrine) - }); - }, - - getHook: function (shrine) { - for (let i = 0; i < this.hooks.length; i++) { - if (this.hooks[i].shrine.gid === shrine.gid) { - return this.hooks[i].hook; - } - } - - return false; - }, - - remove: function (shrine) { - for (let i = 0; i < this.hooks.length; i++) { - if (this.hooks[i].shrine.gid === shrine.gid) { - this.hooks[i].hook[0].remove(); - this.hooks.splice(i, 1); - - return true; - } - } - - return false; - }, - - flush: function () { - while (this.hooks.length) { - this.hooks[0].hook[0].remove(); - this.hooks.shift(); - } - } + enabled: true, + hooks: [], + shrines: new Map(), + + check: function () { + if (!this.enabled || me.inTown) { + this.flush(); + + return; + } + + for (let i = 0; i < this.hooks.length; i++) { + if (!copyUnit(this.hooks[i].shrine).objtype) { + this.hooks[i].hook[0].remove(); + this.hooks.splice(i, 1); + + i -= 1; + } + } + + let shrine = Game.getObject(); + + if (shrine) { + do { + if (this.shrines.has(shrine.objtype) && shrine.name.toLowerCase().includes("shrine")) { + if (shrine.mode === sdk.objects.mode.Inactive) { + if (!this.getHook(shrine)) { + this.add(shrine); + } + } else { + this.remove(shrine); + } + } + } while (shrine.getNext()); + } + }, + + newHook: function (shrine) { + let typeName = this.shrines.get(shrine.objtype); + return typeName ? [new Text(typeName, shrine.x, shrine.y, 4, 6, 2, true)] : []; + }, + + add: function (shrine) { + if (!shrine.objtype) return; + + this.hooks.push({ + shrine: copyUnit(shrine), + hook: this.newHook(shrine) + }); + }, + + getHook: function (shrine) { + for (let i = 0; i < this.hooks.length; i++) { + if (this.hooks[i].shrine.gid === shrine.gid) { + return this.hooks[i].hook; + } + } + + return false; + }, + + remove: function (shrine) { + for (let i = 0; i < this.hooks.length; i++) { + if (this.hooks[i].shrine.gid === shrine.gid) { + this.hooks[i].hook[0].remove(); + this.hooks.splice(i, 1); + + return true; + } + } + + return false; + }, + + flush: function () { + while (this.hooks.length) { + this.hooks[0].hook[0].remove(); + this.hooks.shift(); + } + } }; -ShrineHooks.shrines[sdk.shrines.Refilling] = "Refilling"; -ShrineHooks.shrines[sdk.shrines.Health] = "Health"; -ShrineHooks.shrines[sdk.shrines.Mana] = "Mana"; -ShrineHooks.shrines[sdk.shrines.HealthExchange] = "Health Exchange"; -ShrineHooks.shrines[sdk.shrines.ManaExchange] = "Mana Exchange"; -ShrineHooks.shrines[sdk.shrines.Armor] = "Armor"; -ShrineHooks.shrines[sdk.shrines.Combat] = "Combat"; -ShrineHooks.shrines[sdk.shrines.ResistFire] = "Resist Fire"; -ShrineHooks.shrines[sdk.shrines.ResistCold] = "Resist Cold"; -ShrineHooks.shrines[sdk.shrines.ResistLightning] = "Resist Lightning"; -ShrineHooks.shrines[sdk.shrines.ResistPoison] = "Resist Poison"; -ShrineHooks.shrines[sdk.shrines.Skill] = "Skill"; -ShrineHooks.shrines[sdk.shrines.ManaRecharge] = "Mana Recharge"; -ShrineHooks.shrines[sdk.shrines.Stamina] = "Stamina"; -ShrineHooks.shrines[sdk.shrines.Experience] = "Experience"; -ShrineHooks.shrines[sdk.shrines.Enirhs] = "Enirhs"; -ShrineHooks.shrines[sdk.shrines.Portal] = "Portal"; -ShrineHooks.shrines[sdk.shrines.Gem] = "Gem"; -ShrineHooks.shrines[sdk.shrines.Fire] = "Fire"; -ShrineHooks.shrines[sdk.shrines.Monster] = "Monster"; -ShrineHooks.shrines[sdk.shrines.Exploding] = "Exploding"; -ShrineHooks.shrines[sdk.shrines.Poison] = "Poison"; +ShrineHooks.shrines.set(sdk.shrines.Refilling, "Refilling"); +ShrineHooks.shrines.set(sdk.shrines.Health, "Health"); +ShrineHooks.shrines.set(sdk.shrines.Mana, "Mana"); +ShrineHooks.shrines.set(sdk.shrines.HealthExchange, "Health Exchange"); +ShrineHooks.shrines.set(sdk.shrines.ManaExchange, "Mana Exchange"); +ShrineHooks.shrines.set(sdk.shrines.Armor, "Armor"); +ShrineHooks.shrines.set(sdk.shrines.Combat, "Combat"); +ShrineHooks.shrines.set(sdk.shrines.ResistFire, "Resist Fire"); +ShrineHooks.shrines.set(sdk.shrines.ResistCold, "Resist Cold"); +ShrineHooks.shrines.set(sdk.shrines.ResistLightning, "Resist Lightning"); +ShrineHooks.shrines.set(sdk.shrines.ResistPoison, "Resist Poison"); +ShrineHooks.shrines.set(sdk.shrines.Skill, "Skill"); +ShrineHooks.shrines.set(sdk.shrines.ManaRecharge, "Mana Recharge"); +ShrineHooks.shrines.set(sdk.shrines.Stamina, "Stamina"); +ShrineHooks.shrines.set(sdk.shrines.Experience, "Experience"); +ShrineHooks.shrines.set(sdk.shrines.Enirhs, "Enirhs"); +ShrineHooks.shrines.set(sdk.shrines.Portal, "Portal"); +ShrineHooks.shrines.set(sdk.shrines.Gem, "Gem"); +ShrineHooks.shrines.set(sdk.shrines.Fire, "Fire"); +ShrineHooks.shrines.set(sdk.shrines.Monster, "Monster"); +ShrineHooks.shrines.set(sdk.shrines.Exploding, "Exploding"); +ShrineHooks.shrines.set(sdk.shrines.Poison, "Poison"); diff --git a/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js index d52a4a213..e95b3e064 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * @filename TextHooks.js * @author theBGuy @@ -6,308 +7,308 @@ */ const TextHooks = { - enabled: true, - lastAct: 0, - wasInTown: true, - displayTitle: true, - displaySettings: true, - frameworkDisplayed: false, - frameYSizeScale: 0, - frameYLocScale: 0, - settingsModifer: 0, - dashBoardWidthScale: 0, - statusFrameYSize: 0, - qolFrameYSize: 0, - statusHookNames: ["pickitStatus", "vectorStatus", "monsterStatus", "itemStatus"], - qols: ["previousAct", "nextAct", "key6", "key5"], - statusHooks: [], - dashBoard: [], - qolHooks: [], - hooks: [], - yLocMapScale: {1: 40, 2: 30, 3: 20, 4: 10, 6: -10, 9: -40}, - modifier: 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename) + Number(!!me.gameserverip && !me.realm)), - - getScale: function (hkLen) { - if (!!this.yLocMapScale[hkLen]) { - this.frameYSizeScale = (-1 * this.yLocMapScale[hkLen]); - this.frameYLocScale = this.yLocMapScale[hkLen]; - } else { - this.frameYSizeScale = 0; - this.frameYLocScale = 0; - } - - this.settingsModifer = Math.max(0, hkLen - 3); - }, - - check: function () { - if (!this.enabled) { - this.flush(); - - return; - } - - if (!this.frameworkDisplayed) { - !this.getHook("credits", this.hooks) && this.add("credits"); - !!me.gameserverip && !this.getHook("ip", this.hooks) && this.add("ip"); - this.lastAct = 0; // sorta hacky solution, but works this will cause qolBoard to update after being flushed from a uiflag - this.frameworkDisplayed = true; - } - - this.displaySettings ? !this.getHook("showSettings", this.statusHooks) && this.add("showSettings") : !this.getHook("hideSettings", this.statusHooks) && this.add("hideSettings"); - this.displayTitle && !this.getHook("title", this.hooks) && this.add("title"); - !this.getHook("ping", this.hooks) ? this.add("ping") : (this.getHook("ping", this.hooks).hook.text = "Ping: " + me.ping); - !this.getHook("time", this.hooks) ? this.add("time") : (this.getHook("time", this.hooks).hook.text = this.timer()); - }, - - update: function (hkLen = 0) { - let updateSettingsDisplay = (this.displaySettings && this.settingsModifer < Math.max(0, hkLen - 3)); - - this.getScale(hkLen); - this.add("dashboard"); - updateSettingsDisplay && this.add("showSettings"); - (this.lastAct !== me.act || this.wasInTown !== me.inTown || !this.getHook("qolBoard", this.qolHooks)) && this.add("qolBoard"); - }, - - hookHandler: function (click, x, y) { - function sortHooks(h1, h2) { - return Math.abs(h1.hook.y - y) - Math.abs(h2.hook.y - y); - } - - if (click === 0) { - TextHooks.statusHooks.sort(sortHooks); - - if (TextHooks.statusHooks[0].name === "hideSettings" || TextHooks.statusHooks[0].name === "showSettings") { - TextHooks.displaySettings = !TextHooks.displaySettings; - - return true; - } - } - - return false; - }, - - add: function (name, hookArr = []) { - let orginalLen = hookArr.length; - - switch (name) { - case "credits": - this.hooks.push({ - name: "credits", - hook: new Text("MM by theBGuy", 0, 600 + Hooks.resfix.y, 4, 0, 0) - }); - - break; - case "title": - this.hooks.push({ - name: "title", - hook: new Text(":: Running Map-Mode, enter .help in chat to see more commands ::", 0, 13, 4, 0, 0) - }); - - break; - case "ping": - this.hooks.push({ - name: "ping", - hook: new Text("Ping: " + me.ping, 785 + Hooks.resfix.x, 56 + this.modifier, 4, 1, 1) - }); - - break; - case "time": - this.hooks.push({ - name: "time", - hook: new Text(this.timer(), 785 + Hooks.resfix.x, 72 + this.modifier, 4, 1, 1) - }); - - break; - case "ip": - this.hooks.push({ - name: "ip", - hook: new Text("IP: " + (me.gameserverip.length > 0 ? me.gameserverip.split(".")[3] : "0"), 785 + Hooks.resfix.x, 88 + this.modifier, 4, 1, 1) - }); - - break; - case "dashboard": - while (this.dashBoard.length) { - this.dashBoard.shift().hook.remove(); - } - - this.dashBoard.push({ - name: "dashboard", - hook: new Box(Hooks.dashBoard.x, Hooks.dashBoard.y + Hooks.resfix.y + this.frameYLocScale, 225, 60 + this.frameYSizeScale, 0x0, 1, 0) - }); - - this.dashBoard.push({ - name: "dashboardframe", - hook: new Frame(Hooks.dashBoard.x, Hooks.dashBoard.y + Hooks.resfix.y + this.frameYLocScale, 225, 60 + this.frameYSizeScale, 0) - }); - - this.getHook("dashboard", this.dashBoard).hook.zorder = 0; - - break; - case "key5": - this.qolHooks.push({ - name: "key5", - hook: new Text("Key 5: " + (me.inTown ? "Heal" : "Make Portal"), Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) - }); - - break; - case "key6": - this.qolHooks.push({ - name: "key6", - hook: new Text("Key 6: " + (me.inTown ? "Open Stash" : "Go To Town"), Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) - }); - - break; - case "nextAct": - me.inTown && Pather.accessToAct(me.act + 1) && this.qolHooks.push({ - name: "Next Act", - dest: me.act + 1, - type: "actChange", - hook: new Text("Shift > : Next Act", Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) - }); - - break; - case "previousAct": - me.inTown && me.act > 1 && this.qolHooks.push({ - name: "Previous Act", - dest: me.act - 1, - type: "actChange", - hook: new Text("Shift < : Previous Act", Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) - }); - - break; - case "qolBoard": - this.qolFrameYSize = 50; - this.lastAct = me.act; - this.wasInTown = me.inTown; - - while (this.qolHooks.length) { - this.qolHooks.shift().hook.remove(); - } - - this.qols.forEach(function (hook) { - TextHooks.add(hook, TextHooks.qolHooks) && (TextHooks.qolFrameYSize -= 10); - }); - - this.qolHooks.push({ - name: "qolBoard", - hook: new Box(Hooks.qolBoard.x + Hooks.resfix.x, Hooks.qolBoard.y + this.qolFrameYSize + Hooks.resfix.y, 140, 60 + (-1 * this.qolFrameYSize), 0x0, 1, 0) - }); - - this.qolHooks.push({ - name: "qolFrame", - hook: new Frame(Hooks.qolBoard.x + Hooks.resfix.x, Hooks.qolBoard.y + this.qolFrameYSize + Hooks.resfix.y, 140, 60 + (-1 * this.qolFrameYSize), 0) - }); - - this.qolHooks[this.qolHooks.length - 2].hook.zorder = 0; - - break; - case "pickitStatus": - this.statusHooks.push({ - name: "pickitStatus", - hook: new Text("ÿc4N-Pad - ÿc0: " + (ItemHooks.pickitEnabled ? "ÿc orginalLen; - }, - - getHook: function (name, hookArr = []) { - for (let i = 0; i < hookArr.length; i++) { - if (hookArr[i].name === name) { - return hookArr[i]; - } - } - - return false; - }, - - timer: function () { - return " (" + new Date(getTickCount() - me.gamestarttime).toISOString().slice(11, -5) + ")"; - }, - - flush: function () { - if (!Hooks.enabled) { - while (this.hooks.length) { - this.hooks.shift().hook.remove(); - } - - this.frameworkDisplayed = false; - } - - while (this.statusHooks.length) { - this.statusHooks.shift().hook.remove(); - } - - while (this.dashBoard.length) { - this.dashBoard.shift().hook.remove(); - } - - while (this.qolHooks.length) { - this.qolHooks.shift().hook.remove(); - } - }, + enabled: true, + lastAct: 0, + wasInTown: true, + displayTitle: true, + displaySettings: true, + frameworkDisplayed: false, + frameYSizeScale: 0, + frameYLocScale: 0, + settingsModifer: 0, + dashBoardWidthScale: 0, + statusFrameYSize: 0, + qolFrameYSize: 0, + statusHookNames: ["pickitStatus", "vectorStatus", "monsterStatus", "itemStatus"], + qols: ["previousAct", "nextAct", "key6", "key5"], + statusHooks: [], + dashBoard: [], + qolHooks: [], + hooks: [], + yLocMapScale: { 1: 40, 2: 30, 3: 20, 4: 10, 6: -10, 9: -40 }, + modifier: 16 * (Number(!!me.diff) + Number(!!me.gamepassword) + Number(!!me.gametype) + Number(!!me.gamename) + Number(!!me.gameserverip && !me.realm)), + + getScale: function (hkLen) { + if (!!TextHooks.yLocMapScale[hkLen]) { + TextHooks.frameYSizeScale = (-1 * this.yLocMapScale[hkLen]); + TextHooks.frameYLocScale = this.yLocMapScale[hkLen]; + } else { + TextHooks.frameYSizeScale = 0; + TextHooks.frameYLocScale = 0; + } + + TextHooks.settingsModifer = Math.max(0, hkLen - 3); + }, + + check: function () { + if (!this.enabled) { + this.flush(); + + return; + } + + if (!this.frameworkDisplayed) { + !this.getHook("credits", this.hooks) && this.add("credits"); + !!me.gameserverip && !this.getHook("ip", this.hooks) && this.add("ip"); + TextHooks.lastAct = 0; // sorta hacky solution, but works this will cause qolBoard to update after being flushed from a uiflag + TextHooks.frameworkDisplayed = true; + } + + this.displaySettings ? !this.getHook("showSettings", this.statusHooks) && this.add("showSettings") : !this.getHook("hideSettings", this.statusHooks) && this.add("hideSettings"); + this.displayTitle && !this.getHook("title", this.hooks) && this.add("title"); + !this.getHook("ping", this.hooks) ? this.add("ping") : (this.getHook("ping", this.hooks).hook.text = "Ping: " + me.ping); + !this.getHook("time", this.hooks) ? this.add("time") : (this.getHook("time", this.hooks).hook.text = this.timer()); + }, + + update: function (hkLen = 0) { + let updateSettingsDisplay = (this.displaySettings && this.settingsModifer < Math.max(0, hkLen - 3)); + + this.getScale(hkLen); + this.add("dashboard"); + updateSettingsDisplay && this.add("showSettings"); + (this.lastAct !== me.act || this.wasInTown !== me.inTown || !this.getHook("qolBoard", this.qolHooks)) && this.add("qolBoard"); + }, + + hookHandler: function (click, x, y) { + function sortHooks(h1, h2) { + return Math.abs(h1.hook.y - y) - Math.abs(h2.hook.y - y); + } + + if (click === 0) { + TextHooks.statusHooks.sort(sortHooks); + + if (TextHooks.statusHooks[0].name === "hideSettings" || TextHooks.statusHooks[0].name === "showSettings") { + TextHooks.displaySettings = !TextHooks.displaySettings; + + return true; + } + } + + return false; + }, + + add: function (name, hookArr = []) { + let orginalLen = hookArr.length; + + switch (name) { + case "credits": + this.hooks.push({ + name: "credits", + hook: new Text("MM by theBGuy", 0, 600 + Hooks.resfix.y, 4, 0, 0) + }); + + break; + case "title": + this.hooks.push({ + name: "title", + hook: new Text(":: Running Map-Mode, enter .help in chat to see more commands ::", 0, 13, 4, 0, 0) + }); + + break; + case "ping": + this.hooks.push({ + name: "ping", + hook: new Text("Ping: " + me.ping, 785 + Hooks.resfix.x, 56 + this.modifier, 4, 1, 1) + }); + + break; + case "time": + this.hooks.push({ + name: "time", + hook: new Text(this.timer(), 785 + Hooks.resfix.x, 72 + this.modifier, 4, 1, 1) + }); + + break; + case "ip": + this.hooks.push({ + name: "ip", + hook: new Text("IP: " + (me.gameserverip.length > 0 ? me.gameserverip.split(".")[3] : "0"), 785 + Hooks.resfix.x, 88 + this.modifier, 4, 1, 1) + }); + + break; + case "dashboard": + while (this.dashBoard.length) { + this.dashBoard.shift().hook.remove(); + } + + this.dashBoard.push({ + name: "dashboard", + hook: new Box(Hooks.dashBoard.x, Hooks.dashBoard.y + Hooks.resfix.y + this.frameYLocScale, 225, 60 + this.frameYSizeScale, 0x0, 1, 0) + }); + + this.dashBoard.push({ + name: "dashboardframe", + hook: new Frame(Hooks.dashBoard.x, Hooks.dashBoard.y + Hooks.resfix.y + this.frameYLocScale, 225, 60 + this.frameYSizeScale, 0) + }); + + this.getHook("dashboard", this.dashBoard).hook.zorder = 0; + + break; + case "key5": + this.qolHooks.push({ + name: "key5", + hook: new Text("Key 5: " + (me.inTown ? "Heal" : "Make Portal"), Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) + }); + + break; + case "key6": + this.qolHooks.push({ + name: "key6", + hook: new Text("Key 6: " + (me.inTown ? "Open Stash" : "Go To Town"), Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) + }); + + break; + case "nextAct": + me.inTown && me.accessToAct(me.act + 1) && this.qolHooks.push({ + name: "Next Act", + dest: me.act + 1, + type: "actChange", + hook: new Text("Shift > : Next Act", Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) + }); + + break; + case "previousAct": + me.inTown && me.act > 1 && this.qolHooks.push({ + name: "Previous Act", + dest: me.act - 1, + type: "actChange", + hook: new Text("Shift < : Previous Act", Hooks.qolBoard.x + 5 + Hooks.resfix.x, 545 - (this.qolHooks.length * 10) + Hooks.resfix.y, 4) + }); + + break; + case "qolBoard": + TextHooks.qolFrameYSize = 50; + TextHooks.lastAct = me.act; + TextHooks.wasInTown = me.inTown; + + while (this.qolHooks.length) { + this.qolHooks.shift().hook.remove(); + } + + this.qols.forEach(function (hook) { + TextHooks.add(hook, TextHooks.qolHooks) && (TextHooks.qolFrameYSize -= 10); + }); + + this.qolHooks.push({ + name: "qolBoard", + hook: new Box(Hooks.qolBoard.x + Hooks.resfix.x, Hooks.qolBoard.y + this.qolFrameYSize + Hooks.resfix.y, 140, 60 + (-1 * this.qolFrameYSize), 0x0, 1, 0) + }); + + this.qolHooks.push({ + name: "qolFrame", + hook: new Frame(Hooks.qolBoard.x + Hooks.resfix.x, Hooks.qolBoard.y + this.qolFrameYSize + Hooks.resfix.y, 140, 60 + (-1 * this.qolFrameYSize), 0) + }); + + this.qolHooks[this.qolHooks.length - 2].hook.zorder = 0; + + break; + case "pickitStatus": + this.statusHooks.push({ + name: "pickitStatus", + hook: new Text("ÿc4N-Pad - ÿc0: " + (ItemHooks.pickitEnabled ? "ÿc orginalLen; + }, + + getHook: function (name, hookArr = []) { + for (let i = 0; i < hookArr.length; i++) { + if (hookArr[i].name === name) { + return hookArr[i]; + } + } + + return false; + }, + + timer: function () { + return " (" + new Date(getTickCount() - me.gamestarttime).toISOString().slice(11, -5) + ")"; + }, + + flush: function () { + if (!Hooks.enabled) { + while (this.hooks.length) { + this.hooks.shift().hook.remove(); + } + + TextHooks.frameworkDisplayed = false; + } + + while (this.statusHooks.length) { + this.statusHooks.shift().hook.remove(); + } + + while (this.dashBoard.length) { + this.dashBoard.shift().hook.remove(); + } + + while (this.qolHooks.length) { + this.qolHooks.shift().hook.remove(); + } + }, }; diff --git a/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js index 18a745450..e3ce724ad 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js @@ -6,396 +6,392 @@ */ const VectorHooks = { - enabled: true, - currArea: 0, - lastLoc: {x: 0, y: 0}, - names: [], - hooks: [], - - check: function () { - if (!this.enabled) { - this.flush(); - - return; - } - - if (me.area !== this.currArea) { - this.flush(); - - if (!me.area || !me.gameReady) return; - - let nextAreas = []; - - // Specific area override - nextAreas[sdk.areas.TamoeHighland] = sdk.areas.MonasteryGate; - nextAreas[sdk.areas.SpiderForest] = sdk.areas.FlayerJungle; - nextAreas[sdk.areas.GreatMarsh] = sdk.areas.FlayerJungle; - nextAreas[sdk.areas.CrystalizedPassage] = sdk.areas.GlacialTrail; - nextAreas[sdk.areas.GlacialTrail] = sdk.areas.FrozenTundra; - nextAreas[sdk.areas.AncientsWay] = sdk.areas.ArreatSummit; - nextAreas[sdk.areas.ThroneofDestruction] = sdk.areas.WorldstoneChamber; - - try { - let exits = getArea().exits; - this.currArea = me.area; - - if (exits) { - for (let i = 0; i < exits.length; i++) { - if (me.inArea(sdk.areas.CanyonofMagic)) { - this.add(exits[i].x, exits[i].y, exits[i].target === getRoom().correcttomb ? 0x69 : 0x99); - } else if (exits[i].target === nextAreas[me.area] && nextAreas[me.area]) { - this.add(exits[i].x, exits[i].y, 0x1F); - } else if (exits[i].target === ActionHooks.prevAreas.indexOf(me.area) && nextAreas[me.area]) { - this.add(exits[i].x, exits[i].y, 0x99); - } else if (exits[i].target === ActionHooks.prevAreas.indexOf(me.area)) { - this.add(exits[i].x, exits[i].y, 0x1F); - } else if (exits[i].target === ActionHooks.prevAreas[me.area]) { - this.add(exits[i].x, exits[i].y, 0x0A); - } else { - this.add(exits[i].x, exits[i].y, 0x99); - } - - this.addNames(exits[i]); - } - } - - let wp = this.getWP(); - wp && this.add(wp.x, wp.y, 0xA8); - let poi = this.getPOI(); - poi && this.add(poi.x, poi.y, 0x7D); - } catch (e) { - console.error(e); - } - } else if (me.x !== this.lastLoc.x || me.y !== this.lastLoc.y) { - this.update(); - } - }, - - add: function (x, y, color) { - this.hooks.push(new Line(me.x, me.y, x, y, color, true)); - }, - - addNames: function (area) { - this.names.push(new Text(Pather.getAreaName(area.target), area.x, area.y, 0, 6, 2, true)); - }, - - update: function () { - this.lastLoc = {x: me.x, y: me.y}; - - for (let i = 0; i < this.hooks.length; i++) { - this.hooks[i].x = me.x; - this.hooks[i].y = me.y; - } - }, - - flush: function () { - while (this.hooks.length) { - this.hooks.shift().remove(); - } - - while (this.names.length) { - this.names.shift().remove(); - } - - this.currArea = 0; - }, - - getWP: function () { - if (Pather.wpAreas.indexOf(me.area) === -1) return false; - - for (let i = 0; i < sdk.waypoints.Ids.length; i++) { - let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); - - if (preset) { - return { - x: preset.roomx * 5 + preset.x, - y: preset.roomy * 5 + preset.y - }; - } - } - - return false; - }, - - getPOI: function () { - let unit, name; - let poi = {}; - - switch (me.area) { - case sdk.areas.CaveLvl2: - case sdk.areas.HoleLvl2: - case sdk.areas.PitLvl2: - case sdk.areas.Crypt: - case sdk.areas.Mausoleum: - case sdk.areas.StonyTombLvl2: - case sdk.areas.AncientTunnels: - case sdk.areas.GreatMarsh: - case sdk.areas.SpiderCave: - case sdk.areas.SwampyPitLvl3: - case sdk.areas.DisusedFane: - case sdk.areas.ForgottenReliquary: - case sdk.areas.ForgottenTemple: - case sdk.areas.DisusedReliquary: - case sdk.areas.DrifterCavern: - case sdk.areas.IcyCellar: - case sdk.areas.Abaddon: - case sdk.areas.PitofAcheron: - case sdk.areas.InfernalPit: - unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); - poi = {name: "SuperChest", action: {do: "openChest", id: sdk.objects.SmallSparklyChest}}; - - break; - case sdk.areas.GlacialTrail: - case sdk.areas.HallsofAnguish: - case sdk.areas.HallsofPain: - unit = Game.getPresetObject(me.area, sdk.objects.LargeSparklyChest); - poi = {name: "SuperChest", action: {do: "openChest", id: sdk.objects.LargeSparklyChest}}; - - break; - case sdk.areas.ColdPlains: - unit = Game.getPresetStair(me.area, sdk.exits.preset.AreaEntrance); - name = "Cave Level 1"; - - break; - case sdk.areas.StonyField: - unit = Game.getPresetMonster(me.area, sdk.monsters.preset.Rakanishu); - poi = {name: "Cairn Stones", action: {do: "usePortal", id: sdk.areas.Tristram}}; - - break; - case sdk.areas.DarkWood: - unit = Game.getPresetObject(me.area, sdk.quest.chest.InifussTree); - name = "Tree"; - - break; - case sdk.areas.BlackMarsh: - unit = Game.getPresetStair(me.area, sdk.exits.preset.AreaEntrance); - name = "Hole Level 1"; - - break; - case sdk.areas.DenofEvil: - unit = Game.getPresetMonster(me.area, sdk.monsters.preset.Corpsefire); - name = "Corpsefire"; - - break; - case sdk.areas.BurialGrounds: - unit = Game.getPresetMonster(me.area, sdk.monsters.preset.BloodRaven); - name = "Bloodraven"; - - break; - case sdk.areas.TowerCellarLvl5: - unit = Game.getPresetObject(me.area, sdk.objects.SuperChest); - name = "Countess"; - - break; - case sdk.areas.Barracks: - unit = Game.getPresetObject(me.area, sdk.quest.chest.MalusHolder); - name = "Smith"; - - break; - case sdk.areas.Cathedral: - unit = {x: 20047, y: 4898}; - name = "BoneAsh"; - - break; - case sdk.areas.CatacombsLvl4: - unit = {x: 22549, y: 9520}; - name = "Andariel"; - - break; - case sdk.areas.Tristram: - unit = Game.getMonster(sdk.monsters.Griswold) ? Game.getMonster(sdk.monsters.Griswold) : {x: 25163, y: 5170}; - name = "Griswold"; - - break; - case sdk.areas.MooMooFarm: - unit = Game.getMonster(sdk.monsters.TheCowKing) ? Game.getMonster(sdk.monsters.TheCowKing) : Game.getPresetMonster(me.area, sdk.monsters.preset.TheCowKing); - name = "Cow King"; - - break; - case sdk.areas.LutGholein: - unit = Game.getPresetStair(me.area, sdk.exits.preset.A2EnterSewersDoor); - name = "Sewer's Level 1"; - - break; - case sdk.areas.A2SewersLvl3: - unit = Game.getPresetObject(me.area, sdk.quest.chest.HoradricScrollChest); - name = "Radament"; - - break; - case sdk.areas.PalaceCellarLvl3: - unit = {x: 10073, y: 8670}; - poi = {name: "Arcane Sanctuary", action: {do: "usePortal"}}; - - break; - case sdk.areas.HallsoftheDeadLvl3: - unit = Game.getPresetObject(me.area, sdk.quest.chest.HoradricCubeChest); - poi = {name: "Cube", action: {do: "openChest", id: sdk.quest.chest.HoradricCubeChest}}; - - break; - case sdk.areas.ClawViperTempleLvl2: - unit = Game.getPresetObject(me.area, sdk.quest.chest.ViperAmuletChest); - poi = {name: "Amulet", action: {do: "openChest", id: sdk.quest.chest.ViperAmuletChest}}; - - break; - case sdk.areas.MaggotLairLvl3: - unit = Game.getPresetObject(me.area, sdk.quest.chest.ShaftoftheHoradricStaffChest); - poi = {name: "Staff", action: {do: "openChest", id: sdk.quest.chest.ShaftoftheHoradricStaffChest}}; - - break; - case sdk.areas.ArcaneSanctuary: - unit = Game.getPresetObject(me.area, sdk.quest.chest.Journal); - name = "Summoner"; - - break; - case sdk.areas.TalRashasTomb1: - case sdk.areas.TalRashasTomb2: - case sdk.areas.TalRashasTomb3: - case sdk.areas.TalRashasTomb4: - case sdk.areas.TalRashasTomb5: - case sdk.areas.TalRashasTomb6: - case sdk.areas.TalRashasTomb7: - unit = Game.getPresetObject(me.area, sdk.quest.chest.HoradricStaffHolder); - name = "Orifice"; - - if (!unit) { - unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); - name = "SuperChest"; - } - - break; - case sdk.areas.DurielsLair: - unit = {x: 22577, y: 15609}; - name = "Tyrael"; - - break; - case sdk.areas.FlayerJungle: - unit = Game.getPresetObject(me.area, sdk.quest.chest.GidbinnAltar); - name = "Gidbinn"; - - break; - case sdk.areas.KurastBazaar: - unit = Game.getPresetStair(me.area, sdk.exits.preset.A3EnterSewers); - name = "Sewer's Level 1"; - - break; - case sdk.areas.SpiderCavern: - unit = Game.getPresetObject(me.area, sdk.quest.chest.KhalimsEyeChest); - poi = {name: "Eye", action: {do: "openChest", id: sdk.quest.chest.KhalimsEyeChest}}; - - break; - case sdk.areas.FlayerDungeonLvl3: - unit = Game.getPresetObject(me.area, sdk.quest.chest.KhalimsBrainChest); - poi = {name: "Brain", action: {do: "openChest", id: sdk.quest.chest.KhalimsBrainChest}}; - - break; - case sdk.areas.A3SewersLvl2: - unit = Game.getPresetObject(me.area, sdk.quest.chest.KhalimsHeartChest); - poi = {name: "Heart", action: {do: "openChest", id: sdk.quest.chest.KhalimsHeartChest}}; - - break; - case sdk.areas.RuinedTemple: - unit = Game.getPresetObject(me.area, sdk.quest.chest.LamEsensTomeHolder); - poi = {name: "Lam Esen", action: {do: "openChest", id: sdk.quest.chest.LamEsensTomeHolder}}; - - break; - case sdk.areas.Travincal: - unit = Game.getPresetObject(me.area, sdk.objects.CompellingOrb); - name = "Orb"; - - break; - case sdk.areas.DuranceofHateLvl3: - unit = {x: 17588, y: 8069}; - name = "Mephisto"; - - break; - case sdk.areas.PlainsofDespair: - unit = Game.getPresetMonster(me.area, sdk.monsters.Izual); - name = "Izual"; - - break; - case sdk.areas.RiverofFlame: - unit = Game.getPresetObject(me.area, sdk.quest.chest.HellForge); - name = "Hephasto"; - - break; - case sdk.areas.ChaosSanctuary: - unit = Game.getPresetObject(me.area, sdk.objects.DiabloStar); - name = "Star"; - - break; - case sdk.areas.Harrogath: - unit = {x: 5112, y: 5120}; - poi = {name: "Anya Portal", action: {do: "usePortal", id: sdk.areas.NihlathaksTemple}}; - - break; - case sdk.areas.BloodyFoothills: - unit = {x: 3899, y: 5113}; - name = "Shenk"; - - break; - case sdk.areas.FrigidHighlands: - case sdk.areas.ArreatPlateau: - case sdk.areas.FrozenTundra: - unit = Game.getPresetObject(me.area, sdk.objects.RedPortal); - poi = {name: "Hell Entrance", action: {do: "usePortal"}}; - - break; - case sdk.areas.FrozenRiver: - unit = Game.getPresetObject(me.area, sdk.objects.FrozenAnyasPlatform); - name = "Frozen Anya"; - - break; - case sdk.areas.NihlathaksTemple: - unit = {x: 10058, y: 13234}; - name = "Pindle"; - - break; - case sdk.areas.HallsofVaught: - unit = Game.getPresetObject(me.area, sdk.objects.NihlathaksPlatform); - name = "Nihlathak"; - - break; - case sdk.areas.ThroneofDestruction: - unit = {x: 15118, y: 5002}; - name = "Throne Room"; - - break; - case sdk.areas.WorldstoneChamber: - unit = Game.getMonster(sdk.monsters.Baal) ? Game.getMonster(sdk.monsters.Baal) : {x: 15134, y: 5923}; - name = "Baal"; - - break; - case sdk.areas.MatronsDen: - unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); - name = "Lilith"; - - break; - case sdk.areas.ForgottenSands: - unit = Game.getMonster(sdk.monsters.UberDuriel); - name = "Duriel"; - - break; - case sdk.areas.FurnaceofPain: - unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); - name = "Izual"; - - break; - } - - if (unit) { - name && !poi.name && (poi.name = name); - - if (unit instanceof PresetUnit) { - poi.x = unit.roomx * 5 + unit.x; - poi.y = unit.roomy * 5 + unit.y; - } else { - poi.x = unit.x; - poi.y = unit.y; - } - - return poi; - } - - return false; - } + enabled: true, + currArea: 0, + lastLoc: { x: 0, y: 0 }, + names: [], + hooks: [], + nextAreas: (function () { + let nextAreas = []; + + // Specific area override + nextAreas[sdk.areas.TamoeHighland] = sdk.areas.MonasteryGate; + nextAreas[sdk.areas.SpiderForest] = sdk.areas.FlayerJungle; + nextAreas[sdk.areas.GreatMarsh] = sdk.areas.FlayerJungle; + nextAreas[sdk.areas.CrystalizedPassage] = sdk.areas.GlacialTrail; + nextAreas[sdk.areas.GlacialTrail] = sdk.areas.FrozenTundra; + nextAreas[sdk.areas.AncientsWay] = sdk.areas.ArreatSummit; + nextAreas[sdk.areas.ThroneofDestruction] = sdk.areas.WorldstoneChamber; + + return nextAreas; + })(), + + check: function () { + if (!this.enabled) { + this.flush(); + + return; + } + + if (me.area !== this.currArea) { + this.flush(); + + if (!me.area || !me.gameReady) return; + + try { + let exits = getArea().exits; + VectorHooks.currArea = me.area; + + if (exits) { + for (let i = 0; i < exits.length; i++) { + if (me.inArea(sdk.areas.CanyonofMagic)) { + this.add(exits[i].x, exits[i].y, exits[i].target === getRoom().correcttomb ? 0x69 : 0x99); + } else if (exits[i].target === this.nextAreas[me.area] && this.nextAreas[me.area]) { + this.add(exits[i].x, exits[i].y, 0x1F); + } else if (exits[i].target === ActionHooks.prevAreas.indexOf(me.area) && this.nextAreas[me.area]) { + this.add(exits[i].x, exits[i].y, 0x99); + } else if (exits[i].target === ActionHooks.prevAreas.indexOf(me.area)) { + this.add(exits[i].x, exits[i].y, 0x1F); + } else if (exits[i].target === ActionHooks.prevAreas[me.area]) { + this.add(exits[i].x, exits[i].y, 0x0A); + } else { + this.add(exits[i].x, exits[i].y, 0x99); + } + + this.addNames(exits[i]); + } + } + + let wp = this.getWP(); + wp && this.add(wp.x, wp.y, 0xA8); + let poi = this.getPOI(); + poi && this.add(poi.x, poi.y, 0x7D); + } catch (e) { + console.error(e); + } + } else if (me.x !== this.lastLoc.x || me.y !== this.lastLoc.y) { + this.update(); + } + }, + + add: function (x, y, color) { + this.hooks.push(new Line(me.x, me.y, x, y, color, true)); + }, + + addNames: function (area) { + this.names.push(new Text(getAreaName(area.target), area.x, area.y, 0, 6, 2, true)); + }, + + update: function () { + VectorHooks.lastLoc = { x: me.x, y: me.y }; + + for (let i = 0; i < this.hooks.length; i++) { + this.hooks[i].x = me.x; + this.hooks[i].y = me.y; + } + }, + + flush: function () { + while (this.hooks.length) { + this.hooks.shift().remove(); + } + + while (this.names.length) { + this.names.shift().remove(); + } + + VectorHooks.currArea = 0; + }, + + getWP: function () { + if (Pather.wpAreas.indexOf(me.area) === -1) return false; + + for (let i = 0; i < sdk.waypoints.Ids.length; i++) { + let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); + + if (preset) { + return preset.realCoords(); + } + } + + return false; + }, + + getPOI: function () { + let unit, name; + let poi = {}; + + switch (me.area) { + case sdk.areas.CaveLvl2: + case sdk.areas.HoleLvl2: + case sdk.areas.PitLvl2: + case sdk.areas.Crypt: + case sdk.areas.Mausoleum: + case sdk.areas.StonyTombLvl2: + case sdk.areas.AncientTunnels: + case sdk.areas.GreatMarsh: + case sdk.areas.SpiderCave: + case sdk.areas.SwampyPitLvl3: + case sdk.areas.DisusedFane: + case sdk.areas.ForgottenReliquary: + case sdk.areas.ForgottenTemple: + case sdk.areas.DisusedReliquary: + case sdk.areas.DrifterCavern: + case sdk.areas.IcyCellar: + case sdk.areas.Abaddon: + case sdk.areas.PitofAcheron: + case sdk.areas.InfernalPit: + unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); + poi = { name: "SuperChest", action: { do: "openChest", id: sdk.objects.SmallSparklyChest } }; + + break; + case sdk.areas.GlacialTrail: + case sdk.areas.HallsofAnguish: + case sdk.areas.HallsofPain: + unit = Game.getPresetObject(me.area, sdk.objects.LargeSparklyChest); + poi = { name: "SuperChest", action: { do: "openChest", id: sdk.objects.LargeSparklyChest } }; + + break; + case sdk.areas.ColdPlains: + unit = Game.getPresetStair(me.area, sdk.exits.preset.AreaEntrance); + name = "Cave Level 1"; + + break; + case sdk.areas.StonyField: + unit = Game.getPresetMonster(me.area, sdk.monsters.preset.Rakanishu); + poi = { name: "Cairn Stones", action: { do: "usePortal", id: sdk.areas.Tristram } }; + + break; + case sdk.areas.DarkWood: + unit = Game.getPresetObject(me.area, sdk.quest.chest.InifussTree); + name = "Tree"; + + break; + case sdk.areas.BlackMarsh: + unit = Game.getPresetStair(me.area, sdk.exits.preset.AreaEntrance); + name = "Hole Level 1"; + + break; + case sdk.areas.DenofEvil: + unit = Game.getPresetMonster(me.area, sdk.monsters.preset.Corpsefire); + name = "Corpsefire"; + + break; + case sdk.areas.BurialGrounds: + unit = Game.getPresetMonster(me.area, sdk.monsters.preset.BloodRaven); + name = "Bloodraven"; + + break; + case sdk.areas.TowerCellarLvl5: + unit = Game.getPresetObject(me.area, sdk.objects.SuperChest); + name = "Countess"; + + break; + case sdk.areas.Barracks: + unit = Game.getPresetObject(me.area, sdk.quest.chest.MalusHolder); + name = "Smith"; + + break; + case sdk.areas.Cathedral: + unit = { x: 20047, y: 4898 }; + name = "BoneAsh"; + + break; + case sdk.areas.CatacombsLvl4: + unit = { x: 22549, y: 9520 }; + name = "Andariel"; + + break; + case sdk.areas.Tristram: + unit = Game.getMonster(sdk.monsters.Griswold) ? Game.getMonster(sdk.monsters.Griswold) : { x: 25163, y: 5170 }; + name = "Griswold"; + + break; + case sdk.areas.MooMooFarm: + unit = Game.getMonster(sdk.monsters.TheCowKing) + ? Game.getMonster(sdk.monsters.TheCowKing) + : Game.getPresetMonster(me.area, sdk.monsters.preset.TheCowKing); + name = "Cow King"; + + break; + case sdk.areas.LutGholein: + unit = Game.getPresetStair(me.area, sdk.exits.preset.A2EnterSewersDoor); + name = "Sewer's Level 1"; + + break; + case sdk.areas.A2SewersLvl3: + unit = Game.getPresetObject(me.area, sdk.quest.chest.HoradricScrollChest); + name = "Radament"; + + break; + case sdk.areas.PalaceCellarLvl3: + unit = { x: 10073, y: 8670 }; + poi = { name: "Arcane Sanctuary", action: { do: "usePortal" } }; + + break; + case sdk.areas.HallsoftheDeadLvl3: + unit = Game.getPresetObject(me.area, sdk.quest.chest.HoradricCubeChest); + poi = { name: "Cube", action: { do: "openChest", id: sdk.quest.chest.HoradricCubeChest } }; + + break; + case sdk.areas.ClawViperTempleLvl2: + unit = Game.getPresetObject(me.area, sdk.quest.chest.ViperAmuletChest); + poi = { name: "Amulet", action: { do: "openChest", id: sdk.quest.chest.ViperAmuletChest } }; + + break; + case sdk.areas.MaggotLairLvl3: + unit = Game.getPresetObject(me.area, sdk.quest.chest.ShaftoftheHoradricStaffChest); + poi = { name: "Staff", action: { do: "openChest", id: sdk.quest.chest.ShaftoftheHoradricStaffChest } }; + + break; + case sdk.areas.ArcaneSanctuary: + unit = Game.getPresetObject(me.area, sdk.quest.chest.Journal); + name = "Summoner"; + + break; + case sdk.areas.TalRashasTomb1: + case sdk.areas.TalRashasTomb2: + case sdk.areas.TalRashasTomb3: + case sdk.areas.TalRashasTomb4: + case sdk.areas.TalRashasTomb5: + case sdk.areas.TalRashasTomb6: + case sdk.areas.TalRashasTomb7: + unit = Game.getPresetObject(me.area, sdk.quest.chest.HoradricStaffHolder); + name = "Orifice"; + + if (!unit) { + unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); + name = "SuperChest"; + } + + break; + case sdk.areas.DurielsLair: + unit = { x: 22577, y: 15609 }; + name = "Tyrael"; + + break; + case sdk.areas.FlayerJungle: + unit = Game.getPresetObject(me.area, sdk.quest.chest.GidbinnAltar); + name = "Gidbinn"; + + break; + case sdk.areas.KurastBazaar: + unit = Game.getPresetStair(me.area, sdk.exits.preset.A3EnterSewers); + name = "Sewer's Level 1"; + + break; + case sdk.areas.SpiderCavern: + unit = Game.getPresetObject(me.area, sdk.quest.chest.KhalimsEyeChest); + poi = { name: "Eye", action: { do: "openChest", id: sdk.quest.chest.KhalimsEyeChest } }; + + break; + case sdk.areas.FlayerDungeonLvl3: + unit = Game.getPresetObject(me.area, sdk.quest.chest.KhalimsBrainChest); + poi = { name: "Brain", action: { do: "openChest", id: sdk.quest.chest.KhalimsBrainChest } }; + + break; + case sdk.areas.A3SewersLvl2: + unit = Game.getPresetObject(me.area, sdk.quest.chest.KhalimsHeartChest); + poi = { name: "Heart", action: { do: "openChest", id: sdk.quest.chest.KhalimsHeartChest } }; + + break; + case sdk.areas.RuinedTemple: + unit = Game.getPresetObject(me.area, sdk.quest.chest.LamEsensTomeHolder); + poi = { name: "Lam Esen", action: { do: "openChest", id: sdk.quest.chest.LamEsensTomeHolder } }; + + break; + case sdk.areas.Travincal: + unit = Game.getPresetObject(me.area, sdk.objects.CompellingOrb); + name = "Orb"; + + break; + case sdk.areas.DuranceofHateLvl3: + unit = { x: 17588, y: 8069 }; + name = "Mephisto"; + + break; + case sdk.areas.PlainsofDespair: + unit = Game.getPresetMonster(me.area, sdk.monsters.Izual); + name = "Izual"; + + break; + case sdk.areas.RiverofFlame: + unit = Game.getPresetObject(me.area, sdk.quest.chest.HellForge); + name = "Hephasto"; + + break; + case sdk.areas.ChaosSanctuary: + unit = Game.getPresetObject(me.area, sdk.objects.DiabloStar); + name = "Star"; + + break; + case sdk.areas.Harrogath: + unit = { x: 5112, y: 5120 }; + poi = { name: "Anya Portal", action: { do: "usePortal", id: sdk.areas.NihlathaksTemple } }; + + break; + case sdk.areas.BloodyFoothills: + unit = { x: 3899, y: 5113 }; + name = "Shenk"; + + break; + case sdk.areas.FrigidHighlands: + case sdk.areas.ArreatPlateau: + case sdk.areas.FrozenTundra: + unit = Game.getPresetObject(me.area, sdk.objects.RedPortal); + poi = { name: "Hell Entrance", action: { do: "usePortal" } }; + + break; + case sdk.areas.FrozenRiver: + unit = Game.getPresetObject(me.area, sdk.objects.FrozenAnyasPlatform); + name = "Frozen Anya"; + + break; + case sdk.areas.NihlathaksTemple: + unit = { x: 10058, y: 13234 }; + name = "Pindle"; + + break; + case sdk.areas.HallsofVaught: + unit = Game.getPresetObject(me.area, sdk.objects.NihlathaksPlatform); + name = "Nihlathak"; + + break; + case sdk.areas.ThroneofDestruction: + unit = { x: 15118, y: 5002 }; + name = "Throne Room"; + + break; + case sdk.areas.WorldstoneChamber: + unit = Game.getMonster(sdk.monsters.Baal) ? Game.getMonster(sdk.monsters.Baal) : { x: 15134, y: 5923 }; + name = "Baal"; + + break; + case sdk.areas.MatronsDen: + unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); + name = "Lilith"; + + break; + case sdk.areas.ForgottenSands: + unit = Game.getMonster(sdk.monsters.UberDuriel); + name = "Duriel"; + + break; + case sdk.areas.FurnaceofPain: + unit = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); + name = "Izual"; + + break; + } + + if (unit) { + name && !poi.name && (poi.name = name); + (unit instanceof PresetUnit) && (unit = unit.realCoords()); + [poi.x, poi.y] = [unit.x, unit.y]; + + return poi; + } + + return false; + } }; diff --git a/d2bs/kolbot/libs/manualplay/libs/AttackOverrides.js b/d2bs/kolbot/libs/manualplay/libs/AttackOverrides.js index d003b3507..d6677ebab 100644 --- a/d2bs/kolbot/libs/manualplay/libs/AttackOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/AttackOverrides.js @@ -5,29 +5,29 @@ * */ -includeIfNotIncluded("common/Attack.js"); +includeIfNotIncluded("core/Attack.js"); Attack.init = function (notify = false) { - if (Config.Wereform) { - include("common/Attacks/wereform.js"); - } else if (Config.CustomClassAttack && FileTools.exists("libs/common/Attacks/" + Config.CustomClassAttack + ".js")) { - print("Loading custom attack file"); - include("common/Attacks/" + Config.CustomClassAttack + ".js"); - } else { - include("common/Attacks/" + sdk.player.class.nameOf(me.classid) + ".js"); - } + if (Config.Wereform) { + include("core/Attacks/wereform.js"); + } else if (Config.CustomClassAttack && FileTools.exists("libs/core/Attacks/" + Config.CustomClassAttack + ".js")) { + print("Loading custom attack file"); + include("core/Attacks/" + Config.CustomClassAttack + ".js"); + } else { + include("core/Attacks/" + sdk.player.class.nameOf(me.classid) + ".js"); + } - if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { - notify && print("ÿc1Bad attack config. Don't expect your bot to attack."); - } + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { + notify && print("ÿc1Bad attack config. Don't expect your bot to attack."); + } - this.getPrimarySlot(); - Skill.init(); + this.getPrimarySlot(); + Skill.init(); - if (me.expansion) { - Precast.checkCTA(); - this.checkInfinity(); - this.checkAuradin(); - } + if (me.expansion) { + Precast.checkCTA(); + this.checkInfinity(); + this.checkAuradin(); + } }; diff --git a/d2bs/kolbot/libs/manualplay/libs/ConfigOverrides.js b/d2bs/kolbot/libs/manualplay/libs/ConfigOverrides.js index 9f776ec7d..c8a7ed3fc 100644 --- a/d2bs/kolbot/libs/manualplay/libs/ConfigOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/ConfigOverrides.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * @filename ConfigOverrides.js * @author theBGuy @@ -5,109 +6,104 @@ * */ -includeIfNotIncluded("common/Config.js"); - -let original = Config.init; - -Config.init = function (notify) { - let configFilename = ""; - let classes = ["Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin"]; - - for (let i = 0; i < 5; i += 1) { - switch (i) { - case 0: // Custom config - includeIfNotIncluded("config/_customconfig.js"); - - for (let n in CustomConfig) { - if (CustomConfig.hasOwnProperty(n)) { - if (CustomConfig[n].indexOf(me.profile) > -1) { - if (notify) { - print("ÿc2Loading custom config: ÿc9" + n + ".js"); - } - - configFilename = n + ".js"; - - break; - } - } - } - - break; - case 1:// Class.Profile.js - configFilename = classes[me.classid] + "." + me.profile + ".js"; - - break; - case 2: // Realm.Class.Charname.js - configFilename = me.realm + "." + classes[me.classid] + "." + me.charname + ".js"; - - break; - case 3: // Class.Charname.js - configFilename = classes[me.classid] + "." + me.charname + ".js"; - - break; - case 4: // Profile.js - configFilename = me.profile + ".js"; - - break; - } - - if (configFilename && FileTools.exists("libs/manualplay/config/" + configFilename)) { - break; - } - } - - if (FileTools.exists("libs/manualplay/config/" + configFilename)) { - try { - if (!include("manualplay/config/" + configFilename)) { - throw new Error(); - } - } catch (e1) { - throw new Error("Failed to load character config."); - } - } else { - if (notify) { - print("ÿc1" + classes[me.classid] + "." + me.charname + ".js not found!"); // Use the primary format - print("ÿc1Loading default config."); - } - - try { - // Try to find default config - if (!FileTools.exists("libs/manualplay/config/" + classes[me.classid] + ".js")) { - D2Bot.printToConsole("Not going well? Read the guides: https://github.com/blizzhackers/documentation"); - throw new Error("ÿc1Default config not found. \nÿc9 Try reading the kolbot guides."); - } - - if (!include("manualplay/config/" + classes[me.classid] + ".js")) { - throw new Error("ÿc1Failed to load default config."); - } - } catch (e) { - print(e); - original(); - } - } - - try { - LoadConfig.call(); - } catch (e2) { - if (notify) { - print("ÿc8Error in " + e2.fileName.substring(e2.fileName.lastIndexOf("\\") + 1, e2.fileName.length) + "(line " + e2.lineNumber + "): " + e2.message); - - throw new Error("Config.init: Error in character config."); - } - } - - if (Config.Silence && !Config.LocalChat.Enabled) { - // Override the say function with print, so it just gets printed to console - global._say = global.say; - global.say = (what) => print("Tryed to say: " + what); - } - - try { - if (Config.AutoBuild.Enabled === true && !isIncluded("common/AutoBuild.js") && include("common/AutoBuild.js")) { - AutoBuild.initialize(); - } - } catch (e3) { - print("ÿc8Error in libs/common/AutoBuild.js (AutoBuild system is not active!)"); - print(e3.toSource()); - } -}; +includeIfNotIncluded("core/Config.js"); + +(function (Config, original) { + Config.init = function (notify) { + const className = sdk.player.class.nameOf(me.classid); + const formats = ((className, profile, charname, realm) => ({ + // Class.Profile.js + 1: className + "." + profile + ".js", + // Realm.Class.Charname.js + 2: realm + "." + className + "." + charname + ".js", + // Class.Charname.js + 3: className + "." + charname + ".js", + // Profile.js + 4: profile + ".js", + // Class.js + 5: className + ".js", + }))(className, me.profile, me.charname, me.realm); + let configFilename = ""; + + for (let i = 0; i < 5; i++) { + switch (i) { + case 0: // Custom config + includeIfNotIncluded("config/_customconfig.js"); + + for (let n in CustomConfig) { + if (CustomConfig.hasOwnProperty(n) && CustomConfig[n].includes(me.profile)) { + notify && console.log("ÿc2Loading custom config: ÿc9" + n + ".js"); + configFilename = n + ".js"; + + break; + } + } + + break; + default: + configFilename = formats[i]; + + break; + } + + if (configFilename && FileTools.exists("libs/manualplay/config/" + configFilename)) { + break; + } + } + + if (FileTools.exists("libs/manualplay/config/" + configFilename)) { + try { + if (!include("manualplay/config/" + configFilename)) { + throw new Error(); + } + } catch (e1) { + throw new Error("Failed to load character config."); + } + } else { + if (notify) { + console.log("ÿc1" + className + "." + me.charname + ".js not found!"); // Use the primary format + console.log("ÿc1Loading default config."); + } + + try { + // Try to find default config + if (!FileTools.exists("libs/manualplay/config/" + className + ".js")) { + D2Bot.printToConsole("Not going well? Read the guides: https://github.com/blizzhackers/documentation"); + throw new Error("ÿc1Default config not found. \nÿc9 Try reading the kolbot guides."); + } + + if (!include("manualplay/config/" + className + ".js")) { + throw new Error("ÿc1Failed to load default config."); + } + } catch (e) { + console.log(e); + original.apply(this, arguments); + } + } + + try { + LoadConfig.call(); + } catch (e2) { + if (notify) { + console.error(e2); + + throw new Error("Config.init: Error in character config."); + } + } + + if (Config.Silence && !Config.LocalChat.Enabled) { + // Override the say function with print, so it just gets printed to console + global._say = global.say; + global.say = (what) => console.log("Tryed to say: " + what); + } + + try { + if (Config.AutoBuild.Enabled === true && includeIfNotIncluded("core/Auto/AutoBuild.js")) { + AutoBuild.initialize(); + } + } catch (e3) { + console.log("ÿc8Error in libs/core/AutoBuild.js (AutoBuild system is not active!)"); + console.error(e3); + } + }; +})(Config, Config.init); diff --git a/d2bs/kolbot/libs/manualplay/libs/MiscOverrides.js b/d2bs/kolbot/libs/manualplay/libs/MiscOverrides.js index 631832360..5b8a53bb8 100644 --- a/d2bs/kolbot/libs/manualplay/libs/MiscOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/MiscOverrides.js @@ -5,159 +5,161 @@ * */ -includeIfNotIncluded("common/Misc.js"); +includeIfNotIncluded("core/Misc.js"); Misc.openRedPortal = function (portalID) { - if (!me.getItem(sdk.quest.item.Cube)) return; - - function getTome () { - let npc, tome, scroll; - let tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); - - try { - if (tpTome.length < 2) { - npc = Town.initNPC("Shop", "buyTpTome"); - - if (!getInteractedNPC()) { - throw new Error("Failed to find npc"); - } - - tome = npc.getItem(sdk.items.TomeofTownPortal); - - if (!!tome && tome.getItemCost(sdk.items.cost.ToBuy) < me.gold && tome.buy()) { - delay(500); - tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); - tpTome.forEach(function (book) { - while (book.getStat(sdk.stats.Quantity) < 20) { - scroll = npc.getItem(sdk.items.ScrollofTownPortal); - - if (!!scroll && scroll.getItemCost(sdk.items.cost.ToBuy) < me.gold) { - scroll.buy(); - } else { - break; - } - - delay(20); - } - }); - } - } - } finally { - me.cancel(); - } - } - - try { - let materials, validMats = []; - - switch (portalID) { - case sdk.areas.MooMooFarm: - if (me.getQuest(sdk.quest.id.TheSearchForCain, 10)) { - throw new Error("Unable to open cow portal because cow king has been killed"); - } - - materials = [sdk.items.quest.WirtsLeg, sdk.items.TomeofTownPortal]; - - break; - case sdk.areas.UberTristram: - materials = [sdk.quest.item.DiablosHorn, sdk.quest.item.BaalsEye, sdk.quest.item.MephistosBrain]; - - break; - default: - materials = [sdk.quest.item.KeyofTerror, sdk.quest.item.KeyofHate, sdk.quest.item.KeyofDestruction]; - - break; - } - - materials.forEach(function (mat) { - mat === sdk.items.TomeofTownPortal && getTome(); - let item = me.getItem(mat); - !!item && validMats.push(item); - }); - - if (validMats.length !== materials.length) throw new Error("Missing materials to open portal"); - - portalID === sdk.areas.MooMooFarm ? !me.inArea(sdk.areas.RogueEncampment) && Town.goToTown(1) : !me.inArea(sdk.areas.Harrogath) && Town.goToTown(5); - - Town.move("stash"); - - if (portalID && Pather.getPortal(portalID)) throw new Error("Portal is already open"); - - Cubing.openCube(); - - if (!Cubing.emptyCube()) throw new Error("Failed to empty cube"); - - validMats.forEach(function (mat) { - return Storage.Cube.MoveTo(mat); - }); - - Cubing.openCube() && transmute(); - } catch (e) { - console.error(e); - } finally { - me.cancel(); - } + if (!me.getItem(sdk.quest.item.Cube)) return; + + function getTome () { + let npc, tome, scroll; + let tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); + + try { + if (tpTome.length < 2) { + npc = Town.initNPC("Shop", "buyTpTome"); + + if (!getInteractedNPC()) { + throw new Error("Failed to find npc"); + } + + tome = npc.getItem(sdk.items.TomeofTownPortal); + + if (!!tome && tome.getItemCost(sdk.items.cost.ToBuy) < me.gold && tome.buy()) { + delay(500); + tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); + tpTome.forEach(function (book) { + while (book.getStat(sdk.stats.Quantity) < 20) { + scroll = npc.getItem(sdk.items.ScrollofTownPortal); + + if (!!scroll && scroll.getItemCost(sdk.items.cost.ToBuy) < me.gold) { + scroll.buy(); + } else { + break; + } + + delay(20); + } + }); + } + } + } finally { + me.cancel(); + } + } + + try { + let materials, validMats = []; + + switch (portalID) { + case sdk.areas.MooMooFarm: + if (me.getQuest(sdk.quest.id.TheSearchForCain, 10)) { + throw new Error("Unable to open cow portal because cow king has been killed"); + } + + materials = [sdk.items.quest.WirtsLeg, sdk.items.TomeofTownPortal]; + + break; + case sdk.areas.UberTristram: + materials = [sdk.quest.item.DiablosHorn, sdk.quest.item.BaalsEye, sdk.quest.item.MephistosBrain]; + + break; + default: + materials = [sdk.quest.item.KeyofTerror, sdk.quest.item.KeyofHate, sdk.quest.item.KeyofDestruction]; + + break; + } + + materials.forEach(function (mat) { + mat === sdk.items.TomeofTownPortal && getTome(); + let item = me.getItem(mat); + !!item && validMats.push(item); + }); + + if (validMats.length !== materials.length) throw new Error("Missing materials to open portal"); + + portalID === sdk.areas.MooMooFarm + ? !me.inArea(sdk.areas.RogueEncampment) && Town.goToTown(1) + : !me.inArea(sdk.areas.Harrogath) && Town.goToTown(5); + + Town.move("stash"); + + if (portalID && Pather.getPortal(portalID)) throw new Error("Portal is already open"); + + Cubing.openCube(); + + if (!Cubing.emptyCube()) throw new Error("Failed to empty cube"); + + validMats.forEach(function (mat) { + return Storage.Cube.MoveTo(mat); + }); + + Cubing.openCube() && transmute(); + } catch (e) { + console.error(e); + } finally { + me.cancel(); + } }; Misc.talkToTyrael = function () { - if (!me.inArea(sdk.areas.DurielsLair)) return false; - - Pather.walkTo(22621, 15711); - Pather.moveTo(22602, 15705); - Pather.moveTo(22579, 15704); - Pather.moveTo(22575, 15675); - Pather.moveTo(22579, 15655); - Pather.walkTo(22578, 15642); // walk trough door - Pather.moveTo(22578, 15618); - Pather.moveTo(22576, 15591); // tyreal - - let tyrael = Game.getNPC(NPC.Tyrael); - - if (tyrael) { - for (let i = 0; i < 3; i++) { - if (getDistance(me, tyrael) > 3) { - Pather.moveToUnit(tyrael); - } - - tyrael.interact(); - delay(1000 + me.ping); - me.cancel(); - - if (Pather.getPortal(null)) { - me.cancel(); - - break; - } - } - } - - return Pather.usePortal(null); + if (!me.inArea(sdk.areas.DurielsLair)) return false; + + Pather.walkTo(22621, 15711); + Pather.moveTo(22602, 15705); + Pather.moveTo(22579, 15704); + Pather.moveTo(22575, 15675); + Pather.moveTo(22579, 15655); + Pather.walkTo(22578, 15642); // walk trough door + Pather.moveTo(22578, 15618); + Pather.moveTo(22576, 15591); // tyreal + + let tyrael = Game.getNPC(NPC.Tyrael); + + if (tyrael) { + for (let i = 0; i < 3; i++) { + if (getDistance(me, tyrael) > 3) { + Pather.moveToUnit(tyrael); + } + + tyrael.interact(); + delay(1000 + me.ping); + me.cancel(); + + if (Pather.getPortal(null)) { + me.cancel(); + + break; + } + } + } + + return Pather.usePortal(null); }; Misc.dropItems = function (fromLoc) { - try { - if (!fromLoc) throw new Error("No location given"); - if (fromLoc === sdk.storage.Stash && !Town.openStash()) throw new Error("Failed to open stash"); - - let items = me.findItems(-1, sdk.items.mode.inStorage, fromLoc); - - if (items) { - while (items.length > 0) { - let item = items.shift(); - - if (item.classid === sdk.quest.item.Cube - || (item.isInInventory && [sdk.items.type.SmallCharm, sdk.items.type.LargeCharm, sdk.items.type.GrandCharm].includes(item.itemType) && Storage.Inventory.IsLocked(item, Config.Inventory))) { - continue; - } else { - item.drop(); - } - } - } else { - throw new Error("Couldn't find any items"); - } - } catch (e) { - console.error(e); - } finally { - me.cancel(); - } + try { + if (!fromLoc) throw new Error("No location given"); + if (fromLoc === sdk.storage.Stash && !Town.openStash()) throw new Error("Failed to open stash"); + + let items = me.findItems(-1, sdk.items.mode.inStorage, fromLoc); + + if (items) { + while (items.length > 0) { + let item = items.shift(); + + if (item.classid === sdk.quest.item.Cube + || (item.isEquippedCharm && Storage.Inventory.IsLocked(item, Config.Inventory))) { + continue; + } else { + item.drop(); + } + } + } else { + throw new Error("Couldn't find any items"); + } + } catch (e) { + console.error(e); + } finally { + me.cancel(); + } }; diff --git a/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js b/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js index 179b72ff7..9f6f52c83 100644 --- a/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * @filename PatherOverides.js * @author theBGuy @@ -5,402 +6,400 @@ * */ -includeIfNotIncluded("common/Pather.js"); +includeIfNotIncluded("core/Pather.js"); Pather.stop = false; Pather.stopEvent = function (key) { - key === sdk.keys.Numpad9 && !me.idle && (Pather.stop = true); + key === sdk.keys.Numpad9 && !me.idle && (Pather.stop = true); }; Pather.changeAct = function (act) { - let npc, npcUnit, loc; - let wp, useWp = false; - - switch (act) { - case 1: - npc = "Warriv"; - loc = sdk.areas.RogueEncampment; - - break; - case 2: - loc = sdk.areas.LutGholein; - npc = me.act === 1 ? "Warriv" : "Meshif"; - - break; - case 3: - npc = "Meshif"; - loc = sdk.areas.KurastDocktown; - - break; - case 5: - npc = "Tyrael"; - loc = sdk.areas.Harrogath; - - break; - } - - !me.inTown && Town.goToTown(); - - if (npc) { - npcUnit = Game.getNPC(NPC[npc]); - wp = Game.getObject("waypoint"); - - if (Pather.accessToAct(act) - && ((wp && !npcUnit) - || (wp && npcUnit && getDistance(me, wp) < getDistance(me, npcUnit)) - || (Town.getDistance("waypoint") < Town.getDistance(NPC[npc])))) { - useWp = true; - } - } else { - useWp = true; - } - - if (!npcUnit && !useWp) { - let timeout = getTickCount() + 3000; - - while (!npcUnit) { - if (timeout < getTickCount()) { - break; - } - - Town.move(NPC[npc]); - Packet.flash(me.gid); - delay(me.ping * 2 + 100); - npcUnit = Game.getNPC(NPC[npc]); - } - } - - if (npcUnit && !useWp) { - getDistance(me, npcUnit) > 5 && Town.move(NPC[npc]); - - for (let i = 0; i < 5; i += 1) { - sendPacket(1, sdk.packets.send.EntityAction, 4, 0, 4, npcUnit.gid, 4, loc); - delay(500 + me.ping); - - if (me.act === act) { - break; - } - } - } else if (useWp) { - Town.goToTown(act); - } else { - print("Failed to move to " + npc); - me.overhead("Failed to move to " + npc); - } - - while (!me.gameReady) { - delay(100); - } - - return me.act === act; + let npc, npcUnit, loc; + let wp, useWp = false; + + switch (act) { + case 1: + npc = "Warriv"; + loc = sdk.areas.RogueEncampment; + + break; + case 2: + loc = sdk.areas.LutGholein; + npc = me.act === 1 ? "Warriv" : "Meshif"; + + break; + case 3: + npc = "Meshif"; + loc = sdk.areas.KurastDocktown; + + break; + case 5: + npc = "Tyrael"; + loc = sdk.areas.Harrogath; + + break; + } + + !me.inTown && Town.goToTown(); + + if (npc) { + npcUnit = Game.getNPC(NPC[npc]); + wp = Game.getObject("waypoint"); + + if (me.accessToAct(act) + && ((wp && !npcUnit) + || (wp && npcUnit && getDistance(me, wp) < getDistance(me, npcUnit)) + || (Town.getDistance("waypoint") < Town.getDistance(NPC[npc])))) { + useWp = true; + } + } else { + useWp = true; + } + + if (!npcUnit && !useWp) { + let timeout = getTickCount() + 3000; + + while (!npcUnit) { + if (timeout < getTickCount()) { + break; + } + + Town.move(NPC[npc]); + Packet.flash(me.gid); + delay(me.ping * 2 + 100); + npcUnit = Game.getNPC(NPC[npc]); + } + } + + if (npcUnit && !useWp) { + getDistance(me, npcUnit) > 5 && Town.move(NPC[npc]); + + for (let i = 0; i < 5; i += 1) { + sendPacket(1, sdk.packets.send.EntityAction, 4, 0, 4, npcUnit.gid, 4, loc); + delay(500 + me.ping); + + if (me.act === act) { + break; + } + } + } else if (useWp) { + Town.goToTown(act); + } else { + print("Failed to move to " + npc); + me.overhead("Failed to move to " + npc); + } + + while (!me.gameReady) { + delay(100); + } + + return me.act === act; }; Pather.getWP = function (area, clearPath) { - area !== me.area && this.journeyTo(area); + area !== me.area && this.journeyTo(area); - for (let i = 0; i < sdk.waypoints.Ids.length; i++) { - let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); + for (let i = 0; i < sdk.waypoints.Ids.length; i++) { + let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); - if (preset) { - Skill.haveTK ? this.moveNearUnit(preset, 20, {clearSettings: {clearPath: clearPath}}) : this.moveToUnit(preset, 0, 0, clearPath); + if (preset) { + Skill.haveTK ? this.moveNearUnit(preset, 20, { clearSettings: { clearPath: clearPath } }) : this.moveToUnit(preset, 0, 0, clearPath); - let wp = Game.getObject("waypoint"); + let wp = Game.getObject("waypoint"); - if (wp) { - for (let j = 0; j < 10; j++) { - if (!getUIFlag(sdk.uiflags.Waypoint)) { - if (wp.distance > 5 && Skill.useTK(wp) && j < 3) { - wp.distance > 21 && Attack.getIntoPosition(wp, 20, sdk.collision.Ranged); - Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, wp); - } else if (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint)) { - this.moveToUnit(wp) && Misc.click(0, 0, wp); - } - } + if (wp) { + for (let j = 0; j < 10; j++) { + if (!getUIFlag(sdk.uiflags.Waypoint)) { + if (wp.distance > 5 && Skill.useTK(wp) && j < 3) { + wp.distance > 21 && Attack.getIntoPosition(wp, 20, sdk.collision.Ranged); + Packet.telekinesis(wp); + } else if (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint)) { + this.moveToUnit(wp) && Misc.click(0, 0, wp); + } + } - if (getUIFlag(sdk.uiflags.Waypoint)) { - delay(500); + if (getUIFlag(sdk.uiflags.Waypoint)) { + delay(500); - // Keep wp menu open in town - !me.inTown && me.cancel(); + // Keep wp menu open in town + !me.inTown && me.cancel(); - return true; - } + return true; + } - delay(500); - } - } - } - } + delay(500); + } + } + } + } - return false; + return false; }; Pather.walkTo = function (x = undefined, y = undefined, minDist = undefined) { - while (!me.gameReady) { - delay(100); - } - - if (!x || !y) return false; - minDist === undefined && (minDist = me.inTown ? 2 : 4); - - let angle, angles, nTimer, whereToClick; - let nFail = 0; - let attemptCount = 0; - - // credit @Jaenster - // Stamina handler and Charge - if (!me.inTown && !me.dead) { - // Check if I have a stamina potion and use it if I do - if (me.staminaPercent <= 20) { - let stam = me.getItemsEx(-1, sdk.items.mode.inStorage).filter((i) => i.classid === sdk.items.StaminaPotion && i.isInInventory).first(); - !!stam && !me.deadOrInSequence && stam.use(); - } - (me.running && me.staminaPercent <= 15) && me.walk(); - // the less stamina you have, the more you wait to recover - let recover = me.staminaMaxDuration < 30 ? 80 : 50; - (me.walking && me.staminaPercent >= recover) && me.run(); - if (Skill.canUse(sdk.skills.Charge) && me.mp >= 9 && getDistance(me.x, me.y, x, y) > 8 && Skill.setSkill(sdk.skills.Charge, sdk.skills.hand.Left)) { - if (Skill.canUse(sdk.skills.Vigor)) { - Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right); - } else if (!Config.Vigor && !Attack.auradin && Skill.canUse(sdk.skills.HolyFreeze)) { - // Useful in classic to keep mobs cold while you rush them - Skill.setSkill(sdk.skills.HolyFreeze, sdk.skills.hand.Right); - } - Misc.click(0, 1, x, y); - while (!me.idle) { - delay(40); - } - } - } - - (me.inTown && me.walking) && me.run(); - - while (getDistance(me.x, me.y, x, y) > minDist && !me.dead && !Pather.stop) { - if (me.paladin) { - Skill.canUse(sdk.skills.Vigor) ? Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right) : Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); - } - - if (this.openDoors(x, y) && getDistance(me.x, me.y, x, y) <= minDist) { - return true; - } - - Misc.click(0, 0, x, y); - - attemptCount += 1; - nTimer = getTickCount(); - - while (!me.moving) { - if (me.dead || Pather.stop) return false; - - if ((getTickCount() - nTimer) > 500) { - if (nFail >= 3) return false; - - nFail += 1; - angle = Math.atan2(me.y - y, me.x - x); - angles = [Math.PI / 2, -Math.PI / 2]; - - for (let i = 0; i < angles.length; i += 1) { - // TODO: might need rework into getnearestwalkable - whereToClick = { - x: Math.round(Math.cos(angle + angles[i]) * 5 + me.x), - y: Math.round(Math.sin(angle + angles[i]) * 5 + me.y) - }; - - if (Attack.validSpot(whereToClick.x, whereToClick.y)) { - Misc.click(0, 0, whereToClick.x, whereToClick.y); - - let tick = getTickCount(); - - while (getDistance(me, whereToClick) > 2 && getTickCount() - tick < 1000) { - delay(40); - } - - break; - } - } - - break; - } - - delay(10); - } - - attemptCount > 1 && this.kickBarrels(x, y); - - // Wait until we're done walking - idle or dead - while (getDistance(me.x, me.y, x, y) > minDist && !me.idle) { - delay(10); - } - - if (attemptCount >= 3) return false; - } - - return !me.dead && getDistance(me.x, me.y, x, y) <= minDist; + while (!me.gameReady) { + delay(100); + } + + if (!x || !y) return false; + minDist === undefined && (minDist = me.inTown ? 2 : 4); + + let angle, angles, nTimer, whereToClick; + let nFail = 0; + let attemptCount = 0; + + // credit @Jaenster + // Stamina handler and Charge + if (!me.inTown && !me.dead) { + // Check if I have a stamina potion and use it if I do + if (me.staminaPercent <= 20) { + let stam = me.getItemsEx(-1, sdk.items.mode.inStorage).filter((i) => i.classid === sdk.items.StaminaPotion && i.isInInventory).first(); + !!stam && !me.deadOrInSequence && stam.use(); + } + (me.running && me.staminaPercent <= 15) && me.walk(); + // the less stamina you have, the more you wait to recover + let recover = me.staminaMaxDuration < 30 ? 80 : 50; + (me.walking && me.staminaPercent >= recover) && me.run(); + if (Skill.canUse(sdk.skills.Charge) && me.mp >= 9 && getDistance(me.x, me.y, x, y) > 8 && Skill.setSkill(sdk.skills.Charge, sdk.skills.hand.Left)) { + if (Skill.canUse(sdk.skills.Vigor)) { + Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right); + } else if (!Config.Vigor && !Attack.auradin && Skill.canUse(sdk.skills.HolyFreeze)) { + // Useful in classic to keep mobs cold while you rush them + Skill.setSkill(sdk.skills.HolyFreeze, sdk.skills.hand.Right); + } + Misc.click(0, 1, x, y); + while (!me.idle) { + delay(40); + } + } + } + + (me.inTown && me.walking) && me.run(); + + while (getDistance(me.x, me.y, x, y) > minDist && !me.dead && !Pather.stop) { + if (me.paladin) { + Skill.canUse(sdk.skills.Vigor) ? Skill.setSkill(sdk.skills.Vigor, sdk.skills.hand.Right) : Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); + } + + if (this.openDoors(x, y) && getDistance(me.x, me.y, x, y) <= minDist) { + return true; + } + + Misc.click(0, 0, x, y); + + attemptCount += 1; + nTimer = getTickCount(); + + while (!me.moving) { + if (me.dead || Pather.stop) return false; + + if ((getTickCount() - nTimer) > 500) { + if (nFail >= 3) return false; + + nFail += 1; + angle = Math.atan2(me.y - y, me.x - x); + angles = [Math.PI / 2, -Math.PI / 2]; + + for (let i = 0; i < angles.length; i += 1) { + // TODO: might need rework into getnearestwalkable + whereToClick = { + x: Math.round(Math.cos(angle + angles[i]) * 5 + me.x), + y: Math.round(Math.sin(angle + angles[i]) * 5 + me.y) + }; + + if (Attack.validSpot(whereToClick.x, whereToClick.y)) { + Misc.click(0, 0, whereToClick.x, whereToClick.y); + + let tick = getTickCount(); + + while (getDistance(me, whereToClick) > 2 && getTickCount() - tick < 1000) { + delay(40); + } + + break; + } + } + + break; + } + + delay(10); + } + + attemptCount > 1 && this.kickBarrels(x, y); + + // Wait until we're done walking - idle or dead + while (getDistance(me.x, me.y, x, y) > minDist && !me.idle) { + delay(10); + } + + if (attemptCount >= 3) return false; + } + + return !me.dead && getDistance(me.x, me.y, x, y) <= minDist; }; Pather.teleportTo = function (x, y, maxRange = 5) { - for (let i = 0; i < 3; i++) { - Config.PacketCasting > 0 ? Packet.teleport(x, y) : Skill.cast(sdk.skills.Teleport, sdk.skills.hand.Right, x, y); - let tick = getTickCount(); - let pingDelay = i === 0 ? 150 : me.getPingDelay(); + for (let i = 0; i < 3; i++) { + Config.PacketCasting > 0 ? Packet.teleport(x, y) : Skill.cast(sdk.skills.Teleport, sdk.skills.hand.Right, x, y); + let tick = getTickCount(); + let pingDelay = i === 0 ? 150 : me.getPingDelay(); - while (getTickCount() - tick < Math.max(500, pingDelay * 2 + 200)) { - if ([x, y].distance < maxRange || Pather.stop) { - return true; - } + while (getTickCount() - tick < Math.max(500, pingDelay * 2 + 200)) { + if ([x, y].distance < maxRange || Pather.stop) { + return true; + } - delay(10); - } - } + delay(10); + } + } - return false; + return false; }; Pather.moveTo = function (x, y, retry, clearPath, pop) { - // Abort if dead - if (me.dead) return false; - - if (!x || !y) return false; // I don't think this is a fatal error so just return false - if (typeof x !== "number" || typeof y !== "number") throw new Error("moveTo: Coords must be numbers"); - if ([x, y].distance < 2) return true; - - for (let i = 0; i < this.cancelFlags.length; i += 1) { - getUIFlag(this.cancelFlags[i]) && me.cancel(); - } - - let fail = 0; - let node = {x: x, y: y}; - let cleared = false; - let leaped = false; - let invalidCheck = false; - let useTeleport = this.useTeleport(); - let tpMana = Skill.getManaCost(sdk.skills.Teleport); - let preSkill = me.getSkill(sdk.skills.get.RightId); - let annoyingArea = [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3].includes(me.area); - let clearSettings = { - clearPath: (!!clearPath || !useTeleport), // walking characters need to clear in front of them - range: 10, - specType: (typeof clearPath === "number" ? clearPath : 0), - }; - - retry === undefined && (retry = useTeleport ? 3 : 15); - clearPath === undefined && (clearPath = Config.AttackSkill.some(skillId => skillId > 0) && !useTeleport ? true : false); - pop === undefined && (pop = false); - let path = getPath(me.area, x, y, me.x, me.y, useTeleport ? 1 : 0, useTeleport ? (annoyingArea ? 30 : this.teleDistance) : this.walkDistance); - if (!path) throw new Error("moveTo: Failed to generate path."); - - path.reverse(); - pop && path.pop(); - PathDebug.drawPath(path); - useTeleport && Config.TeleSwitch && path.length > 5 && me.switchWeapons(Attack.getPrimarySlot() ^ 1); - - while (path.length > 0) { - // Abort if dead - if (me.dead || Pather.stop) { - Pather.stop = false; // Reset value - - return false; - } - - for (let i = 0; i < this.cancelFlags.length; i++) { - getUIFlag(this.cancelFlags[i]) && me.cancel(); - } - - node = path.shift(); - - /* Right now getPath's first node is our own position so it's not necessary to take it into account - This will be removed if getPath changes - */ - if (getDistance(me, node) > 2) { - fail >= 3 && fail % 3 === 0 && !Attack.validSpot(node.x, node.y) && (invalidCheck = true); - // Make life in Maggot Lair easier - should this include arcane as well? - if (annoyingArea || invalidCheck) { - let adjustedNode = this.getNearestWalkable(node.x, node.y, 15, 3, sdk.collision.BlockWalk); - - if (adjustedNode) { - node.x = adjustedNode[0]; - node.y = adjustedNode[1]; - invalidCheck && (invalidCheck = false); - } - - if (annoyingArea) { - clearSettings.overrideConfig = true; - clearSettings.range = 5; - } - - retry <= 3 && !useTeleport && (retry = 15); - } - - if (useTeleport && tpMana < me.mp ? Pather.teleportTo(node.x, node.y) : Pather.walkTo(node.x, node.y, (fail > 0 || me.inTown) ? 2 : 4)) { - if (Pather.stop) { - continue; // stops on next interation - } - - if (!me.inTown) { - if (this.recursion) { - this.recursion = false; - try { - NodeAction.go(clearSettings); - - if (getDistance(me, node.x, node.y) > 5) { - this.moveTo(node.x, node.y); - } - } finally { - this.recursion = true; - } - } - - Misc.townCheck(); - } - } else { - if (Pather.stop) { - continue; // stops on next interation - } - - if (!me.inTown) { - if (!useTeleport && ((me.checkForMobs({range: 10}) && Attack.clear(8)) || Pather.kickBarrels(node.x, node.y) || Pather.openDoors(node.x, node.y))) { - continue; - } - - if (fail > 0 && (!useTeleport || tpMana > me.mp)) { - // Don't go berserk on longer paths - if (!cleared && me.checkForMobs({range: 6}) && Attack.clear(5)) { - cleared = true; - } - - // Only do this once - if (fail > 1 && !leaped && Skill.canUse(sdk.skills.LeapAttack) && Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, node.x, node.y)) { - leaped = true; - } - } - } - - // Reduce node distance in new path - path = getPath(me.area, x, y, me.x, me.y, useTeleport ? 1 : 0, useTeleport ? rand(25, 35) : rand(10, 15)); - if (!path) throw new Error("moveNear: Failed to generate path."); - - path.reverse(); - PathDebug.drawPath(path); - pop && path.pop(); - - if (fail > 0) { - console.debug("move retry " + fail); - Packet.flash(me.gid); - - if (fail >= retry) { - break; - } - } - fail++; - } - } - - delay(5); - } - - useTeleport && Config.TeleSwitch && me.switchWeapons(Attack.getPrimarySlot()); - me.getSkill(sdk.skills.get.RightId) !== preSkill && Skill.setSkill(preSkill, sdk.skills.hand.Right); - PathDebug.removeHooks(); - - return getDistance(me, node.x, node.y) < 5; + // Abort if dead + if (me.dead) return false; + + if (!x || !y) return false; // I don't think this is a fatal error so just return false + if (typeof x !== "number" || typeof y !== "number") throw new Error("moveTo: Coords must be numbers"); + if ([x, y].distance < 2) return true; + + for (let i = 0; i < this.cancelFlags.length; i += 1) { + getUIFlag(this.cancelFlags[i]) && me.cancel(); + } + + let fail = 0; + let node = { x: x, y: y }; + let cleared = false; + let leaped = false; + let invalidCheck = false; + let useTeleport = this.useTeleport(); + let tpMana = Skill.getManaCost(sdk.skills.Teleport); + let preSkill = me.getSkill(sdk.skills.get.RightId); + let annoyingArea = [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3].includes(me.area); + let clearSettings = { + clearPath: (!!clearPath || !useTeleport), // walking characters need to clear in front of them + range: 10, + specType: (typeof clearPath === "number" ? clearPath : 0), + }; + + retry === undefined && (retry = useTeleport ? 3 : 15); + clearPath === undefined && (clearPath = Config.AttackSkill.some(skillId => skillId > 0) && !useTeleport ? true : false); + pop === undefined && (pop = false); + let path = getPath(me.area, x, y, me.x, me.y, useTeleport ? 1 : 0, useTeleport ? (annoyingArea ? 30 : this.teleDistance) : this.walkDistance); + if (!path) throw new Error("moveTo: Failed to generate path."); + + path.reverse(); + pop && path.pop(); + PathDebug.drawPath(path); + useTeleport && Config.TeleSwitch && path.length > 5 && me.switchWeapons(Attack.getPrimarySlot() ^ 1); + + while (path.length > 0) { + // Abort if dead + if (me.dead || Pather.stop) { + Pather.stop = false; // Reset value + + return false; + } + + for (let i = 0; i < this.cancelFlags.length; i++) { + getUIFlag(this.cancelFlags[i]) && me.cancel(); + } + + node = path.shift(); + + /* Right now getPath's first node is our own position so it's not necessary to take it into account + This will be removed if getPath changes + */ + if (getDistance(me, node) > 2) { + fail >= 3 && fail % 3 === 0 && !Attack.validSpot(node.x, node.y) && (invalidCheck = true); + // Make life in Maggot Lair easier - should this include arcane as well? + if (annoyingArea || invalidCheck) { + let adjustedNode = this.getNearestWalkable(node.x, node.y, 15, 3, sdk.collision.BlockWalk); + + if (adjustedNode) { + node.x = adjustedNode[0]; + node.y = adjustedNode[1]; + invalidCheck && (invalidCheck = false); + } + + if (annoyingArea) { + clearSettings.overrideConfig = true; + clearSettings.range = 5; + } + + retry <= 3 && !useTeleport && (retry = 15); + } + + if (useTeleport && tpMana < me.mp ? Pather.teleportTo(node.x, node.y) : Pather.walkTo(node.x, node.y, (fail > 0 || me.inTown) ? 2 : 4)) { + if (Pather.stop) { + continue; // stops on next interation + } + + if (!me.inTown) { + if (this.recursion) { + this.recursion = false; + try { + NodeAction.go(clearSettings); + + if (getDistance(me, node.x, node.y) > 5) { + this.moveTo(node.x, node.y); + } + } finally { + this.recursion = true; + } + } + } + } else { + if (Pather.stop) { + continue; // stops on next interation + } + + if (!me.inTown) { + if (!useTeleport && ((me.checkForMobs({ range: 10 }) && Attack.clear(8)) || Pather.kickBarrels(node.x, node.y) || Pather.openDoors(node.x, node.y))) { + continue; + } + + if (fail > 0 && (!useTeleport || tpMana > me.mp)) { + // Don't go berserk on longer paths + if (!cleared && me.checkForMobs({ range: 6 }) && Attack.clear(5)) { + cleared = true; + } + + // Only do this once + if (fail > 1 && !leaped && Skill.canUse(sdk.skills.LeapAttack) && Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, node.x, node.y)) { + leaped = true; + } + } + } + + // Reduce node distance in new path + path = getPath(me.area, x, y, me.x, me.y, useTeleport ? 1 : 0, useTeleport ? rand(25, 35) : rand(10, 15)); + if (!path) throw new Error("moveNear: Failed to generate path."); + + path.reverse(); + PathDebug.drawPath(path); + pop && path.pop(); + + if (fail > 0) { + console.debug("move retry " + fail); + Packet.flash(me.gid); + + if (fail >= retry) { + break; + } + } + fail++; + } + } + + delay(5); + } + + useTeleport && Config.TeleSwitch && me.switchWeapons(Attack.getPrimarySlot()); + me.getSkill(sdk.skills.get.RightId) !== preSkill && Skill.setSkill(preSkill, sdk.skills.hand.Right); + PathDebug.removeHooks(); + + return getDistance(me, node.x, node.y) < 5; }; diff --git a/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.js b/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.js index 051dde7ee..98c9bd702 100644 --- a/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.js @@ -5,31 +5,31 @@ * */ -includeIfNotIncluded("common/Pickit.js"); +includeIfNotIncluded("core/Pickit.js"); Pickit.basicPickItems = function () { - let itemList = []; - let item = Game.getItem(); + let itemList = []; + let item = Game.getItem(); - if (item) { - do { - if (item.onGroundOrDropping) { - itemList.push(copyUnit(item)); - } - } while (item.getNext()); - } + if (item) { + do { + if (item.onGroundOrDropping) { + itemList.push(copyUnit(item)); + } + } while (item.getNext()); + } - while (itemList.length > 0) { - itemList.sort(this.sortFastPickItems); - item = copyUnit(itemList.shift()); + while (itemList.length > 0) { + itemList.sort(this.sortFastPickItems); + item = copyUnit(itemList.shift()); - // Check if the item unit is still valid - if (item.x !== undefined) { - if (this.canPick(item) && (Storage.Inventory.CanFit(item) || Pickit.essentials.includes(item.itemType))) { - this.pickItem(item); - } - } - } + // Check if the item unit is still valid + if (item.x !== undefined) { + if (this.canPick(item) && (Storage.Inventory.CanFit(item) || Pickit.essentials.includes(item.itemType))) { + this.pickItem(item); + } + } + } - return true; + return true; }; diff --git a/d2bs/kolbot/libs/manualplay/libs/TownOverrides.js b/d2bs/kolbot/libs/manualplay/libs/TownOverrides.js index 306aadcf5..28b823db8 100644 --- a/d2bs/kolbot/libs/manualplay/libs/TownOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/TownOverrides.js @@ -5,44 +5,47 @@ * */ -includeIfNotIncluded("common/Town.js"); +includeIfNotIncluded("core/Town.js"); Town.stash = function (stashGold = true) { - me.cancel(); - - let items = me.getItemsEx() - .filter(function (item) { - return item.isInInventory && !(item.isEquippedCharm && (item.unique || Storage.Inventory.IsLocked(item, Config.Inventory))); - }) - .sort(function (a, b) { - if ((a.itemType >= sdk.items.type.Amethyst && a.itemType <= sdk.items.type.Skull) || a.itemType === sdk.items.type.Rune || a.unique) { - return -1; - } - - if ((b.itemType >= sdk.items.type.Amethyst && b.itemType <= sdk.items.type.Skull) || b.itemType === sdk.items.type.Rune || b.unique) { - return 1; - } - - return a.quality - b.quality; - }); - - if (items) { - for (let i = 0; i < items.length; i++) { - if (this.canStash(items[i])) { - Misc.itemLogger("Stashed", items[i]); - Storage.Stash.MoveTo(items[i]); - } - } - } - - // Stash gold - if (stashGold) { - if (me.getStat(sdk.stats.Gold) >= Config.StashGold && me.getStat(sdk.stats.GoldBank) < 25e5 && this.openStash()) { - gold(me.getStat(sdk.stats.Gold), 3); - delay(1000); // allow UI to initialize - me.cancel(); - } - } - - return true; + me.cancel(); + + let items = me.getItemsEx() + .filter(function (item) { + return item.isInInventory + && !(item.isEquippedCharm && (item.unique || Storage.Inventory.IsLocked(item, Config.Inventory))); + }) + .sort(function (a, b) { + if ((a.itemType >= sdk.items.type.Amethyst + && a.itemType <= sdk.items.type.Skull) || a.itemType === sdk.items.type.Rune || a.unique) { + return -1; + } + + if ((b.itemType >= sdk.items.type.Amethyst + && b.itemType <= sdk.items.type.Skull) || b.itemType === sdk.items.type.Rune || b.unique) { + return 1; + } + + return a.quality - b.quality; + }); + + if (items) { + for (let i = 0; i < items.length; i++) { + if (this.canStash(items[i])) { + Item.logger("Stashed", items[i]); + Storage.Stash.MoveTo(items[i]); + } + } + } + + // Stash gold + if (stashGold) { + if (me.getStat(sdk.stats.Gold) >= Config.StashGold && me.getStat(sdk.stats.GoldBank) < 25e5 && this.openStash()) { + gold(me.getStat(sdk.stats.Gold), 3); + delay(1000); // allow UI to initialize + me.cancel(); + } + } + + return true; }; diff --git a/d2bs/kolbot/libs/manualplay/main.js b/d2bs/kolbot/libs/manualplay/main.js new file mode 100644 index 000000000..25e42bf1b --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/main.js @@ -0,0 +1,376 @@ +/* eslint-disable max-len */ +/** +* @filename main.js +* @author theBGuy +* @credits kolton for orginal MapThread, +* isid0re for the box/frame style, +* laz for gamepacketsent event handler +* @desc main thread for D2BotMap.dbj +*/ +js_strict(true); +include("critical.js"); // required + +// globals needed for core gameplay +includeCoreLibs(); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +// main thread specific +const LocalChat = require("../modules/LocalChat"); + +include("manualplay/MapMode.js"); +MapMode.include(); + +const Hooks = { + dashBoard: { x: 113, y: 490 }, + portalBoard: { x: 12, y: 432 }, + qolBoard: { x: 545, y: 490 }, + resfix: { x: (me.screensize ? 0 : -160), y: (me.screensize ? 0 : -120) }, + saidMessage: false, + userAddon: false, + enabled: true, + flushed: false, + + init: function () { + let files = dopen("libs/manualplay/hooks/").getFiles(); + + Array.isArray(files) && files + .filter(file => file.endsWith(".js")) + .forEach(function (x) { + if (!isIncluded("manualplay/hooks/" + x)) { + if (!include("manualplay/hooks/" + x)) { + throw new Error("Failed to include " + "manualplay/hooks/" + x); + } + } + }); + }, + + update: function () { + while (!me.gameReady) { + delay(100); + } + + if (!this.enabled) { + Hooks.enabled = getUIFlag(sdk.uiflags.AutoMap); + + return; + } + + ActionHooks.check(); + VectorHooks.check(); + MonsterHooks.check(); + ShrineHooks.check(); + ItemHooks.check(); + TextHooks.check(); + Hooks.flushed = false; + }, + + flush: function (flag) { + if (Hooks.flushed === flag) return true; + + if (flag === true) { + Hooks.enabled = false; + + MonsterHooks.flush(); + ShrineHooks.flush(); + TextHooks.flush(); + VectorHooks.flush(); + ActionHooks.flush(); + ItemHooks.flush(); + } else { + if (sdk.uiflags.Waypoint === flag) { + VectorHooks.flush(); + TextHooks.displaySettings = false; + TextHooks.check(); + } else if (sdk.uiflags.Inventory === flag && [sdk.uiflags.Stash, sdk.uiflags.Cube, sdk.uiflags.TradePrompt].every((el) => !getUIFlag(el))) { + ItemHooks.flush(); + TextHooks.check(); + } else { + MonsterHooks.flush(); + ShrineHooks.flush(); + TextHooks.flush(); + VectorHooks.flush(); + ActionHooks.flush(); + ItemHooks.flush(); + } + } + + Hooks.flushed = flag; + + return true; + } +}; + +function main () { + D2Bot.init(); // Get D2Bot# handle + D2Bot.ingame(); + + (function (global, original) { + global.load = function (...args) { + original.apply(this, args); + delay(500); + }; + })([].filter.constructor("return this")(), load); + + // wait until game is ready + while (!me.gameReady) { + delay(50); + } + + clearAllEvents(); // remove any event listeners from game crash + + // load heartbeat if it isn't already running + let _heartbeat = getScript("threads/heartbeat.js"); + if (!_heartbeat || !_heartbeat.running) { + load("threads/heartbeat.js"); + } + + console.log("ÿc9Map Thread Loaded."); + MapMode.include(); + Config.init(true); + LocalChat.init(); + Storage.Init(); + Pickit.init(true); + Hooks.init(); + + // load threads + me.automap = true; + load("libs/manualplay/threads/maphelper.js"); + load("libs/manualplay/threads/maptoolsthread.js"); + Config.ManualPlayPick && load("libs/manualplay/threads/pickthread.js"); + if (Config.PublicMode) { + Config.PublicMode === true + ? require("../modules/workers/SimpleParty") + : load("threads/Party.js"); + } + + const Worker = require("../modules/Worker"); + const UnitInfo = new (require("../modules/UnitInfo")); + + Worker.runInBackground.unitInfo = function () { + // always, maybe a timeout would be good though + UnitInfo.check(); + + // not being used atm - keep looping + if (!Hooks.userAddon) { + return true; + } + + UnitInfo.createInfo(Game.getSelectedUnit()); + + return true; + }; + + Worker.runInBackground.antiIdle = (function () { + const last = { + area: me.area, + x: me.x, + y: me.y, + idleTick: getTickCount() + Time.seconds(rand(1200, 1500)), + }; + + return function () { + if (!me.gameReady) return true; + if (last.area !== me.area || last.distance > 10) { + last.area = me.area; + last.x = me.x; + last.y = me.y; + last.idleTick = getTickCount() + Time.seconds(rand(1200, 1500)); + } + + if (getTickCount() - last.idleTick > 0) { + Packet.questRefresh(); + last.idleTick += Time.seconds(rand(1200, 1500)); + console.log("Sent anti-idle packet, next refresh in: (" + Time.format(last.idleTick - getTickCount()) + ")"); + } + return true; + }; + })(); + + const log = function (msg = "") { + me.overhead(msg); + console.log(msg); + }; + + if (Config.MapMode.UseOwnItemFilter) { + ItemHooks.pickitEnabled = true; + } + + const hideFlags = [ + sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, + sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow, + sdk.uiflags.ChatBox, sdk.uiflags.EscMenu, + sdk.uiflags.Shop, sdk.uiflags.Quest, + sdk.uiflags.Waypoint, sdk.uiflags.TradePrompt, + sdk.uiflags.Msgs, sdk.uiflags.Stash, + sdk.uiflags.Cube, sdk.uiflags.Help, sdk.uiflags.MercScreen + ]; + /** @type {Set} */ + const revealedAreas = new Set(); + + /** @param {number} area */ + const revealArea = function (area) { + if (!revealedAreas.has(area)) { + delay(500); + + if (!getRoom()) { + return; + } + + revealLevel(true); + revealedAreas.add(area); + } + }; + + /** + * Run commands from chat + * @param {string} msg + * @returns {boolean} + */ + const runCommand = function (msg) { + if (msg.length <= 1) return true; + + msg = msg.toLowerCase(); + let cmd = msg.split(" ")[0].split(".")[1]; + let msgList = msg.split(" "); + let qolObj = { type: "qol", dest: false, action: false }; + + switch (cmd) { + case "useraddon": + Hooks.userAddon = !Hooks.userAddon; + log("userAddon set to " + Hooks.userAddon); + + break; + case "me": + log("Character Level: " + me.charlvl + " | Area: " + me.area + " | x: " + me.x + ", y: " + me.y); + + break; + case "stash": + me.inTown && (qolObj.action = "stashItems"); + + break; + case "gamble": + me.inTown && (qolObj.action = "gamble"); + + break; + case "pick": + case "cowportal": + case "uberportal": + case "filltps": + qolObj.action = cmd; + + break; + case "drop": + if (msgList.length < 2) { + console.log("ÿc1Missing arguments"); + break; + } + + qolObj.type = "drop"; + qolObj.action = msgList[1]; + + break; + case "stack": + if (msgList.length < 2) { + console.log("ÿc1Missing arguments"); + break; + } + + qolObj.type = "stack"; + qolObj.action = msgList[1]; + + break; + case "help": + if (HelpMenu.cleared) { + HelpMenu.showMenu(); + log("Click each command for more info"); + } + + break; + case "hide": + hideConsole(); + HelpMenu.hideMenu(); + TextHooks.displayTitle = false; + { + let tHook = TextHooks.getHook("title", TextHooks.hooks); + !!tHook && tHook.hook.remove(); + } + + break; + case "make": + { + let className = sdk.player.class.nameOf(me.classid); + if (!FileTools.exists("libs/manualplay/config/" + className + "." + me.name + ".js")) { + FileTools.copy("libs/manualplay/config/" + className + ".js", "libs/manualplay/config/" + className + "." + me.name + ".js"); + D2Bot.printToConsole("libs/manualplay/config/" + className + "." + me.name + ".js has been created. Configure the bot and reload to apply changes"); + log("libs/manualplay/config/" + className + "." + me.name + ".js has been created. Configure the bot and reload to apply changes"); + } + } + + break; + default: + console.warn("ÿc1Invalid command : " + cmd); + + break; + } + + qolObj.action && Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(qolObj)); + + return true; + }; + + /** + * @param {string} speaker + * @param {string} msg + * @returns {boolean} + */ + const onChatInput = function (speaker, msg) { + if (msg.length && msg[0] === ".") { + runCommand(msg); + + return true; + } + + return false; + }; + + addEventListener("chatinputblocker", onChatInput); + addEventListener("keyup", ActionHooks.event); + // addEventListener("itemaction", Pickit.itemEvent); + + while (true) { + while (!me.area || !me.gameReady) { + delay(100); + } + + let hideFlagFound = false; + + revealArea(me.area); + + for (let i = 0; i < hideFlags.length; i++) { + if (getUIFlag(hideFlags[i])) { + Hooks.flush(hideFlags[i]); + ActionHooks.checkAction(); + hideFlagFound = true; + delay(100); + + break; + } + } + + if (hideFlagFound) continue; + + getUIFlag(sdk.uiflags.AutoMap) + ? Hooks.update() + : Hooks.flush(true) && (!HelpMenu.cleared && HelpMenu.hideMenu()); + + delay(20); + + while (getUIFlag(sdk.uiflags.ShowItem)) { + ItemHooks.flush(); + } + } +} diff --git a/d2bs/kolbot/libs/manualplay/threads/MapHelper.js b/d2bs/kolbot/libs/manualplay/threads/MapHelper.js index 906f75c1f..7030fb07c 100644 --- a/d2bs/kolbot/libs/manualplay/threads/MapHelper.js +++ b/d2bs/kolbot/libs/manualplay/threads/MapHelper.js @@ -2,419 +2,429 @@ * @filename MapHelper.js * @author theBGuy * @credits kolton -* @desc MapHelper used in conjuction with MapThread.js +* @desc MapHelper used in conjuction with main.js * */ -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("AutoMule.js"); -include("Gambling.js"); -include("TorchSystem.js"); -include("CraftingSystem.js"); -include("MuleLogger.js"); -include("common/util.js"); - -includeCommonLibs(); +js_strict(true); +include("critical.js"); // required + +// globals needed for core gameplay +includeCoreLibs(); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); // MapMode include("manualplay/MapMode.js"); MapMode.include(); -function main() { - let obj = {type: false, dest: false, action: false}; - let action, fail = 0, x, y; - let mapThread = getScript("libs/manualplay/threads/mapthread.js"); - - const portalMap = {}; - portalMap[sdk.areas.Abaddon] = { - 14: [12638, 6373], - 15: [12638, 6063], - 20: [12708, 6063], - 25: [12948, 6128], - }; - portalMap[sdk.areas.PitofAcheron] = { - 14: [12638, 7873], - 15: [12638, 7563], - 20: [12708, 7563], - 25: [12948, 7628], - }; - portalMap[sdk.areas.InfernalPit] = { - 14: [12638, 9373], - 20: [12708, 9063], - 25: [12948, 9128], - }; - - console.log("ÿc9MapHelper loaded"); - Config.init(); - Attack.init(true); - Pickit.init(); - Storage.Init(); - addEventListener("scriptmsg", function (msg) { - action = msg; - }); - - this.togglePickThread = function () { - if (!Config.ManualPlayPick) return; - - let pickThread = getScript("tools/pickthread.js"); - - if (pickThread) { - if (pickThread.running) { - pickThread.pause(); - } else if (!pickThread.running) { - pickThread.resume(); - } - } - }; - - this.togglePause = function () { - if (mapThread) { - if (mapThread.running) { - print("pause mapthread"); - mapThread.pause(); - } else if (!mapThread.running) { - print("resume mapthread"); - mapThread.resume(); - - if (!mapThread.running) { - fail++; - - if (fail % 5 === 0 && !getScript("libs/manualplay/threads/mapthread.js")) { - print("MapThread shut down, exiting MapHelper"); - - return false; - } - } - } - } else if (!getScript("libs/manualplay/threads/mapthread.js")) { - print("MapThread shut down, exiting MapHelper"); - - return false; - } - - return true; - }; - - while (true) { - if (getUIFlag(sdk.uiflags.EscMenu)) { - delay(100); - mapThread.running && this.togglePause(); - - } else { - if (!mapThread.running) { - if (!this.togglePause()) { - return; - } - } - } - - if (action) { - try { - let temp = JSON.parse(action); - temp && Object.assign(obj, temp); - - addEventListener("keyup", Pather.stopEvent); - this.togglePickThread(); - - if (obj) { - let redPortal, chestLoc, king, unit; - - switch (obj.type) { - case "area": - if (obj.dest === sdk.areas.ArreatSummit) { - Pather.moveToExit(obj.dest, false); - } else if ([sdk.areas.CanyonofMagic, sdk.areas.A2SewersLvl1, sdk.areas.PalaceCellarLvl3, sdk.areas.PandemoniumFortress, sdk.areas.BloodyFoothills].includes(obj.dest)) { - Pather.journeyTo(obj.dest); - } else if (obj.dest === sdk.areas.DurielsLair) { - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder, -11, 3); - - for (let i = 0; i < 3; i++) { - if (Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair)) { - break; - } - } - } else { - Pather.moveToExit(obj.dest, true); - } - - break; - case "unit": - if (me.inArea(sdk.areas.MooMooFarm) - || (me.inArea(sdk.areas.DurielsLair) && Misc.talkToTyrael())) { - break; - } - - Pather.moveToUnit(obj.dest, true); - - switch (me.area) { - case sdk.areas.ColdPlains: - Pather.moveToExit(sdk.areas.CaveLvl1, true); - - break; - case sdk.areas.BlackMarsh: - Pather.moveToExit(sdk.areas.HoleLvl1, true); - - break; - case sdk.areas.LutGholein: - Pather.useUnit(sdk.unittype.Stairs, sdk.exits.preset.A2EnterSewersDoor, sdk.areas.A2SewersLvl1); - - break; - case sdk.areas.KurastBazaar: - Pather.useUnit(sdk.unittype.Stairs, sdk.exits.preset.A3EnterSewers, sdk.areas.A3SewersLvl1); - - break; - } - - if (obj.action && typeof obj.action === "object") { - if (obj.action.do === "openChest") { - !!obj.action.id && Misc.openChest(obj.action.id); - } else if (obj.action.do === "usePortal") { - !!obj.action.id ? Pather.usePortal(obj.action.id) : Pather.usePortal(); - } - } - - break; - case "wp": - Pather.getWP(me.area); - - break; - case "actChange": - print("Going to act: " + obj.dest); - Pather.changeAct(obj.dest); - - break; - case "portal": - if (obj.dest === sdk.areas.WorldstoneChamber && Game.getMonster(sdk.monsters.ThroneBaal)) { - me.overhead("Can't enter Worldstone Chamber yet. Baal still in area"); - - break; - } else if (obj.dest === sdk.areas.WorldstoneChamber && !Game.getMonster(sdk.monsters.ThroneBaal)) { - redPortal = Game.getObject(sdk.objects.WorldstonePortal); - redPortal && Pather.usePortal(null, null, redPortal); - - break; - } - - switch (obj.dest) { - case sdk.areas.RogueEncampment: - king = Game.getPresetMonster(me.area, sdk.monsters.preset.TheCowKing); - - switch (king.x) { - case 1: - Pather.moveTo(25183, 5923); - - break; - } - - break; - case sdk.areas.StonyField: - Pather.moveTo(25173, 5086); - redPortal = Pather.getPortal(obj.dest); - - break; - case sdk.areas.MooMooFarm: - redPortal = Pather.getPortal(obj.dest); - - break; - case sdk.areas.ArcaneSanctuary: - Pather.moveTo(12692, 5195); - redPortal = Pather.getPortal(obj.dest); - !redPortal && Pather.useWaypoint(obj.dest); - - break; - case sdk.areas.Harrogath: - Pather.moveTo(20193, 8693); - - break; - case sdk.areas.FrigidHighlands: - case sdk.areas.ArreatPlateau: - case sdk.areas.FrozenTundra: - chestLoc = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); - - if (!chestLoc) { - break; - } - - [x, y] = portalMap[me.area][chestLoc.x]; - - Pather.moveTo(x, y); - Pather.usePortal(); - - break; - case sdk.areas.MatronsDen: - case sdk.areas.ForgottenSands: - case sdk.areas.FurnaceofPain: - case sdk.areas.UberTristram: - redPortal = Pather.getPortal(obj.dest); - - break; - default: - Pather.usePortal(obj.dest); - - break; - } - - if (redPortal) { - Pather.moveToUnit(redPortal); - Pather.usePortal(null, null, redPortal); - } - - break; - case "qol": - switch (obj.action) { - case "heal": - Town.initNPC("Heal", "heal"); - - break; - case "openStash": - Town.openStash(); - - break; - case "stashItems": - Town.stash(true, true); - - break; - case "makePortal": - Pather.makePortal(); - - break; - case "takePortal": - Town.goToTown(); - - break; - case "clear": - Attack.clear(10); - - break; - case "cowportal": - Misc.openRedPortal(sdk.areas.MooMooFarm); - - break; - case "ubertrist": - Misc.openRedPortal(sdk.areas.UberTristram); - - break; - case "uberportal": - Misc.openRedPortal(); - - break; - case "filltps": - Town.fillTome(sdk.items.TomeofTownPortal); - me.cancel(); - - break; - case "moveItemFromInvoToStash": - case "moveItemFromStashToInvo": - unit = Game.getSelectedUnit(); - - switch (unit.location) { - case sdk.storage.Inventory: - Storage.Stash.CanFit(unit) && Storage.Stash.MoveTo(unit); - - break; - case sdk.storage.Stash: - Storage.Inventory.CanFit(unit) && Storage.Inventory.MoveTo(unit); - - break; - } - - break; - case "moveItemFromInvoToCube": - case "moveItemFromCubeToInvo": - unit = Game.getSelectedUnit(); - - switch (unit.location) { - case sdk.storage.Inventory: - Storage.Cube.CanFit(unit) && Storage.Cube.MoveTo(unit); - - break; - case sdk.storage.Cube: - Storage.Inventory.CanFit(unit) && Storage.Inventory.MoveTo(unit); - - break; - } - - break; - case "moveItemFromInvoToTrade": - case "moveItemFromTradeToInvo": - unit = Game.getSelectedUnit(); - - switch (unit.location) { - case sdk.storage.Inventory: - Storage.TradeScreen.CanFit(unit) && Storage.TradeScreen.MoveTo(unit); - - break; - case sdk.storage.TradeWindow: - if (Storage.Inventory.CanFit(unit)) { - Packet.itemToCursor(unit); - Storage.Inventory.MoveTo(unit); - } - - break; - } - - break; - case "pick": - Config.ManualPlayPick ? Pickit.pickItems() : Pickit.basicPickItems(); - - break; - case "sellItem": - unit = Game.getSelectedUnit(); - - if (unit.isInInventory && unit.sellable) { - try { - unit.sell(); - } catch (e) { - console.error(e); - } - } - - break; - } - - break; - case "drop": - switch (obj.action) { - case "invo": - Misc.dropItems(sdk.storage.Inventory); - - break; - case "stash": - Misc.dropItems(sdk.storage.Stash); - - break; - } - - break; - case "stack": - switch (obj.action) { - case "thawing": - Town.buyPots(10, "Thawing", true, true); +function main () { + // getUnit test + getUnit(-1) === null && console.warn("getUnit bug detected"); + + console.log("ÿc9MapHelper loaded"); + + let obj = { type: false, dest: false, action: false }; + let action, fail = 0, x, y; + const mapThread = getScript("libs/manualplay/main.js"); + + const portalMap = {}; + portalMap[sdk.areas.Abaddon] = { + 14: [12638, 6373], + 15: [12638, 6063], + 20: [12708, 6063], + 25: [12948, 6128], + }; + portalMap[sdk.areas.PitofAcheron] = { + 14: [12638, 7873], + 15: [12638, 7563], + 20: [12708, 7563], + 25: [12948, 7628], + }; + portalMap[sdk.areas.InfernalPit] = { + 14: [12638, 9373], + 20: [12708, 9063], + 25: [12948, 9128], + }; + + Config.init(); + Attack.init(true); + Pickit.init(); + Storage.Init(); + addEventListener("scriptmsg", function (msg) { + action = msg; + }); + + const togglePickThread = function () { + if (!Config.ManualPlayPick) return; + + const pickThread = getScript("libs/manualplay/threads/pickthread.js"); + + if (pickThread) { + if (pickThread.running) { + pickThread.pause(); + } else if (!pickThread.running) { + pickThread.resume(); + } + } + }; + + const togglePause = function () { + if (mapThread) { + if (mapThread.running) { + console.log("pause mapthread"); + mapThread.pause(); + } else if (!mapThread.running) { + console.log("resume mapthread"); + mapThread.resume(); + + if (!mapThread.running) { + fail++; + + if (fail % 5 === 0 && !getScript("libs/manualplay/main.js")) { + console.log("MapThread shut down, exiting MapHelper"); + + return false; + } + } + } + } else if (!getScript("libs/manualplay/main.js")) { + console.log("MapThread shut down, exiting MapHelper"); + + return false; + } + + return true; + }; + + while (true) { + if (getUIFlag(sdk.uiflags.EscMenu)) { + delay(100); + mapThread.running && togglePause(); + } else { + if (!mapThread.running) { + if (!togglePause()) { + return; + } + } + } + + if (action) { + try { + let temp = JSON.parse(action); + temp && Object.assign(obj, temp); + + addEventListener("keyup", Pather.stopEvent); + togglePickThread(); + + if (obj) { + let redPortal, chestLoc, king, unit; + + switch (obj.type) { + case "area": + if (obj.dest === sdk.areas.ArreatSummit) { + Pather.moveToExit(obj.dest, false); + } else if ([ + sdk.areas.CanyonofMagic, sdk.areas.A2SewersLvl1, + sdk.areas.PalaceCellarLvl3, sdk.areas.PandemoniumFortress, sdk.areas.BloodyFoothills + ].includes(obj.dest)) { + Pather.journeyTo(obj.dest); + } else if (obj.dest === sdk.areas.DurielsLair) { + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder, -11, 3); + + for (let i = 0; i < 3; i++) { + if (Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair)) { + break; + } + } + } else { + Pather.moveToExit(obj.dest, true); + } + + break; + case "unit": + if (me.inArea(sdk.areas.MooMooFarm) + || (me.inArea(sdk.areas.DurielsLair) && Misc.talkToTyrael())) { + break; + } + + Pather.moveToUnit(obj.dest, true); + + switch (me.area) { + case sdk.areas.ColdPlains: + Pather.moveToExit(sdk.areas.CaveLvl1, true); + + break; + case sdk.areas.BlackMarsh: + Pather.moveToExit(sdk.areas.HoleLvl1, true); + + break; + case sdk.areas.LutGholein: + Pather.useUnit(sdk.unittype.Stairs, sdk.exits.preset.A2EnterSewersDoor, sdk.areas.A2SewersLvl1); + + break; + case sdk.areas.KurastBazaar: + Pather.useUnit(sdk.unittype.Stairs, sdk.exits.preset.A3EnterSewers, sdk.areas.A3SewersLvl1); + + break; + } + + if (obj.action && typeof obj.action === "object") { + if (obj.action.do === "openChest") { + !!obj.action.id && Misc.openChest(obj.action.id); + } else if (obj.action.do === "usePortal") { + !!obj.action.id ? Pather.usePortal(obj.action.id) : Pather.usePortal(); + } + } + + break; + case "wp": + Pather.getWP(me.area); + + break; + case "actChange": + console.log("Going to act: " + obj.dest); + Pather.changeAct(obj.dest); + + break; + case "portal": + if (obj.dest === sdk.areas.WorldstoneChamber && Game.getMonster(sdk.monsters.ThroneBaal)) { + me.overhead("Can't enter Worldstone Chamber yet. Baal still in area"); + + break; + } else if (obj.dest === sdk.areas.WorldstoneChamber && !Game.getMonster(sdk.monsters.ThroneBaal)) { + redPortal = Game.getObject(sdk.objects.WorldstonePortal); + redPortal && Pather.usePortal(null, null, redPortal); + + break; + } + + switch (obj.dest) { + case sdk.areas.RogueEncampment: + king = Game.getPresetMonster(me.area, sdk.monsters.preset.TheCowKing); + + switch (king.x) { + case 1: + Pather.moveTo(25183, 5923); + + break; + } + + break; + case sdk.areas.StonyField: + Pather.moveTo(25173, 5086); + redPortal = Pather.getPortal(obj.dest); + + break; + case sdk.areas.MooMooFarm: + redPortal = Pather.getPortal(obj.dest); + + break; + case sdk.areas.ArcaneSanctuary: + Pather.moveTo(12692, 5195); + redPortal = Pather.getPortal(obj.dest); + !redPortal && Pather.useWaypoint(obj.dest); + + break; + case sdk.areas.Harrogath: + Pather.moveTo(20193, 8693); + + break; + case sdk.areas.FrigidHighlands: + case sdk.areas.ArreatPlateau: + case sdk.areas.FrozenTundra: + chestLoc = Game.getPresetObject(me.area, sdk.objects.SmallSparklyChest); + + if (!chestLoc) { + break; + } + + [x, y] = portalMap[me.area][chestLoc.x]; + + Pather.moveTo(x, y); + Pather.usePortal(); + + break; + case sdk.areas.MatronsDen: + case sdk.areas.ForgottenSands: + case sdk.areas.FurnaceofPain: + case sdk.areas.UberTristram: + redPortal = Pather.getPortal(obj.dest); + + break; + default: + Pather.usePortal(obj.dest); + + break; + } + + if (redPortal) { + Pather.moveToUnit(redPortal); + Pather.usePortal(null, null, redPortal); + } + + break; + case "qol": + switch (obj.action) { + case "heal": + Town.initNPC("Heal", "heal"); + + break; + case "openStash": + Town.openStash(); + + break; + case "stashItems": + Town.stash(true, true); + + break; + case "gamble": + Config.Gamble ? Town.gamble() : me.overhead("Check your Config. Gambling is disabled."); + + break; + case "makePortal": + Pather.makePortal(); + + break; + case "takePortal": + Town.goToTown(); + + break; + + case "clear": + Attack.clear(10); - break; - case "antidote": - Town.buyPots(10, "Antidote", true, true); - - break; - case "stamina": - Town.buyPots(10, "Stamina", true, true); - - break; - } - - break; - } - } - } catch (e) { - console.error(e); - } finally { - action = false; - removeEventListener("keyup", Pather.stopEvent); - this.togglePickThread(); - } - } - - delay(20); - } + break; + case "cowportal": + Misc.openRedPortal(sdk.areas.MooMooFarm); + + break; + case "ubertrist": + Misc.openRedPortal(sdk.areas.UberTristram); + + break; + case "uberportal": + Misc.openRedPortal(); + + break; + case "filltps": + Town.fillTome(sdk.items.TomeofTownPortal); + me.cancel(); + + break; + case "moveItemFromInvoToStash": + case "moveItemFromStashToInvo": + unit = Game.getSelectedUnit(); + + switch (unit.location) { + case sdk.storage.Inventory: + Storage.Stash.CanFit(unit) && Storage.Stash.MoveTo(unit); + + break; + case sdk.storage.Stash: + Storage.Inventory.CanFit(unit) && Storage.Inventory.MoveTo(unit); + + break; + } + + break; + case "moveItemFromInvoToCube": + case "moveItemFromCubeToInvo": + unit = Game.getSelectedUnit(); + + switch (unit.location) { + case sdk.storage.Inventory: + Storage.Cube.CanFit(unit) && Storage.Cube.MoveTo(unit); + + break; + case sdk.storage.Cube: + Storage.Inventory.CanFit(unit) && Storage.Inventory.MoveTo(unit); + + break; + } + + break; + case "moveItemFromInvoToTrade": + case "moveItemFromTradeToInvo": + unit = Game.getSelectedUnit(); + + switch (unit.location) { + case sdk.storage.Inventory: + Storage.TradeScreen.CanFit(unit) && Storage.TradeScreen.MoveTo(unit); + + break; + case sdk.storage.TradeWindow: + if (Storage.Inventory.CanFit(unit)) { + Packet.itemToCursor(unit); + Storage.Inventory.MoveTo(unit); + } + + break; + } + + break; + case "pick": + Config.ManualPlayPick ? Pickit.pickItems() : Pickit.basicPickItems(); + + break; + case "sellItem": + unit = Game.getSelectedUnit(); + + if (unit.isInInventory && unit.sellable) { + try { + unit.sell(); + } catch (e) { + console.error(e); + } + } + + break; + } + + break; + case "drop": + switch (obj.action) { + case "invo": + Misc.dropItems(sdk.storage.Inventory); + + break; + case "stash": + Misc.dropItems(sdk.storage.Stash); + + break; + } + + break; + case "stack": + switch (obj.action) { + case "thawing": + Town.buyPots(10, "Thawing", true, true); + + break; + case "antidote": + Town.buyPots(10, "Antidote", true, true); + + break; + case "stamina": + Town.buyPots(10, "Stamina", true, true); + + break; + } + + break; + } + } + } catch (e) { + console.error(e); + } finally { + action = false; + removeEventListener("keyup", Pather.stopEvent); + togglePickThread(); + } + } + + delay(20); + } } diff --git a/d2bs/kolbot/libs/manualplay/threads/MapThread.js b/d2bs/kolbot/libs/manualplay/threads/MapThread.js deleted file mode 100644 index cbd484955..000000000 --- a/d2bs/kolbot/libs/manualplay/threads/MapThread.js +++ /dev/null @@ -1,282 +0,0 @@ -/** -* @filename MapThread.js -* @author theBGuy -* @credits kolton for orginal MapThread, isid0re for the box/frame style, laz for gamepacketsent event handler -* @desc MapThread used with D2BotMap.dbj -* -*/ -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("AutoMule.js"); -include("Gambling.js"); -include("CraftingSystem.js"); -include("TorchSystem.js"); -include("MuleLogger.js"); -include("UnitInfo.js"); -include("common/util.js"); -includeCommonLibs(); - -include("manualplay/MapMode.js"); -MapMode.include(); - -const Hooks = { - dashBoard: {x: 113, y: 490}, - portalBoard: {x: 12, y: 432}, - qolBoard: {x: 545, y: 490}, - resfix: {x: (me.screensize ? 0 : -160), y: (me.screensize ? 0 : -120)}, - saidMessage: false, - userAddon: false, - enabled: true, - flushed: false, - - init: function () { - let files = dopen("libs/manualplay/hooks/").getFiles(); - - Array.isArray(files) && files - .filter(file => file.endsWith(".js")) - .forEach(function (x) { - if (!isIncluded("manualplay/hooks/" + x)) { - if (!include("manualplay/hooks/" + x)) { - throw new Error("Failed to include " + "manualplay/hooks/" + x); - } - } - }); - }, - - update: function () { - while (!me.gameReady) { - delay(100); - } - - if (!this.enabled) { - this.enabled = getUIFlag(sdk.uiflags.AutoMap); - - return; - } - - ActionHooks.check(); - VectorHooks.check(); - MonsterHooks.check(); - ShrineHooks.check(); - ItemHooks.check(); - TextHooks.check(); - Hooks.flushed = false; - }, - - flush: function (flag) { - if (Hooks.flushed === flag) return true; - - if (flag === true) { - this.enabled = false; - - MonsterHooks.flush(); - ShrineHooks.flush(); - TextHooks.flush(); - VectorHooks.flush(); - ActionHooks.flush(); - ItemHooks.flush(); - } else { - if (sdk.uiflags.Waypoint === flag) { - VectorHooks.flush(); - TextHooks.displaySettings = false; - TextHooks.check(); - } else if (sdk.uiflags.Inventory === flag && [sdk.uiflags.stash, sdk.uiflags.Cube, sdk.uiflags.TradePrompt].every((el) => !getUIFlag(el))) { - ItemHooks.flush(); - TextHooks.check(); - } else { - MonsterHooks.flush(); - ShrineHooks.flush(); - TextHooks.flush(); - VectorHooks.flush(); - ActionHooks.flush(); - ItemHooks.flush(); - } - } - - Hooks.flushed = flag; - - return true; - } -}; - -function main() { - print("ÿc9Map Thread Loaded."); - Config.init(false); - Storage.Init(); - Pickit.init(true); - Hooks.init(); - - if (Config.MapMode.UseOwnItemFilter) { - ItemHooks.pickitEnabled = true; - } - - const Worker = require("../../modules/Worker"); - - Worker.runInBackground.unitInfo = function () { - if (!Hooks.userAddon || (!UnitInfo.cleared && !Game.getSelectedUnit())) { - UnitInfo.remove(); - return true; - } - - let unit = Game.getSelectedUnit(); - !!unit && UnitInfo.createInfo(unit); - - return true; - }; - - const hideFlags = [ - sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow, sdk.uiflags.ChatBox, - sdk.uiflags.EscMenu, sdk.uiflags.Shop, sdk.uiflags.Quest, sdk.uiflags.Waypoint, sdk.uiflags.TradePrompt, sdk.uiflags.Msgs, - sdk.uiflags.Stash, sdk.uiflags.Cube, sdk.uiflags.Help, sdk.uiflags.MercScreen - ]; - - this.revealArea = function (area) { - !this.revealedAreas && (this.revealedAreas = []); - - if (this.revealedAreas.indexOf(area) === -1) { - delay(500); - - if (!getRoom()) { - return; - } - - revealLevel(true); - this.revealedAreas.push(area); - } - }; - - // Run commands from chat - this.runCommand = function (msg) { - if (msg.length <= 1) { - return true; - } - - msg = msg.toLowerCase(); - let cmd = msg.split(" ")[0].split(".")[1]; - let msgList = msg.split(" "); - let qolObj = {type: "qol", dest: false, action: false}; - - switch (cmd) { - case "useraddon": - Hooks.userAddon = !Hooks.userAddon; - me.overhead("userAddon set to " + Hooks.userAddon); - - break; - case "me": - print("Character Level: " + me.charlvl + " | Area: " + me.area + " | x: " + me.x + ", y: " + me.y); - me.overhead("Character Level: " + me.charlvl + " | Area: " + me.area + " | x: " + me.x + ", y: " + me.y); - - break; - case "stash": - me.inTown && (qolObj.action = "stashItems"); - - break; - case "pick": - case "cowportal": - case "uberportal": - case "filltps": - qolObj.action = cmd; - - break; - case "drop": - if (msgList.length < 2) { - print("ÿc1Missing arguments"); - break; - } - - qolObj.type = "drop"; - qolObj.action = msgList[1]; - - break; - case "stack": - if (msgList.length < 2) { - print("ÿc1Missing arguments"); - break; - } - - qolObj.type = "stack"; - qolObj.action = msgList[1]; - - break; - case "help": - if (HelpMenu.cleared) { - HelpMenu.showMenu(); - me.overhead("Click each command for more info"); - } - - break; - case "hide": - hideConsole(); - HelpMenu.hideMenu(); - TextHooks.displayTitle = false; - { - let tHook = TextHooks.getHook("title", TextHooks.hooks); - !!tHook && tHook.hook.remove(); - } - - break; - case "make": - if (!FileTools.exists("libs/manualplay/config/" + sdk.player.class.nameOf(me.classid) + "." + me.name + ".js")) { - FileTools.copy("libs/manualplay/config/" + sdk.player.class.nameOf(me.classid) + ".js", "libs/manualplay/config/" + sdk.player.class.nameOf(me.classid) + "." + me.name + ".js"); - D2Bot.printToConsole("libs/manualplay/config/" + sdk.player.class.nameOf(me.classid) + "." + me.name + ".js has been created. Configure the bot and reload to apply changes"); - print("libs/manualplay/config/" + sdk.player.class.nameOf(me.classid) + "." + me.name + ".js has been created. Configure the bot and reload to apply changes"); - me.overhead("libs/manualplay/config/" + sdk.player.class.nameOf(me.classid) + "." + me.name + ".js has been created. Configure the bot and reload to apply changes"); - } - - break; - default: - print("ÿc1Invalid command : " + cmd); - - break; - } - - qolObj.action && Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(qolObj)); - - return true; - }; - - let onChatInput = (speaker, msg) => { - if (msg.length && msg[0] === ".") { - this.runCommand(msg); - - return true; - } - - return false; - }; - - addEventListener("chatinputblocker", onChatInput); - addEventListener("keyup", ActionHooks.event); - - while (true) { - while (!me.area || !me.gameReady) { - delay(100); - } - - let hideFlagFound = false; - - this.revealArea(me.area); - - for (let i = 0; i < hideFlags.length; i++) { - if (getUIFlag(hideFlags[i])) { - Hooks.flush(hideFlags[i]); - ActionHooks.checkAction(); - hideFlagFound = true; - delay(100); - - break; - } - } - - if (hideFlagFound) continue; - - getUIFlag(sdk.uiflags.AutoMap) ? Hooks.update() : Hooks.flush(true) && (!HelpMenu.cleared && HelpMenu.hideMenu()); - - delay(20); - - while (getUIFlag(sdk.uiflags.ShowItem)) { - ItemHooks.flush(); - } - } -} diff --git a/d2bs/kolbot/libs/manualplay/threads/MapToolsThread.js b/d2bs/kolbot/libs/manualplay/threads/MapToolsThread.js index 2d303f60b..e37703df0 100644 --- a/d2bs/kolbot/libs/manualplay/threads/MapToolsThread.js +++ b/d2bs/kolbot/libs/manualplay/threads/MapToolsThread.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /** * @filename MapToolsThread.js * @author theBGuy @@ -6,285 +7,292 @@ * */ js_strict(true); +include("critical.js"); // required -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("AutoMule.js"); -include("Gambling.js"); -include("CraftingSystem.js"); -include("TorchSystem.js"); -include("MuleLogger.js"); -include("common/util.js"); +// globals needed for core gameplay +includeCoreLibs(); +include("core/Common/Tools.js"); -includeCommonLibs(); +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); // MapMode include("manualplay/MapMode.js"); MapMode.include(); -function main() { - let ironGolem, debugInfo = {area: 0, currScript: "no entry"}; - let quitFlag = false; - let quitListDelayTime; - let canQuit = true; - - console.log("ÿc9MapToolsThread loaded"); - D2Bot.init(); - Config.init(false); - Pickit.init(false); - Attack.init(); - Storage.Init(); - CraftingSystem.buildLists(); - Runewords.init(); - Cubing.init(); - - for (let i = 0; i < 5; i += 1) { - Common.Toolsthread.timerLastDrink[i] = 0; - } - - // Reset core chicken - me.chickenhp = -1; - me.chickenmp = -1; - - // General functions - Common.Toolsthread.pauseScripts = [ - "default.dbj", "tools/townchicken.js", "libs/manualplay/threads/pickthread.js", - "tools/antihostile.js", "tools/party.js", "libs/manualplay/threads/maphelper.js", - ]; - Common.Toolsthread.stopScripts = [ - "default.dbj", "tools/townchicken.js", "libs/manualplay/threads/pickthread.js", - "tools/antihostile.js", "tools/party.js", "libs/manualplay/threads/maphelper.js", - ]; - - // Event functions - this.keyEvent = function (key) { - switch (key) { - case sdk.keys.PauseBreak: // pause default.dbj - Common.Toolsthread.togglePause(); - - break; - case sdk.keys.Delete: // quit current game - Common.Toolsthread.exit(); - - break; - case sdk.keys.End: // stop profile and log character - MuleLogger.logChar(); - delay(rand(Time.seconds(Config.QuitListDelay[0]), Time.seconds(Config.QuitListDelay[1]))); - D2Bot.printToConsole(me.profile + " - end run " + me.gamename); - D2Bot.stop(me.profile, true); - - break; - case sdk.keys.NumpadPlus: // log stats - showConsole(); - - console.log("ÿc8My stats :: " + Common.Toolsthread.getStatsString(me)); - let merc = me.getMerc(); - !!merc && console.log("ÿc8Merc stats :: " + Common.Toolsthread.getStatsString(merc)); - - break; - case sdk.keys.NumpadDecimal: - MuleLogger.logChar(); - me.overhead("Logged char: " + me.name); - - break; - case sdk.keys.NumpadSlash: // re-load default - console.log("ÿc8ToolsThread :: " + sdk.colors.Red + "Stopping threads and waiting 5 seconds to restart"); - Common.Toolsthread.stopDefault() && delay(Time.seconds(5)); - console.log("Starting default.dbj"); - load("default.dbj"); - - break; - case sdk.keys.NumpadStar: // precast - { - let preSkill = me.getSkill(sdk.skills.get.RightId); - - Precast.doPrecast(true); - Skill.setSkill(preSkill, sdk.skills.hand.Right); - } - - break; - } - }; - - this.gameEvent = function (mode, param1, param2, name1, name2) { - switch (mode) { - case 0x00: // "%Name1(%Name2) dropped due to time out." - case 0x01: // "%Name1(%Name2) dropped due to errors." - case 0x03: // "%Name1(%Name2) left our world. Diablo's minions weaken." - if ((typeof Config.QuitList === "string" && Config.QuitList.toLowerCase() === "any") - || (Array.isArray(Config.QuitList) && Config.QuitList.includes(name1))) { - print(name1 + (mode === 0 ? " timed out" : " left")); - - if (typeof Config.QuitListDelay !== "undefined" && typeof quitListDelayTime === "undefined" && Config.QuitListDelay.length > 0) { - Config.QuitListDelay.sort((a, b) => a - b); - quitListDelayTime = getTickCount() + rand(Config.QuitListDelay[0] * 1e3, Config.QuitListDelay[1] * 1e3); - } else { - quitListDelayTime = getTickCount(); - } - - quitFlag = true; - } - - if (Config.AntiHostile) { - scriptBroadcast("remove " + name1); - } - - break; - case 0x06: // "%Name1 was Slain by %Name2" - if (Config.AntiHostile && param2 === 0x00 && name2 === me.name) { - scriptBroadcast("mugshot " + name1); - } - - break; - case 0x07: - if (Config.AntiHostile && param2 === 0x03) { // "%Player has declared hostility towards you." - scriptBroadcast("findHostiles"); - } - - break; - case 0x11: // "%Param1 Stones of Jordan Sold to Merchants" - if (Config.DCloneQuit === 2) { - D2Bot.printToConsole("SoJ sold in game. Leaving."); - - quitFlag = true; - - break; - } - - if (Config.SoJWaitTime && me.expansion) { - !!me.realm && D2Bot.printToConsole(param1 + " Stones of Jordan Sold to Merchants on IP " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold); - Messaging.sendToScript("default.dbj", "soj"); - } - - break; - case 0x12: // "Diablo Walks the Earth" - me.expansion && !!me.realm && D2Bot.printToConsole("Diablo Walks the Earth. " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold); - - break; - } - }; - - this.scriptEvent = function (msg) { - switch (msg) { - case "toggleQuitlist": - canQuit = !canQuit; - - break; - case "quit": - quitFlag = true; - - break; - } - }; - - // Cache variables to prevent a bug where d2bs loses the reference to Config object - Config = Misc.copy(Config); - let tick = getTickCount(); - - addEventListener("keyup", this.keyEvent); - addEventListener("gameevent", this.gameEvent); - addEventListener("scriptmsg", this.scriptEvent); - - // Load Fastmod - // Packet.changeStat(105, Config.FCR); - // Packet.changeStat(99, Config.FHR); - // Packet.changeStat(102, Config.FBR); - // Packet.changeStat(93, Config.IAS); - - Config.QuitListMode > 0 && Common.Toolsthread.initQuitList(); - - // Start - while (true) { - try { - if (me.gameReady && !me.inTown) { - Config.UseHP > 0 && me.hpPercent < Config.UseHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Health); - Config.UseRejuvHP > 0 && me.hpPercent < Config.UseRejuvHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); - - if (Config.LifeChicken > 0 && me.hpPercent <= Config.LifeChicken) { - // takes a moment sometimes for townchicken to actually get to town so re-check that we aren't in town before quitting - if (!me.inTown) { - D2Bot.printToConsole("Life Chicken (" + me.hp + "/" + me.hpmax + ")" + Attack.getNearestMonster() + " in " + Pather.getAreaName(me.area) + ". Ping: " + me.ping, sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - } - - Config.UseMP > 0 && me.mpPercent < Config.UseMP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Mana); - Config.UseRejuvMP > 0 && me.mpPercent < Config.UseRejuvMP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); - - if (Config.ManaChicken > 0 && me.mpPercent <= Config.ManaChicken) { - D2Bot.printToConsole("Mana Chicken: (" + me.mp + "/" + me.mpmax + ") in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - - if (Config.IronGolemChicken > 0 && me.necromancer) { - if (!ironGolem || copyUnit(ironGolem).x === undefined) { - ironGolem = Common.Toolsthread.getIronGolem(); - } - - if (ironGolem) { - // ironGolem.hpmax is bugged with BO - if (ironGolem.hp <= Math.floor(128 * Config.IronGolemChicken / 100)) { - D2Bot.printToConsole("Irom Golem Chicken in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - } - } - - if (Config.UseMerc) { - let merc = me.getMerc(); - if (!!merc) { - let mercHP = getMercHP(); - - if (mercHP > 0 && merc.mode !== sdk.monsters.mode.Dead) { - if (mercHP < Config.MercChicken) { - D2Bot.printToConsole("Merc Chicken in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - - mercHP < Config.UseMercHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercHealth); - mercHP < Config.UseMercRejuv && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercRejuv); - } - } - } - - if (Config.ViperCheck && getTickCount() - tick >= 250) { - Common.Toolsthread.checkVipers() && (quitFlag = true); - - tick = getTickCount(); - } - - Common.Toolsthread.checkPing(true) && (quitFlag = true); - } - } catch (e) { - Misc.errorReport(e, "MapToolsThread"); - takeScreenshot(); - - quitFlag = true; - } - - if (quitFlag && canQuit && (typeof quitListDelayTime === "undefined" || getTickCount() >= quitListDelayTime)) { - Common.Toolsthread.checkPing(false); // In case of quitlist triggering first - Common.Toolsthread.exit(); - - break; - } - - if (debugInfo.area !== Pather.getAreaName(me.area)) { - debugInfo.area = Pather.getAreaName(me.area); - DataFile.updateStats("debugInfo", JSON.stringify(debugInfo)); - } - - delay(20); - } - - return true; +function main () { + // getUnit test + getUnit(-1) === null && console.warn("getUnit bug detected"); + + console.log("ÿc9MapToolsThread loaded"); + + let ironGolem, debugInfo = { area: 0, currScript: "no entry" }; + let quitFlag = false; + let quitListDelayTime; + let canQuit = true; + + D2Bot.init(); + Config.init(false); + Pickit.init(false); + Attack.init(); + Storage.Init(); + CraftingSystem.buildLists(); + Runewords.init(); + Cubing.init(); + + for (let i = 0; i < 5; i += 1) { + Common.Toolsthread.timerLastDrink[i] = 0; + } + + // Reset core chicken + me.chickenhp = -1; + me.chickenmp = -1; + + // General functions + Common.Toolsthread.pauseScripts = [ + "libs/manualplay/main.js", + "libs/manualplay/threads/pickthread.js", + "libs/manualplay/threads/maphelper.js", + "threads/antihostile.js", + "threads/party.js", + ]; + Common.Toolsthread.stopScripts = [ + "libs/manualplay/main.js", + "libs/manualplay/threads/pickthread.js", + "libs/manualplay/threads/maphelper.js", + "threads/antihostile.js", + "threads/party.js", + ]; + + // Event functions + const keyEvent = function (key) { + switch (key) { + case sdk.keys.PauseBreak: // pause main.dbj + Common.Toolsthread.togglePause(); + + break; + case sdk.keys.Delete: // quit current game + Common.Toolsthread.exit(); + + break; + case sdk.keys.End: // stop profile and log character + MuleLogger.logChar(); + delay(rand(Time.seconds(Config.QuitListDelay[0]), Time.seconds(Config.QuitListDelay[1]))); + D2Bot.printToConsole(me.profile + " - end run " + me.gamename); + D2Bot.stop(me.profile, true); + + break; + case sdk.keys.NumpadPlus: // log stats + showConsole(); + + console.log("ÿc8My stats :: " + Common.Toolsthread.getStatsString(me)); + let merc = me.getMerc(); + !!merc && console.log("ÿc8Merc stats :: " + Common.Toolsthread.getStatsString(merc)); + + break; + case sdk.keys.NumpadDecimal: + MuleLogger.logChar(); + me.overhead("Logged char: " + me.name); + + break; + case sdk.keys.NumpadSlash: // re-load default + console.log("ÿc8ToolsThread :: " + sdk.colors.Red + "Stopping threads and waiting 5 seconds to restart"); + Common.Toolsthread.stopDefault() && delay(Time.seconds(5)); + console.log("Starting default.dbj"); + load("default.dbj"); + + break; + case sdk.keys.NumpadStar: // precast + { + let preSkill = me.getSkill(sdk.skills.get.RightId); + + Precast.doPrecast(true); + Skill.setSkill(preSkill, sdk.skills.hand.Right); + } + + break; + } + }; + + const gameEvent = function (mode, param1, param2, name1, name2) { + switch (mode) { + case 0x00: // "%Name1(%Name2) dropped due to time out." + case 0x01: // "%Name1(%Name2) dropped due to errors." + case 0x03: // "%Name1(%Name2) left our world. Diablo's minions weaken." + if (Config.QuitList.includes(name1) || Config.QuitList.some(str => String.isEqual(str, "all"))) { + console.log(name1 + (mode === 0 ? " timed out" : " left")); + + if (typeof quitListDelayTime === "undefined" && Config.QuitListDelay.length > 0) { + let [min, max] = Config.QuitListDelay.sort((a, b) => a - b).map(s => Time.seconds(s)); + + quitListDelayTime = getTickCount() + rand(min, max); + } else { + quitListDelayTime = getTickCount(); + } + + quitFlag = true; + } + + if (Config.AntiHostile) { + scriptBroadcast("remove " + name1); + } + + break; + case 0x06: // "%Name1 was Slain by %Name2" + if (Config.AntiHostile && param2 === 0x00 && name2 === me.name) { + scriptBroadcast("mugshot " + name1); + } + + break; + case 0x07: + if (Config.AntiHostile && param2 === 0x03) { // "%Player has declared hostility towards you." + scriptBroadcast("findHostiles"); + } + + break; + case 0x11: // "%Param1 Stones of Jordan Sold to Merchants" + if (Config.DCloneQuit === 2) { + D2Bot.printToConsole("SoJ sold in game. Leaving."); + + quitFlag = true; + + break; + } + + if (Config.SoJWaitTime && me.expansion) { + !!me.realm && D2Bot.printToConsole(param1 + " Stones of Jordan Sold to Merchants on IP " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold); + // Messaging.sendToScript("default.dbj", "soj"); + } + + break; + case 0x12: // "Diablo Walks the Earth" + me.expansion && !!me.realm && D2Bot.printToConsole("Diablo Walks the Earth. " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold); + + break; + } + }; + + const scriptEvent = function (msg) { + switch (msg) { + case "toggleQuitlist": + canQuit = !canQuit; + + break; + case "quit": + quitFlag = true; + + break; + } + }; + + // Cache variables to prevent a bug where d2bs loses the reference to Config object + Config = copyObj(Config); + let tick = getTickCount(); + + addEventListener("keyup", keyEvent); + addEventListener("gameevent", gameEvent); + addEventListener("scriptmsg", scriptEvent); + + Config.QuitListMode > 0 && Common.Toolsthread.initQuitList(); + !Array.isArray(Config.QuitList) && (Config.QuitList = [Config.QuitList]); // make it an array for simpler checks + + // Start + while (true) { + try { + if (me.gameReady && !me.inTown) { + Config.UseHP > 0 && me.hpPercent < Config.UseHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Health); + Config.UseRejuvHP > 0 && me.hpPercent < Config.UseRejuvHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); + + if (Config.LifeChicken > 0 && me.hpPercent <= Config.LifeChicken) { + // takes a moment sometimes for townchicken to actually get to town so re-check that we aren't in town before quitting + if (!me.inTown) { + D2Bot.printToConsole("Life Chicken (" + me.hp + "/" + me.hpmax + ")" + Attack.getNearestMonster() + " in " + getAreaName(me.area) + ". Ping: " + me.ping, sdk.colors.D2Bot.Red); + Common.Toolsthread.exit(true); + + break; + } + } + + Config.UseMP > 0 && me.mpPercent < Config.UseMP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Mana); + Config.UseRejuvMP > 0 && me.mpPercent < Config.UseRejuvMP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); + + if (Config.ManaChicken > 0 && me.mpPercent <= Config.ManaChicken) { + D2Bot.printToConsole("Mana Chicken: (" + me.mp + "/" + me.mpmax + ") in " + getAreaName(me.area), sdk.colors.D2Bot.Red); + Common.Toolsthread.exit(true); + + break; + } + + if (Config.IronGolemChicken > 0 && me.necromancer) { + if (!ironGolem || copyUnit(ironGolem).x === undefined) { + ironGolem = Common.Toolsthread.getIronGolem(); + } + + if (ironGolem) { + // ironGolem.hpmax is bugged with BO + if (ironGolem.hp <= Math.floor(128 * Config.IronGolemChicken / 100)) { + D2Bot.printToConsole("Irom Golem Chicken in " + getAreaName(me.area), sdk.colors.D2Bot.Red); + Common.Toolsthread.exit(true); + + break; + } + } + } + + if (Config.UseMerc) { + let merc = me.getMerc(); + if (!!merc) { + let mercHP = getMercHP(); + + if (mercHP > 0 && merc.mode !== sdk.monsters.mode.Dead) { + if (mercHP < Config.MercChicken) { + D2Bot.printToConsole("Merc Chicken in " + getAreaName(me.area), sdk.colors.D2Bot.Red); + Common.Toolsthread.exit(true); + + break; + } + + mercHP < Config.UseMercHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercHealth); + mercHP < Config.UseMercRejuv && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercRejuv); + } + } + } + + if (Config.ViperCheck && getTickCount() - tick >= 250) { + Common.Toolsthread.checkVipers() && (quitFlag = true); + + tick = getTickCount(); + } + + Common.Toolsthread.checkPing(true) && (quitFlag = true); + } + } catch (e) { + Misc.errorReport(e, "MapToolsThread"); + takeScreenshot(); + + quitFlag = true; + } + + if (quitFlag && canQuit) { + if (typeof quitListDelayTime !== "undefined" && getTickCount() < quitListDelayTime) { + me.overhead("Quitting in " + Math.round((quitListDelayTime - getTickCount()) / 1000) + " Seconds"); + continue; + } + Common.Toolsthread.checkPing(false); // In case of quitlist triggering first + Common.Toolsthread.exit(); + + break; + } + + if (debugInfo.area !== getAreaName(me.area)) { + debugInfo.area = getAreaName(me.area); + DataFile.updateStats("debugInfo", JSON.stringify(debugInfo)); + } + + delay(20); + } + + return true; } diff --git a/d2bs/kolbot/libs/manualplay/threads/PickThread.js b/d2bs/kolbot/libs/manualplay/threads/PickThread.js index 8fdee6a94..e265a76bc 100644 --- a/d2bs/kolbot/libs/manualplay/threads/PickThread.js +++ b/d2bs/kolbot/libs/manualplay/threads/PickThread.js @@ -6,50 +6,56 @@ */ js_strict(true); -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("CraftingSystem.js"); -include("common/util.js"); +include("json2.js"); // required? +include("polyfill.js"); // required +include("oog/D2Bot.js"); // required -includeCommonLibs(); +// globals needed for core gameplay +// todo - figure out what here is actually needed for mapmode vs what is only required for bot mode +include("core/NTItemParser.js"); +include("core/Util"); +includeCoreLibs(); + +// system libs - same for here +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); // MapMode include("manualplay/MapMode.js"); MapMode.include(); -function main() { - print("ÿc9Pick Thread Loaded."); - Config.init(false); - Pickit.init(false); - Attack.init(); - Storage.Init(); - CraftingSystem.buildLists(); - Runewords.init(); - Cubing.init(); - - let noPick = false; - const UIFlagList = [ - sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow, - sdk.uiflags.ChatBox, sdk.uiflags.EscMenu, sdk.uiflags.ConfigControls, sdk.uiflags.SubmitItem, - sdk.uiflags.Quest, sdk.uiflags.Waypoint, sdk.uiflags.Party, sdk.uiflags.Cube, sdk.uiflags.MercScreen - ]; - - addEventListener("itemaction", Pickit.itemEvent); - - while (true) { - for (let i = 0; i < UIFlagList.length; i++) { - if (getUIFlag(UIFlagList[i])) { - noPick = true; - break; - } - } - - if (!me.inTown && !noPick && !me.itemoncursor && Pickit.gidList.length > 0) { - Pickit.fastPick(1); - } - - noPick = false; - delay(100); - } +function main () { + console.log("ÿc9Pick Thread Loaded."); + Config.init(false); + Pickit.init(false); + Attack.init(); + Storage.Init(); + CraftingSystem.buildLists(); + Runewords.init(); + Cubing.init(); + + let noPick = false; + const UIFlagList = [ + sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow, + sdk.uiflags.ChatBox, sdk.uiflags.EscMenu, sdk.uiflags.ConfigControls, sdk.uiflags.SubmitItem, + sdk.uiflags.Quest, sdk.uiflags.Waypoint, sdk.uiflags.Party, sdk.uiflags.Cube, sdk.uiflags.MercScreen + ]; + + addEventListener("itemaction", Pickit.itemEvent); + + while (true) { + for (let i = 0; i < UIFlagList.length; i++) { + if (getUIFlag(UIFlagList[i])) { + noPick = true; + break; + } + } + + if (!me.inTown && !noPick && !me.itemoncursor && Pickit.gidList.size > 0) { + Pickit.fastPick(1); + } + + noPick = false; + delay(100); + } } diff --git a/d2bs/kolbot/libs/modules/Control.js b/d2bs/kolbot/libs/modules/Control.js index 68db3598c..3755c4002 100644 --- a/d2bs/kolbot/libs/modules/Control.js +++ b/d2bs/kolbot/libs/modules/Control.js @@ -3,193 +3,318 @@ * @author Jaenster, theBGuy(added the rest of the controls) */ (function (module) { + /** + * Not callable as a function + * @constructor + * @method Control.click(targetx, targety) + * @method Control.setText(text) + * @method Control.getText(text) + * @param {number} type + * @param {number} x + * @param {number} y + * @param {number} xsize + * @param {number} ysize + */ + function Control (type, x, y, xsize, ysize) { + /** + * @private + * @type {number} + */ + this.type = type; + + /** + * @private + * @type {number} + */ + this.x = x; + + /** + * @private + * @type {number} + */ + this.y = y; - /** - * @constructor - Not callable as a function - * - * @method Control.click(targetx, targety) - * @method Control.setText(text) - * @method Control.getText(text) + /** + * @private + * @type {number} */ - function Control(type, x, y, xsize, ysize) { - this.type = type; - this.x = x; - this.y = y; - this.xsize = xsize; - this.ysize = ysize; - - return new Proxy(this, { - get: function (target, p) { - const passthroughFunc = ["click", "setText", "getText"]; - - if (p === "valueOf") { - return target; - } - - const control = getControl(target.type, target.x, target.y, target.xsize, target.ysize); - if (p === "control") { - return control; - } - - // Relay on old ControlAction functions - if (passthroughFunc.indexOf(p) !== -1) { - return (...args) => ControlAction[p].apply(ControlAction, [target.type, target.x, target.y, target.xsize, target.ysize].concat(args)); - } - - // if control is found, and it's a property of the control - if (typeof control === "object" && control && control.hasOwnProperty(p)) { - return control[p]; - } - - // The target has it - if (target.hasOwnProperty(p)) { - return target[p]; - } - - return null; - } - }); - - } - - Control.SplashScreen = new Control(sdk.controls.TextBox, 0, 599, 800, 600); - Control.D2SplashCopyright = new Control(sdk.controls.LabelBox, 100, 580, 600, 80); - Control.MainMenuD2Version = new Control(sdk.controls.LabelBox, 0, 599, 200, 40); - Control.MainMenuCredits = new Control(sdk.controls.Button, 264, 528, 135, 25); - Control.MainMenuCinematics = new Control(sdk.controls.Button, 402, 528, 135, 25); - Control.MainMenuExit = new Control(sdk.controls.Button, 264, 568, 272, 35); - - Control.SinglePlayer = new Control(-1, 264, 324, 272, 35); - Control.BattleNet = new Control(-1, 264, 366, 272, 35); - Control.Gateway = new Control(sdk.controls.Button, 264, 391, 272, 25); - Control.OtherMultiplayer = new Control(-1, 264, 433, 272, 35); - - Control.Login = new Control(-1, 264, 484, 272, 35); - Control.LoginHeading = new Control(sdk.controls.LabelBox, 200, 350, 400, 100); - Control.LoginUsername = new Control(-1, 322, 342, 162, 19); - Control.LoginPassword = new Control(-1, 322, 396, 162, 19); - Control.LoginErrorOk = new Control(sdk.controls.Button, 335, 412, 128, 35); - Control.LoginCancelWait = new Control(sdk.controls.Button, 330, 416, 128, 35); - Control.LoginInvalidCdKey = new Control(sdk.controls.LabelBox, 162, 270, 477, 50); - Control.LoginCdKeyInUseBy = new Control(sdk.controls.LabelBox, 158, 310, 485, 40); - Control.LoginUnableToConnect = new Control(sdk.controls.LabelBox, 158, 220, 485, 40); - Control.LoginErrorText = new Control(sdk.controls.LabelBox, 199, 377, 402, 140); - Control.LoginAccountSettings = new Control(sdk.controls.Button, 264, 528, 272, 35); - Control.LoginExit = new Control(sdk.controls.Button, 33, 572, 128, 35); - - Control.UnableToConnectOk = new Control(sdk.controls.Button, 335, 450, 128, 35); - - Control.OpenBattleNet = new Control(-1, 264, 310, 272, 35); - Control.TcpIp = new Control(-1, 264, 350, 272, 35); - Control.OtherMultiplayerCancel = new Control(sdk.controls.Button, 264, 568, 272, 35); - - Control.GatewayOk = new Control(sdk.controls.Button, 281, 538, 96, 32); - Control.GatewayCancel = new Control(sdk.controls.Button, 436, 538, 96, 32); - - Control.TcpIpHost = new Control(-1, 265, 206, 272, 35); - Control.TcpIpJoin = new Control(-1, 265, 264, 272, 35); - Control.TcpIpCancel = new Control(sdk.controls.Button, 39, 571, 128, 35); - - Control.IPAdress = new Control(-1, 300, 268, -1, -1); - Control.IPAdressOk = new Control(-1, 421, 337, 96, 32); - - Control.CreateNewAccount = new Control(sdk.controls.Button, 264, 572, 272, 35); - Control.CreateNewAccountName = new Control(sdk.controls.TextBox, 322, 342, 162, 19); - Control.CreateNewAccountPassword = new Control(sdk.controls.TextBox, 322, 396, 162, 19); - Control.CreateNewAccountConfirmPassword = new Control(sdk.controls.TextBox, 322, 450, 162, 19); - Control.CreateNewAccountOk = new Control(sdk.controls.Button, 627, 572, 128, 35); - Control.CreateNewAccountExit = new Control(sdk.controls.Button, 33, 572, 128, 35); - - Control.TermsOfUseAgree = new Control(sdk.controls.Button, 525, 513, 128, 35); - Control.TermsOfUseDisagree = new Control(sdk.controls.Button, 133, 513, 128, 35); - - Control.PleaseReadOk = new Control(sdk.controls.Button, 525, 513, 128, 35); - Control.PleaseReadCancel = new Control(sdk.controls.Button, 133, 513, 128, 35); - - Control.EmailSetEmail = new Control(sdk.controls.TextBox, 253, 342, 293, 19); - Control.EmailVerifyEmail = new Control(sdk.controls.TextBox, 253, 396, 293, 19); - Control.EmailRegister = new Control(sdk.controls.Button, 265, 527, 272, 35); - Control.EmailDontRegister = new Control(sdk.controls.Button, 265, 572, 272, 35); - Control.EmailDontRegisterContinue = new Control(sdk.controls.Button, 415, 412, 128, 35); - - Control.CharSelectCreate = new Control(sdk.controls.Button, 33, 528, 168, 60); - Control.CharSelectExit = new Control(sdk.controls.Button, 33, 572, 128, 35); - Control.CharSelectDelete = new Control(sdk.controls.Button, 433, 528, 168, 60); - Control.CharSelectConvert = new Control(sdk.controls.Button, 233, 528, 168, 60); - Control.CharDeleteYes = new Control(sdk.controls.Button, 421, 337, 96, 32); - Control.CharSelectError = new Control(sdk.controls.LabelBox, 45, 318, 531, 140); - Control.CharSelectCharInfo0 = new Control(sdk.controls.LabelBox, 37, 178, 200, 92); - Control.CharSelectChar4 = new Control(sdk.controls.LabelBox, 237, 364, 72, 93); - Control.CharSelectChar6 = new Control(sdk.controls.LabelBox, 237, 457, 72, 93); - Control.CharSelectCurrentRealm = new Control(sdk.controls.LabelBox, 626, 100, 151, 44); - - Control.CharCreateCharName = new Control(sdk.controls.TextBox, 318, 510, 157, 16); - Control.CharCreateExpansion = new Control(sdk.controls.Button, 319, 540, 15, 16); - Control.CharCreateLadder = new Control(sdk.controls.Button, 319, 580, 15, 16); - Control.CharCreateHardcore = new Control(sdk.controls.Button, 319, 560, 15, 16); - Control.CharCreateHCWarningOk = new Control(sdk.controls.Button, 421, 337, 96, 32); - Control.CharCreateHCWarningCancel = new Control(sdk.controls.Button, 281, 337, 96, 32); - - Control.SinglePlayerNormal = new Control(-1, 264, 297, 272, 35); - Control.SinglePlayerNightmare = new Control(-1, 264, 340, 272, 35); - Control.SinglePlayerHell = new Control(-1, 264, 383, 272, 35); - - Control.LobbyCharacterInfo = new Control(sdk.controls.LabelBox, 143, 588, 230, 87); - Control.LobbyEnterChat = new Control(sdk.controls.Button, 27, 480, 120, 20); - Control.LobbyLadder = new Control(sdk.controls.Button, 614, 490, 80, 20); - Control.LobbyHelp = new Control(sdk.controls.Button, 146, 480, 120, 20); - Control.LobbyQuit = new Control(sdk.controls.Button, 693, 490, 80, 20); - - Control.JoinGameWindow = new Control(sdk.controls.Button, 652, 469, 120, 20); - Control.JoinGame = new Control(sdk.controls.Button, 594, 433, 172, 32); - Control.JoinGameName = new Control(sdk.controls.TextBox, 432, 148, 155, 20); - Control.JoinGamePass = new Control(sdk.controls.TextBox, 606, 148, 155, 20); - Control.JoinGameList = new Control(sdk.controls.LabelBox, 432, 393, 160, 173); - Control.JoinGameDetails = new Control(sdk.controls.LabelBox, 609, 393, 143, 194); - Control.CancelJoinGame = new Control(sdk.controls.Button, 433, 433, 96, 32); - - Control.CreateGameWindow = new Control(sdk.controls.Button, 533, 469, 120, 20); - Control.CreateGame = new Control(sdk.controls.Button, 594, 433, 172, 32); - Control.CreateGameName = new Control(sdk.controls.TextBox, 432, 162, 158, 20); - Control.CreateGamePass = new Control(sdk.controls.TextBox, 432, 217, 158, 20); - Control.CharacterDifferenceButton = new Control(sdk.controls.Button, 431, 341, 15, 16); - Control.CharacterDifference = new Control(sdk.controls.TextBox, 657, 342, 27, 20); - Control.MaxPlayerCount = new Control(sdk.controls.TextBox, 657, 308, 27, 20); - Control.Normal = new Control(sdk.controls.Button, 430, 381, 16, 16); - Control.Nightmare = new Control(sdk.controls.Button, 555, 381, 16, 16); - Control.Hell = new Control(sdk.controls.Button, 698, 381, 16, 16); - Control.CreateGameInLine = new Control(sdk.controls.LabelBox, 427, 234, 300, 100); - Control.CancelCreateGame = new Control(sdk.controls.Button, 433, 433, 96, 32); - - Control.LobbyChannel = new Control(sdk.controls.Button, 535, 490, 80, 20); - Control.LobbyChannelName = new Control(sdk.controls.LabelBox, 28, 138, 354, 60); - Control.LobbyChannelText = new Control(sdk.controls.TextBox, 432, 162, 155, 20); - Control.LobbyChannelOk = new Control(sdk.controls.Button, 671, 433, 96, 32); - Control.LobbyChannelCancel = new Control(sdk.controls.Button, 433, 433, 96, 32); - Control.LobbyChannelSend = new Control(sdk.controls.Button, 27, 470, 80, 20); - Control.LobbyChannelWhisper = new Control(sdk.controls.Button, 107, 470, 80, 20); - Control.LobbyChannelSquelch = new Control(sdk.controls.Button, 27, 490, 72, 20); - Control.LobbyChannelUnsquelch = new Control(sdk.controls.Button, 99, 490, 96, 20); - Control.LobbyChannelEmote = new Control(sdk.controls.Button, 195, 490, 72, 20); - Control.LobbyChannelChar0 = new Control(sdk.controls.Button, 40, 591, 60, 100); - Control.LobbyChannelChar1 = new Control(sdk.controls.Button, 100, 591, 60, 100); - Control.LobbyChannelChar2 = new Control(sdk.controls.Button, 160, 591, 60, 100); - Control.LobbyChannelChar3 = new Control(sdk.controls.Button, 220, 591, 60, 100); - Control.LobbyChannelChar4 = new Control(sdk.controls.Button, 280, 591, 60, 100); - Control.LobbyChannelChar5 = new Control(sdk.controls.Button, 340, 591, 60, 100); - Control.LobbyChannelChar6 = new Control(sdk.controls.Button, 400, 591, 60, 100); - Control.LobbyChannelChar7 = new Control(sdk.controls.Button, 460, 591, 60, 100); - Control.LobbyChannelChar8 = new Control(sdk.controls.Button, 520, 591, 60, 100); - Control.LobbyChannelChar9 = new Control(sdk.controls.Button, 580, 591, 60, 100); - Control.LobbyChannelChar10 = new Control(sdk.controls.Button, 640, 591, 60, 100); - - Control.LobbyChat = new Control(sdk.controls.LabelBox, 28, 410, 354, 298); - Control.LobbyServerDown = new Control(sdk.controls.LabelBox, 438, 300, 326, 150); - - Control.OkCentered = new Control(sdk.controls.Button, 351, 337, 96, 32); - Control.HellSP = new Control(-1, 264, 383, 272, 35); - Control.NightmareSP = new Control(-1, 264, 340, 272, 35); - Control.NormalSP = new Control(-1, 264, 297, 272, 35); - - module.exports = Control; + this.xsize = xsize; + + /** + * @private + * @type {number} + */ + this.ysize = ysize; + + return new Proxy(this, { + get: function (target, p) { + const passthroughFunc = ["click", "setText", "getText"]; + + if (p === "valueOf") { + return target; + } + + const control = getControl(target.type, target.x, target.y, target.xsize, target.ysize); + if (p === "control") { + return control; + } + + // Relay on old ControlAction functions + if (passthroughFunc.indexOf(p) !== -1) { + return (...args) => ControlAction[p] + .apply(ControlAction, [target.type, target.x, target.y, target.xsize, target.ysize].concat(args)); + } + + // if control is found, and it's a property of the control + if (typeof control === "object" && control && control.hasOwnProperty(p)) { + return control[p]; + } + + // The target has it + if (target.hasOwnProperty(p)) { + return target[p]; + } + + return null; + } + }); + } + + // General Controls - Still non-exhaustive + Control.BottomLeftExit = new Control(sdk.controls.Button, 33, 572, 128, 35); + Control.BottomRightOk = new Control(sdk.controls.Button, 627, 572, 128, 35); + Control.OkCentered = new Control(sdk.controls.Button, 351, 337, 96, 32); + Control.OkCenteredText = new Control(sdk.controls.LabelBox, 268, 300, 264, 100); + Control.EnterAccountName = new Control(sdk.controls.TextBox, 322, 342, 162, 19); + Control.EnterAccountPassword = new Control(sdk.controls.TextBox, 322, 396, 162, 19); + Control.PopupYes = new Control(sdk.controls.Button, 421, 337, 96, 32); + Control.PopupNo = new Control(sdk.controls.Button, 281, 337, 96, 32); + + // Main Menu Controls + { + Control.SplashScreen = new Control(sdk.controls.TextBox, 0, 599, 800, 600); + Control.D2SplashCopyright = new Control(sdk.controls.LabelBox, 100, 580, 600, 80); + Control.MainMenuD2Version = new Control(sdk.controls.LabelBox, 0, 599, 200, 40); + Control.MainMenuCredits = new Control(sdk.controls.Button, 264, 528, 135, 25); + Control.MainMenuCinematics = new Control(sdk.controls.Button, 402, 528, 135, 25); + Control.MainMenuExit = new Control(sdk.controls.Button, 264, 568, 272, 35); + Control.SinglePlayer = new Control(-1, 264, 324, 272, 35); + Control.BattleNet = new Control(-1, 264, 366, 272, 35); + Control.Gateway = new Control(sdk.controls.Button, 264, 391, 272, 25); + Control.OtherMultiplayer = new Control(-1, 264, 433, 272, 35); + } + + // Login Menu Controls + { + Control.Login = new Control(-1, 264, 484, 272, 35); + Control.LoginHeading = new Control(sdk.controls.LabelBox, 200, 350, 400, 100); + Control.LoginErrorOk = new Control(sdk.controls.Button, 335, 412, 128, 35); + Control.LoginCancelWait = new Control(sdk.controls.Button, 330, 416, 128, 35); + Control.LoginCdKeyInUseBy = new Control(sdk.controls.LabelBox, 162, 270, 477, 50); + Control.LoginLodKeyInUseBy = new Control(sdk.controls.LabelBox, 158, 310, 485, 40); + Control.LoginInvalidCdKey = new Control(sdk.controls.LabelBox, 4, 162, 320, 477, 100); + Control.LoginUnableToConnect = new Control(sdk.controls.LabelBox, 158, 220, 485, 40); + Control.LoginErrorText = new Control(sdk.controls.LabelBox, 199, 377, 402, 140); + Control.LoginAccountSettings = new Control(sdk.controls.Button, 264, 528, 272, 35); + Control.UnableToConnectOk = new Control(sdk.controls.Button, 335, 450, 128, 35); + } + + // Account Settings Menu Controls + { + Control.AccountSettingsLabel = new Control(sdk.controls.LabelBox, 0, 310, 800, 50); + Control.ChangePassword = new Control(sdk.controls.Button, 264, 335, 272, 35); + Control.GetNewPassword = new Control(sdk.controls.Button, 264, 420, 272, 35); + Control.ChangeEmail = new Control(sdk.controls.Button, 264, 505, 272, 35); + } + + // Change Password + { + Control.ChangePasswordAccount = new Control(sdk.controls.TextBox, 322, 342, 162, 19); + Control.ChangePasswordCurrent = new Control(sdk.controls.TextBox, 322, 396, 162, 19); + Control.ChangePasswordNew = new Control(sdk.controls.TextBox, 322, 450, 162, 19); + Control.ChangePasswordConfirm = new Control(sdk.controls.TextBox, 322, 504, 162, 19); + } + + // Get New Password + { + Control.GetNewPasswordAccount = new Control(sdk.controls.TextBox, 251, 422, 293, 19); + Control.GetNewPasswordEmail = new Control(sdk.controls.TextBox, 251, 472, 293, 19); + } + + // Change Email + { + Control.ChangeEmailAccount = new Control(sdk.controls.TextBox, 251, 397, 293, 19); + Control.ChangeEmailCurrent = new Control(sdk.controls.TextBox, 251, 447, 293, 19); + Control.ChangeEmailNew = new Control(sdk.controls.TextBox, 251, 497, 293, 19); + Control.ChangeEmailConfirm = new Control(sdk.controls.TextBox, 251, 547, 293, 19); + } + + // Other Multiplayer Menu Controls + { + Control.OpenBattleNet = new Control(-1, 264, 310, 272, 35); + Control.TcpIp = new Control(-1, 264, 350, 272, 35); + Control.OtherMultiplayerCancel = new Control(sdk.controls.Button, 264, 568, 272, 35); + } + + // Gateway Menu Controls + { + Control.GatewayOk = new Control(sdk.controls.Button, 281, 538, 96, 32); + Control.GatewayCancel = new Control(sdk.controls.Button, 436, 538, 96, 32); + } + + // TCP/IP Menu Controls + { + Control.TcpIpHost = new Control(-1, 265, 206, 272, 35); + Control.TcpIpJoin = new Control(-1, 265, 264, 272, 35); + Control.TcpIpCancel = new Control(sdk.controls.Button, 39, 571, 128, 35); + + Control.IPAdress = new Control(-1, 300, 268, -1, -1); + Control.IPAdressOk = new Control(-1, 421, 337, 96, 32); + } + + // Create Account Menu Controls + { + Control.CreateNewAccount = new Control(sdk.controls.Button, 264, 572, 272, 35); + Control.ConfirmPassword = new Control(sdk.controls.TextBox, 322, 450, 162, 19); + } + + // Terms of Use Menu Controls + { + Control.TermsOfUseAgree = new Control(sdk.controls.Button, 525, 513, 128, 35); + Control.TermsOfUseDisagree = new Control(sdk.controls.Button, 133, 513, 128, 35); + + Control.PleaseReadOk = new Control(sdk.controls.Button, 525, 513, 128, 35); + Control.PleaseReadCancel = new Control(sdk.controls.Button, 133, 513, 128, 35); + } + + // Email Menu Controls + { + Control.EmailSetEmail = new Control(sdk.controls.TextBox, 253, 342, 293, 19); + Control.EmailVerifyEmail = new Control(sdk.controls.TextBox, 253, 396, 293, 19); + Control.EmailRegister = new Control(sdk.controls.Button, 265, 527, 272, 35); + Control.EmailDontRegister = new Control(sdk.controls.Button, 265, 572, 272, 35); + Control.EmailDontRegisterContinue = new Control(sdk.controls.Button, 415, 412, 128, 35); + } + + // Character Select Menu Controls + { + Control.CharSelectCreate = new Control(sdk.controls.Button, 33, 528, 168, 60); + Control.CharSelectDelete = new Control(sdk.controls.Button, 433, 528, 168, 60); + Control.CharSelectConvert = new Control(sdk.controls.Button, 233, 528, 168, 60); + Control.CharSelectError = new Control(sdk.controls.LabelBox, 45, 318, 531, 140); + Control.CharSelectCharInfo0 = new Control(sdk.controls.LabelBox, 37, 178, 200, 92); + Control.CharSelectChar4 = new Control(sdk.controls.LabelBox, 237, 364, 72, 93); + Control.CharSelectChar6 = new Control(sdk.controls.LabelBox, 237, 457, 72, 93); + Control.CharSelectCurrentRealm = new Control(sdk.controls.LabelBox, 626, 100, 151, 44); + } + + // Character Create Menu Controls + { + Control.CharCreateCharName = new Control(sdk.controls.TextBox, 318, 510, 157, 16); + Control.CharCreateExpansion = new Control(sdk.controls.Button, 319, 540, 15, 16); + Control.CharCreateLadder = new Control(sdk.controls.Button, 319, 580, 15, 16); + Control.CharCreateHardcore = new Control(sdk.controls.Button, 319, 560, 15, 16); + // these two are the same as popup yes/no, should they be kept seperate or merged? + Control.CharCreateHCWarningOk = new Control(sdk.controls.Button, 421, 337, 96, 32); + Control.CharCreateHCWarningCancel = new Control(sdk.controls.Button, 281, 337, 96, 32); + } + + // Lobby Menu Controls + { + Control.LobbyCharacterInfo = new Control(sdk.controls.LabelBox, 143, 588, 230, 87); + Control.LobbyEnterChat = new Control(sdk.controls.Button, 27, 480, 120, 20); + Control.LobbyChat = new Control(sdk.controls.LabelBox, 28, 410, 354, 298); + Control.LobbyServerDown = new Control(sdk.controls.LabelBox, 438, 300, 326, 150); + Control.LobbyLadder = new Control(sdk.controls.Button, 614, 490, 80, 20); + Control.LobbyHelp = new Control(sdk.controls.Button, 146, 480, 120, 20); + Control.LobbyQuit = new Control(sdk.controls.Button, 693, 490, 80, 20); + Control.LobbyNews = new Control(sdk.controls.LabelBox, 28, 410, 354, 298); + Control.LobbyWarning = new Control(sdk.controls.LabelBox, 447, 398, 290, 269); + } + + // Ladder menu controls + { + Control.StandardLadder = new Control(sdk.controls.Button, 463, 188, 272, 32); + Control.HardcoreLadder = new Control(sdk.controls.Button, 463, 238, 272, 32); + Control.ExpansionLadder = new Control(sdk.controls.Button, 463, 288, 272, 32); + Control.ExpansionHardcoreLadder = new Control(sdk.controls.Button, 463, 338, 272, 32); + Control.LadderTab = new Control(sdk.controls.LabelBox, 421, 136, 350, 50); + Control.LadderOverall = new Control(sdk.controls.LabelBox, 427, 157, 85, 29); + Control.LadderAmazon = new Control(sdk.controls.LabelBox, 513, 157, 36, 29); + Control.LadderSorceress = new Control(sdk.controls.LabelBox, 550, 157, 36, 29); + Control.LadderNecromancer = new Control(sdk.controls.LabelBox, 587, 157, 36, 29); + Control.LadderPaladin = new Control(sdk.controls.LabelBox, 624, 157, 36, 29); + Control.LadderBarbarian = new Control(sdk.controls.LabelBox, 661, 157, 36, 29); + Control.LadderDruid = new Control(sdk.controls.LabelBox, 698, 157, 36, 29); + Control.LadderAssassin = new Control(sdk.controls.LabelBox, 735, 157, 36, 29); + Control.LadderRank = new Control(sdk.controls.LabelBox, 434, 162, 217, 12); + Control.LadderName = new Control(sdk.controls.LabelBox, 468, 162, 217, 12); + Control.LadderClass = new Control(sdk.controls.LabelBox, 596, 162, 217, 12); + Control.LadderLevel = new Control(sdk.controls.LabelBox, 640, 162, 217, 12); + Control.LadderExperience = new Control(sdk.controls.LabelBox, 703, 162, 217, 12); + Control.LadderList = new Control(sdk.controls.LabelBox, 434, 391, 313, 218); + Control.LadderScrollDown = new Control(sdk.controls.ScrollBar, 756, 391, 10, 238); + } + + // Join Game Menu Controls + { + Control.JoinGameWindow = new Control(sdk.controls.Button, 652, 469, 120, 20); + Control.JoinGame = new Control(sdk.controls.Button, 594, 433, 172, 32); + Control.JoinGameName = new Control(sdk.controls.TextBox, 432, 148, 155, 20); + Control.JoinGamePass = new Control(sdk.controls.TextBox, 606, 148, 155, 20); + Control.JoinGameList = new Control(sdk.controls.LabelBox, 432, 393, 160, 173); + Control.JoinGameDetails = new Control(sdk.controls.LabelBox, 609, 393, 143, 194); + Control.CancelJoinGame = new Control(sdk.controls.Button, 433, 433, 96, 32); + } + + // Create Game Menu Controls + { + Control.CreateGameWindow = new Control(sdk.controls.Button, 533, 469, 120, 20); + Control.CreateGame = new Control(sdk.controls.Button, 594, 433, 172, 32); + Control.CreateGameName = new Control(sdk.controls.TextBox, 432, 162, 158, 20); + Control.CreateGamePass = new Control(sdk.controls.TextBox, 432, 217, 158, 20); + Control.CreateGameDescription = new Control(sdk.controls.TextBox, 432, 268, 333, 20); + Control.CharacterDifferenceButton = new Control(sdk.controls.Button, 431, 341, 15, 16); + Control.CharacterDifference = new Control(sdk.controls.TextBox, 657, 342, 27, 20); + Control.MaxPlayerCount = new Control(sdk.controls.TextBox, 657, 308, 27, 20); + Control.Normal = new Control(sdk.controls.Button, 430, 381, 16, 16); + Control.Nightmare = new Control(sdk.controls.Button, 555, 381, 16, 16); + Control.Hell = new Control(sdk.controls.Button, 698, 381, 16, 16); + Control.CreateGameInLine = new Control(sdk.controls.LabelBox, 427, 234, 300, 100); + Control.CancelCreateGame = new Control(sdk.controls.Button, 433, 433, 96, 32); + } + + // Channel Menu Controls + { + Control.LobbyChannel = new Control(sdk.controls.Button, 535, 490, 80, 20); + Control.LobbyChannelName = new Control(sdk.controls.LabelBox, 28, 138, 354, 60); + Control.LobbyChannelText = new Control(sdk.controls.TextBox, 432, 162, 155, 20); + Control.LobbyChannelOk = new Control(sdk.controls.Button, 671, 433, 96, 32); + Control.LobbyChannelCancel = new Control(sdk.controls.Button, 433, 433, 96, 32); + Control.LobbyChannelSend = new Control(sdk.controls.Button, 27, 470, 80, 20); + Control.LobbyChannelWhisper = new Control(sdk.controls.Button, 107, 470, 80, 20); + Control.LobbyChannelSquelch = new Control(sdk.controls.Button, 27, 490, 72, 20); + Control.LobbyChannelUnsquelch = new Control(sdk.controls.Button, 99, 490, 96, 20); + Control.LobbyChannelEmote = new Control(sdk.controls.Button, 195, 490, 72, 20); + Control.LobbyChannelChar0 = new Control(sdk.controls.Button, 40, 591, 60, 100); + Control.LobbyChannelChar1 = new Control(sdk.controls.Button, 100, 591, 60, 100); + Control.LobbyChannelChar2 = new Control(sdk.controls.Button, 160, 591, 60, 100); + Control.LobbyChannelChar3 = new Control(sdk.controls.Button, 220, 591, 60, 100); + Control.LobbyChannelChar4 = new Control(sdk.controls.Button, 280, 591, 60, 100); + Control.LobbyChannelChar5 = new Control(sdk.controls.Button, 340, 591, 60, 100); + Control.LobbyChannelChar6 = new Control(sdk.controls.Button, 400, 591, 60, 100); + Control.LobbyChannelChar7 = new Control(sdk.controls.Button, 460, 591, 60, 100); + Control.LobbyChannelChar8 = new Control(sdk.controls.Button, 520, 591, 60, 100); + Control.LobbyChannelChar9 = new Control(sdk.controls.Button, 580, 591, 60, 100); + Control.LobbyChannelChar10 = new Control(sdk.controls.Button, 640, 591, 60, 100); + } + + // Single Player Difficulty Controls + { + Control.HellSP = new Control(-1, 264, 383, 272, 35); + Control.NightmareSP = new Control(-1, 264, 340, 272, 35); + Control.NormalSP = new Control(-1, 264, 297, 272, 35); + } + + module.exports = Control; })(module); diff --git a/d2bs/kolbot/libs/modules/CopyData.js b/d2bs/kolbot/libs/modules/CopyData.js new file mode 100644 index 000000000..583b193df --- /dev/null +++ b/d2bs/kolbot/libs/modules/CopyData.js @@ -0,0 +1,102 @@ +/** + * @filename CopyData.js + * @author theBGuy + * @desc UMD module for creating and sending a copy data obj + * + */ + +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + const v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define([], factory); + } else { + root.CopyData = factory(); + console.trace(); + } +}([].filter.constructor("return this")(), function() { + /** + * @class + * @classdesc A class for creating and sending copy data packets. + * @property {number} _mode - Defaults to 0, works for most D2Bot functions + * @property {number | string} _handle - Defaults to value of D2Bot.handle, works for any D2Bot + * functions that act on ourselves + * @example Request a game from "scl-sorc-001" profile + * new CopyData().handle("scl-sorc-001").mode(3).send(); + * @example Start mule profile "mule" + * new CopyData().data("start", ["mule"]).send(); + */ + function CopyData() { + if (this.__proto__.constructor !== CopyData) throw new Error("CopyData must be called with 'new' operator!"); + + /** + * @private + * @type {string | number} - The handle to send the copy data to. + */ + this._handle = D2Bot.handle || me.profile; + + /** + * @private + * @type {number} - The mode of the copy data packet. + */ + this._mode = 0; + + /** + * @private + * @type {string} - The data to send in the copy data + */ + this._data = null; + } + + /** + * - D2Bot.handle is for any functions that act on ourselves + * - Otherwise it is the D2Bot# profile name of the profile to act upon + * @param {string | number} handle - The handle or profile to send the copy data to. + */ + CopyData.prototype.handle = function (handle) { + this._handle = handle; + return this; + }; + + /** + * - 0 is for most functions, and the default value set + * - 1 is for joinMe + * - 3 is for requestGame + * - 0xbbbb is for heartBeat + * @param {number} mode - The mode of the copy data packet. + */ + CopyData.prototype.mode = function (mode) { + this._mode = mode; + return this; + }; + + /** + * @param {string} [func] - The function to call from D2Bot# + * @param {string[]} [args] - The additonal info needed for the function call + */ + CopyData.prototype.data = function (func = "", args = []) { + if (func.includes("Item") || func === "printToConsole" || (func === "setTag" && typeof args[0] === "object")) { + args[0] = JSON.stringify(args[0]); + } + this._data = JSON.stringify({ + profile: me.profile, + func: func, + args: args + }); + return this; + }; + + /** + * CopyData.data works for functions call d2bot# functions but what about gerneral use? + * @todo handle passing custom data obj + */ + + CopyData.prototype.send = function () { + // check that data is set + this._data === null && this.data(); + return sendCopyData(null, this._handle, this._mode, this._data); + }; + + return CopyData; +})); diff --git a/d2bs/kolbot/libs/modules/Deltas.js b/d2bs/kolbot/libs/modules/Deltas.js index fdc5aba2d..7de278c81 100644 --- a/d2bs/kolbot/libs/modules/Deltas.js +++ b/d2bs/kolbot/libs/modules/Deltas.js @@ -4,36 +4,40 @@ * */ (function (module, require) { - const Worker = require("Worker"); - let instances = 0; + const Worker = require("Worker"); + let instances = 0; - /** @constructor - * @class Delta */ - module.exports = function (trackers) { - let active = true; - this.values = (Array.isArray(trackers) && (Array.isArray(trackers.first()) && trackers || [trackers])) || []; - this.track = function (checkerFn, callback) { - return this.values.push({fn: checkerFn, callback: callback, value: checkerFn()}); - }; - this.check = function () { - this.values.some(delta => { - let val = delta.fn(); + /** @constructor + * @class Delta */ + module.exports = function (trackers) { + let active = true; + this.values = (Array.isArray(trackers) && (Array.isArray(trackers.first()) && trackers || [trackers])) || []; + this.track = function (checkerFn, callback) { + return this.values.push({ fn: checkerFn, callback: callback, value: checkerFn() }); + }; + this.check = function () { + this.values.some(delta => { + let val = delta.fn(); - if (delta.value !== val) { - let ret = delta.callback(delta.value, val); - delta.value = val; + if (delta.value !== val) { + let ret = delta.callback(delta.value, val); + delta.value = val; - return ret; - } + return ret; + } - return null; - }); - }; + return null; + }); + }; - this.destroy = () => active = false; + this.destroy = () => active = false; - Worker.runInBackground["__delta" + (instances++)] = () => active && (this.check() || true); - return this; - }; + Worker.runInBackground["__delta" + (instances++)] = () => active && (this.check() || true); + return this; + }; -}).call(null, typeof module === "object" && module || {}, typeof require === "undefined" && (include("require.js") && require) || require); +}).call( + null, + typeof module === "object" && module || {}, + typeof require === "undefined" && (include("require.js") && require) || require +); diff --git a/d2bs/kolbot/libs/modules/Events.js b/d2bs/kolbot/libs/modules/Events.js index ccec6c572..5d824f1ff 100644 --- a/d2bs/kolbot/libs/modules/Events.js +++ b/d2bs/kolbot/libs/modules/Events.js @@ -5,47 +5,47 @@ */ (function (module, require) { - // eslint-disable-next-line no-unused-vars - const Events = module.exports = function () { - const Worker = require("Worker"), self = this; - - this.hooks = []; - - function Hook(name, callback) { - this.name = name; - this.callback = callback; - this.id = self.hooks.push(this) - 1; - this.__callback = callback; // used for once - } - - this.on = function (name, callback) { - if (callback === undefined && typeof name === "function") [callback, name] = [name, callback]; - return new Hook(name, callback); - }; - - this.trigger = function (name, ...args) { - return self.hooks.forEach(hook => !hook.name || hook.name === name && Worker.push(function () { - hook.callback.apply(hook, args); - })); - }; - - this.emit = this.trigger; // Alias for trigger - - this.once = function (name, callback) { - if (callback === undefined && typeof name === "function") [callback, name] = [name, callback]; - const hook = new Hook(name, function (...args) { - callback.apply(undefined, args); - delete self.hooks[this.id]; - }); - hook.__callback = callback; - }; - - this.off = function (name, callback) { - self.hooks.filter(hook => hook.__callback === callback).forEach(hook => { - delete self.hooks[hook.id]; - }); - }; - - this.removeListener = this.off; // Alias for remove - }; + // eslint-disable-next-line no-unused-vars + const Events = module.exports = function () { + const Worker = require("Worker"), self = this; + + this.hooks = []; + + function Hook(name, callback) { + this.name = name; + this.callback = callback; + this.id = self.hooks.push(this) - 1; + this.__callback = callback; // used for once + } + + this.on = function (name, callback) { + if (callback === undefined && typeof name === "function") [callback, name] = [name, callback]; + return new Hook(name, callback); + }; + + this.trigger = function (name, ...args) { + return self.hooks.forEach(hook => !hook.name || hook.name === name && Worker.push(function () { + hook.callback.apply(hook, args); + })); + }; + + this.emit = this.trigger; // Alias for trigger + + this.once = function (name, callback) { + if (callback === undefined && typeof name === "function") [callback, name] = [name, callback]; + const hook = new Hook(name, function (...args) { + callback.apply(undefined, args); + delete self.hooks[this.id]; + }); + hook.__callback = callback; + }; + + this.off = function (name, callback) { + self.hooks.filter(hook => hook.__callback === callback).forEach(hook => { + delete self.hooks[hook.id]; + }); + }; + + this.removeListener = this.off; // Alias for remove + }; })(module, require); diff --git a/d2bs/kolbot/libs/modules/Graph.js b/d2bs/kolbot/libs/modules/Graph.js new file mode 100644 index 000000000..fb1622663 --- /dev/null +++ b/d2bs/kolbot/libs/modules/Graph.js @@ -0,0 +1,361 @@ +/** + * @author ryancrunchi, theBGuy + * @description Graph algorithms implementation for rooms exploration. + */ + +(function (module) { + /** + * Wrapper class for room as vertex + * @constructor + * @param {Room} room + */ + function Vertex(room) { + this.id = Vertex._id++; + this.centerX = room.x * 5 + room.xsize / 2; + this.centerY = room.y * 5 + room.ysize / 2; + this.x = room.x; + this.y = room.y; + this.xsize = room.xsize; + this.ysize = room.ysize; + this.seen = false; + this.walkableX = this.centerX; + this.walkableY = this.centerY; + this.area = room.level; + // Should the step be lowered? + let adjusted = Pather.getNearestWalkable(this.centerX, this.centerY, 20, 10); + if (!adjusted) { + throw new Error("Vertex is not walkable"); + } + this.walkableX = adjusted[0]; + this.walkableY = adjusted[1]; + + /** @type {Record} */ + this.cache = {}; + } + + /** @static */ + Vertex._id = 0; + + /** @this {Vertex} */ + Vertex.prototype.clearCache = function() { + this.cache = {}; + }; + + Vertex.prototype.markAsSeen = function() { + this.seen = true; + }; + + /** + * @param {number} x + * @param {number} y + * @returns {boolean} + */ + Vertex.prototype.coordsInRoom = function(x, y) { + return (x >= this.x * 5 && x < this.x * 5 + this.xsize + && y >= this.y * 5 && y < this.y * 5 + this.ysize + ); + }; + + /** + * @param {"walk" | "teleport"} mode + * @returns {PathNode[]} + */ + Vertex.prototype.path = function(mode = "walk") { + const key = mode + "Path"; + if (this.cache[key]) { + return this.cache[key]; + } + const x = mode === "walk" ? this.walkableX : this.centerX; + const y = mode === "walk" ? this.walkableY : this.centerY; + const rType = mode === "walk" ? 0 : 1; + const nDist = mode === "walk" ? Pather.walkDistance : Pather.teleDistance; + let path = getPath(this.area, me.x, me.y, x, y, rType, nDist); + this.cache[key] = path; + return path; + }; + + /** + * @param {"walk" | "teleport"} mode + * @returns {number} + */ + Vertex.prototype.pathDistance = function(mode = "walk") { + const key = mode + "PathDistance"; + if (this.cache[key]) { + return this.cache[key]; + } + let path = this.path(mode); + if (!path.length) { + return Infinity; + } + let distance = path.reduce(function (acc, v, i, arr) { + let prev = i ? arr[i - 1] : v; + return acc + Math.sqrt((prev.x - v.x) * (prev.x - v.x) + (prev.y - v.y) * (prev.y - v.y)); + }, 0); + this.cache[key] = distance; + return distance; + }; + + /** + * @this {Vertex} + * @param {Vertex} other + * @param {"walk" | "teleport"} mode + * @returns {Array<{x: number, y: number}>} + */ + Vertex.prototype.pathTo = function(other, mode = "walk") { + const key = mode + "PathTo"; + if (this.cache[key] && this.cache[key][other.id]) { + return this.cache[key][other.id]; + } + const inRoom = CollMap.coordsInRoom(me.x, me.y, this); + const area = this.area; + const rType = mode === "walk" ? 0 : 1; + const nDist = mode === "walk" ? Pather.walkDistance : Pather.teleDistance; + const x = inRoom ? me.x : mode === "walk" ? this.walkableX : this.centerX; + const y = inRoom ? me.y : mode === "walk" ? this.walkableY : this.centerY; + const otherX = mode === "walk" ? other.walkableX : other.centerX; + const otherY = mode === "walk" ? other.walkableY : other.centerY; + + let path = getPath(area, x, y, otherX, otherY, rType, nDist); + if (!this.cache[key]) { + this.cache[key] = {}; + } + this.cache[key][other.id] = path; + return path; + }; + + /** + * @param {Vertex} other + * @param {"walk" | "teleport"} mode + * @returns {number} + */ + Vertex.prototype.pathDistanceTo = function(other, mode = "walk") { + const key = mode + "PathDistanceTo"; + if (this.cache[key] && this.cache[key][other.id]) { + return this.cache[key][other.id]; + } + let path = this.pathTo(other, mode); + if (!path.length) { + return Infinity; + } + let distance = path.reduce(function (acc, v, i, arr) { + let prev = i ? arr[i - 1] : v; + return acc + Math.sqrt((prev.x - v.x) * (prev.x - v.x) + (prev.y - v.y) * (prev.y - v.y)); + }, 0); + if (!this.cache[key]) { + this.cache[key] = {}; + } + this.cache[key][other.id] = distance; + return distance; + }; + + /** + * @description Graph class to handle vertices and search algorithms + * @constructor + */ + function Graph() { + CollMap.removeHooks(); + /** @type {Vertex[]} */ + this.vertices = []; + + // TODO: We should eliminate rooms that are over 80% unwalkable + let room = getRoom(); + if (room) { + do { + try { + let vertex = new Vertex(room); + this.vertices.push(vertex); + CollMap.drawRoom(copyObj(room), "blue"); + } catch (e) { + CollMap.drawRoom(copyObj(room), "red"); + } + } while (room.getNext()); + } + + this.vertices.sort(function (a, b) { + return getDistance(me.x, me.y, a.walkableX, a.walkableY) - getDistance(me.x, me.y, b.walkableX, b.walkableY); + }); + + /** + * get the graph vertex from room object + * @param {Room} room + */ + this.vertexForRoom = function (room) { + return this.vertices.find(function (v) { + return v.x === room.x && v.y === room.y; + }); + }; + + /** + * get the room the vertex is in + * @param {Vertex} vertex + * @returns {Room} + */ + this.roomForVertex = function (vertex) { + return getRoom(vertex.centerX, vertex.centerY); + }; + + /** + * get nearby vertices from vertex (child) by getting neaby rooms. + * @param {Vertex} vertex + * @returns + */ + this.nearbyVertices = function (vertex) { + let room = this.roomForVertex(vertex); + if (!room) { + return []; + } + const self = this; + return room.getNearby() + .compactMap(function (r) { + return self.vertexForRoom(r); + }); + //.sort((a, b) => a.adjustedPathDistance - b.adjustedPathDistance); + }; + } + + // eslint-disable-next-line no-unused-vars + Graph.customSearch = function(graph, explore) { + + }; + + /** @param {Vertex} v */ + const filterSeen = function (v) { + return !v.seen; + }; + + /** + * @param {Graph} graph + * @param {(vertex: Vertex) => any} explore + * @param {"walk" | "teleport"} mode + */ + Graph.nearestNeighbourSearch = function(graph, explore, mode = "walk") { + let currentVertex = graph.vertices.filter(filterSeen).first(); + while (currentVertex) { + CollMap.drawRoom(currentVertex, "green", true); + + explore(currentVertex); + currentVertex.markAsSeen(); + CollMap.drawRoom(currentVertex, "purple", true); + + // our explore method could move us to a different room, so we need to get the vertex again + if (!currentVertex.coordsInRoom(me.x, me.y)) { + console.debug("Moved to a different room, getting new vertex"); + let _newVertex = graph.vertexForRoom(getRoom(me.x, me.y)); + if (_newVertex) { + currentVertex = _newVertex; + } else { + console.warn("Could not find vertex for my room?"); + } + } + + let nearbies = graph.nearbyVertices(currentVertex) + .filter(filterSeen) + .sort(function (a, b) { + let distanceToA = currentVertex.pathDistanceTo(a, mode); + let distanceToB = currentVertex.pathDistanceTo(b, mode); + let distDiff = Math.abs(distanceToA - distanceToB); + + // If the difference is less than 5% of the distance to a, sort by number of neighbors + if (distDiff / distanceToA < 0.05) { + // sort by number of neighbors (ascending) + let diff = graph.nearbyVertices(a).length - graph.nearbyVertices(b).length; + if (diff !== 0) return diff; + } + // If number of neighbors is the same, sort by walkable path distance (ascending) + return distanceToA - distanceToB; + }); + nearbies.forEach(function (n) { + CollMap.drawRoom(graph.roomForVertex(n), "white", true); + }); + currentVertex = nearbies.first() || + // if no neihbors is found, get next nearest vertex in graph + graph.vertices + .filter(filterSeen) + .sort(function (a, b) { + let aDist = a.pathDistance(mode); + let bDist = b.pathDistance(mode); + let distDiff = Math.abs(aDist - bDist); + + // If the difference is less than 5% of the distance to a, sort by number of neighbors + if (distDiff / aDist < 0.05) { + // sort by number of neighbors (ascending) + let diff = graph.nearbyVertices(a).length - graph.nearbyVertices(b).length; + if (diff !== 0) return diff; + } + + // return a.pathDistance(mode) - b.pathDistance(mode); + return aDist - bDist; + }) + .first(); + for (let vertice of graph.vertices) { + vertice.clearCache(); + } + } + + //TODO: sometimes, the bot leaves a small group of vertices alone, and continues to the biggest part of the graph + // this leads the bot to go to this small group at the end and it is not optimal. It should have gone to this small group before finishing all the rest + // we need to construct get disconnected parts of graph and go to the nearest smallest part before continuing + }; + + /** + * DFS implementation + * exploreFunction is a function called for every explored vertex in the graph that takes a vertex as parameter + * @param {Graph} graph + * @param {(vertex: Vertex) => any} exploreFunction + * @param {"walk" | "teleport"} mode + */ + Graph.depthFirstSearch = function(graph, exploreFunction, mode = "walk") { + /** @type {Vertex[]} */ + let stack = []; + let startVertex = graph.vertices.first(); + stack.push(startVertex); + + while (stack.length) { + let vertex = stack.pop(); + if (vertex.seen) continue; + exploreFunction(vertex); + vertex.seen = true; + + CollMap.drawRoom(vertex, "green", true); + let neighbors = graph.nearbyVertices(vertex).filter(filterSeen); + for (let i = 0; i < neighbors.length; i++) { + stack.push(neighbors[i]); + CollMap.drawRoom(neighbors[i], "purple", true); + } + // console.time("sort"); + stack.sort(function (a, b) { + return b.pathDistance(mode) - a.pathDistance(mode); + }); + // console.timeEnd("sort"); + // clear cache for all vertices + for (let vertice of graph.vertices) { + vertice.clearCache(); + } + } + }; + + /** + * BFS implementation + * exploreFunction is a function called for every explored vertex in the graph that takes a vertex as parameter + * @param {Graph} graph + * @param {(vertex: Vertex) => any} exploreFunction + */ + Graph.breadthFirstSearch = function(graph, exploreFunction) { + let queue = []; + let startVertex = graph.vertices.first(); + queue.push(startVertex); + while (queue.length) { + let vertex = queue.shift(); + let neighbors = graph.nearbyVertices(vertex).filter(filterSeen); + for (let i = 0; i < neighbors.length; i++) { + queue.push(neighbors[i]); + neighbors[i].seen = true; + } + exploreFunction(vertex); + vertex.seen = true; + CollMap.drawRoom(vertex, "green", true); + } + }; + + module.exports = Graph; +})(module, require); diff --git a/d2bs/kolbot/libs/modules/HTTP.js b/d2bs/kolbot/libs/modules/HTTP.js index 1e66b8561..584d1f7da 100644 --- a/d2bs/kolbot/libs/modules/HTTP.js +++ b/d2bs/kolbot/libs/modules/HTTP.js @@ -5,48 +5,62 @@ (function (module, require) { - const Socket = require("Socket"); - const Promise = require("Promise"); - - const defaultOptions = { - url: "", - headers: { - "User-Agent": "d2bs", - "Accept": "*/*", - }, - port: 80, - method: "GET", - data: "", // Fill with content for post - }; - - const HTTP = function (config = {}) { - config = Object.assign(defaultOptions, config); - if (!config.url) { - throw new Error("Must give a url to connect to"); - } - const [fullUrl, protocol, hostname, uri] = config.url.match(/^(.*:)\/\/([A-Za-z0-9\-\.]+)?(.*)/); - const socket = new Socket(hostname, config.port); - - socket.connect(); - if (!socket.connected) { - throw new Error("failed to connect to " + hostname); - } - - if (config.data.length) { // in case we send data - config.headers["Content-Length"] = config.data.length; - } - config.headers["Host"] = hostname; // required for HTTP/1.1 - - const data = [config.method + " " + uri + " " + "HTTP/1.1"]; - Object.keys(config.headers).forEach((key) => data.push(key + ": " + config.headers[key])); - - socket.send(data.join("\r\n") + "\r\n\r\n" + config.data); - - let recvd = false; // where we store recv'd data - socket.once("data", data => recvd = data); - return new Promise((resolve) => recvd && resolve(recvd)); - }; - - module.exports = HTTP; + const Socket = require("Socket"); + const Promise = require("Promise"); + + /** + * @typedef {Object} HTTPConfig + * @property {string} url - The URL to send the request to. + * @property {Object} headers - The headers to include in the request. + * @property {string} headers.User-Agent - The User-Agent header value. + * @property {string} headers.Accept - The Accept header value. + * @property {number} port - The port to use for the request. + * @property {string} method - The HTTP method to use for the request. + * @property {string} data - The data to send with the request (for POST requests). + */ + + const defaultOptions = { + url: "", + headers: { + "User-Agent": "d2bs", + "Accept": "*/*", + }, + port: 80, + method: "GET", + data: "", // Fill with content for post + }; + + /** + * @param {HTTPConfig} config + */ + const HTTP = function (config = {}) { + config = Object.assign(defaultOptions, config); + if (!config.url) { + throw new Error("Must give a url to connect to"); + } + const [_fullUrl, _protocol, hostname, uri] = config.url.match(/^(.*:)\/\/([A-Za-z0-9\-\.]+)?(.*)/); + const socket = new Socket(hostname, config.port); + + socket.connect(); + if (!socket.connected) { + throw new Error("failed to connect to " + hostname); + } + + if (config.data.length) { // in case we send data + config.headers["Content-Length"] = config.data.length; + } + config.headers.Host = hostname; // required for HTTP/1.1 + + const data = [config.method + " " + uri + " " + "HTTP/1.1"]; + Object.keys(config.headers).forEach((key) => data.push(key + ": " + config.headers[key])); + + socket.send(data.join("\r\n") + "\r\n\r\n" + config.data); + + let recvd = false; // where we store recv'd data + socket.once("data", data => recvd = data); + return new Promise((resolve) => recvd && resolve(recvd)); + }; + + module.exports = HTTP; }).call(null, module, require); diff --git a/d2bs/kolbot/libs/modules/LocalChat.js b/d2bs/kolbot/libs/modules/LocalChat.js new file mode 100644 index 000000000..298e8d1e4 --- /dev/null +++ b/d2bs/kolbot/libs/modules/LocalChat.js @@ -0,0 +1,85 @@ +/** + * @filename LocalChat.js + * @author theBGuy + * @desc UMD module for LocalChat system + * + */ + +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + const v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define(["require", "../core/Util"], factory); + } else { + root.LocalChat = factory(); + console.trace(); + } +}(this, function() { + // const { PacketBuilder } = require("../core/Util"); + + const LocalChat = new function () { + const LOCAL_CHAT_ID = 0xD2BAAAA; + let toggle, proxy = say; + + let relay = (msg) => D2Bot.shoutGlobal( + JSON.stringify({ msg: msg, realm: me.realm, charname: me.charname, gamename: me.gamename }), + LOCAL_CHAT_ID + ); + + let onChatInput = (speaker, msg) => { + relay(msg); + return true; + }; + + let onChatRecv = (mode, msg) => { + if (mode !== LOCAL_CHAT_ID) return; + + msg = JSON.parse(msg); + + if (me.gamename === msg.gamename && me.realm === msg.realm) { + new PacketBuilder().byte(38).byte(1, me.locale).word(2, 0, 0).byte(90).string(msg.charname, msg.msg).get(); + } + }; + + let onKeyEvent = (key) => { + if (toggle === key) { + this.init(true); + } + }; + + this.init = (cycle = false) => { + if (!Config.LocalChat.Enabled) return; + + Config.LocalChat.Mode = (Config.LocalChat.Mode + cycle) % 3; + print("ÿc2LocalChat enabled. Mode: " + Config.LocalChat.Mode); + + switch (Config.LocalChat.Mode) { + case 2: + removeEventListener("chatinputblocker", onChatInput); + addEventListener("chatinputblocker", onChatInput); + // eslint-disable-next-line no-fallthrough + case 1: + removeEventListener("copydata", onChatRecv); + addEventListener("copydata", onChatRecv); + say = (msg, force = false) => force ? proxy(msg) : relay(msg); + break; + case 0: + removeEventListener("chatinputblocker", onChatInput); + removeEventListener("copydata", onChatRecv); + say = proxy; + break; + } + + if (Config.LocalChat.Toggle) { + toggle = typeof Config.LocalChat.Toggle === "string" + ? Config.LocalChat.Toggle.charCodeAt(0) + : Config.LocalChat.Toggle; + Config.LocalChat.Toggle = false; + addEventListener("keyup", onKeyEvent); + } + }; + }; + + return LocalChat; +})); diff --git a/d2bs/kolbot/libs/modules/Messaging.js b/d2bs/kolbot/libs/modules/Messaging.js index 7329de728..3b1587b04 100644 --- a/d2bs/kolbot/libs/modules/Messaging.js +++ b/d2bs/kolbot/libs/modules/Messaging.js @@ -5,34 +5,34 @@ (function (module, require) { - const myEvents = new (require("Events")); - const Worker = require("Worker"); - - - Worker.runInBackground.messaging = (new function () { - const workBench = []; - addEventListener("scriptmsg", data => workBench.push(data)); - - this.update = function () { - if (!workBench.length) return true; - - let work = workBench.splice(0, workBench.length); - work.filter(data => typeof data === "object" && data) - .forEach(function (data) { - Object.keys(data).forEach(function (item) { - myEvents.emit(item, data[item]); // Trigger those events - }); - }); - - return true; // always, to keep looping; - }; - }).update; - - module.exports = { - on: myEvents.on, - off: myEvents.off, - once: myEvents.once, - send: what => scriptBroadcast(what) - }; + const myEvents = new (require("Events")); + const Worker = require("Worker"); + + + Worker.runInBackground.messaging = (new function () { + const workBench = []; + addEventListener("scriptmsg", data => workBench.push(data)); + + this.update = function () { + if (!workBench.length) return true; + + let work = workBench.splice(0, workBench.length); + work.filter(data => typeof data === "object" && data) + .forEach(function (data) { + Object.keys(data).forEach(function (item) { + myEvents.emit(item, data[item]); // Trigger those events + }); + }); + + return true; // always, to keep looping; + }; + }).update; + + module.exports = { + on: myEvents.on, + off: myEvents.off, + once: myEvents.once, + send: what => scriptBroadcast(what) + }; })(module, require); diff --git a/d2bs/kolbot/libs/modules/Override.js b/d2bs/kolbot/libs/modules/Override.js index 030b35042..4eac2330f 100644 --- a/d2bs/kolbot/libs/modules/Override.js +++ b/d2bs/kolbot/libs/modules/Override.js @@ -1,59 +1,60 @@ +/* eslint-disable */ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { - if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { - if (ar || !(i in from)) { - if (!ar) ar = Array.prototype.slice.call(from, 0, i); - ar[i] = from[i]; - } + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; } - return to.concat(ar || Array.prototype.slice.call(from)); + } + return to.concat(ar || Array.prototype.slice.call(from)); }; (function (factory) { - if (typeof module === "object" && typeof module.exports === "object") { - var v = factory(require, exports); - if (v !== undefined) module.exports = v; - } - else if (typeof define === "function" && define.amd) { - define(["require", "exports"], factory); - } + if (typeof module === "object" && typeof module.exports === "object") { + var v = factory(require, exports); + if (v !== undefined) module.exports = v; + } + else if (typeof define === "function" && define.amd) { + define(["require", "exports"], factory); + } })(function (require, exports) { - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.Override = void 0; - var Override = /** @class */ (function () { - function Override(target, original, method) { - this.target = target; - if (typeof original !== 'string') { - this.original = original; - this.key = Object.keys(target).find(function (key) { return target[key] === original; }); - } - else { - this.original = undefined; - this.key = original; - } - this.method = method; - Override.all.push(this); + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.Override = void 0; + var Override = /** @class */ (function () { + function Override(target, original, method) { + this.target = target; + if (typeof original !== 'string') { + this.original = original; + this.key = Object.keys(target).find(function (key) { return target[key] === original; }); + } + else { + this.original = undefined; + this.key = original; + } + this.method = method; + Override.all.push(this); + } + Override.prototype.apply = function () { + var _a = this, target = _a.target, key = _a.key, method = _a.method, original = _a.original; + target[key] = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; } - Override.prototype.apply = function () { - var _a = this, target = _a.target, key = _a.key, method = _a.method, original = _a.original; - target[key] = function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - return method.apply(this, __spreadArray([original && original.bind(this)], args, true)); - }; - }; - Override.prototype.rollback = function () { - var _a = this, target = _a.target, key = _a.key, original = _a.original; - if (original) { - target[key] = original; - } - else { - delete target[key]; - } - }; - Override.all = []; - return Override; - }()); - exports.Override = Override; + return method.apply(this, __spreadArray([original && original.bind(this)], args, true)); + }; + }; + Override.prototype.rollback = function () { + var _a = this, target = _a.target, key = _a.key, original = _a.original; + if (original) { + target[key] = original; + } + else { + delete target[key]; + } + }; + Override.all = []; + return Override; + }()); + exports.Override = Override; }); diff --git a/d2bs/kolbot/libs/modules/Promise.js b/d2bs/kolbot/libs/modules/Promise.js index 61aa0282a..71a17a141 100644 --- a/d2bs/kolbot/libs/modules/Promise.js +++ b/d2bs/kolbot/libs/modules/Promise.js @@ -5,95 +5,95 @@ */ (function (module, require) { - const Worker = require("Worker"); - /** - * - * @param {function({resolve},{reject}):boolean} callback - * @constructor - */ - const Promise = module.exports = function (callback) { - typeof Promise.__promiseCounter === "undefined" && (Promise.__promiseCounter = 0); - - this._after = []; - this._catchers = []; - this._finally = []; - this.stopped = false; - this.value = this; - const self = this; - - const final = function () { - typeof self._finally !== "undefined" && self._finally.forEach(function (callback) { - return callback(self.value); - }); - }, resolve = function (result) { - self.value = result; - self.stopped = true; - typeof self._after !== "undefined" && self._after.forEach(callback => Worker.push(function () { - return callback(result); - })); - Worker.push(final); - }, - reject = function (e) { - self.stopped = true; - typeof self._catchers !== "undefined" && self._catchers.forEach(callback => Worker.push(function () { - return callback(e); - })); - if (!Array.isArray(self._catchers) || !self._catchers.length) Misc.errorReport(e || (new Error)); - Worker.push(final); - }; - - - if (this.__proto__.constructor !== Promise) { - print((new Error).stack); - throw new Error("Promise must be called with 'new' operator!"); - } - - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf - // override the valueOf as primitive value function - this.valueOf = () => self.stopped ? self.value : self; - - this.then = function (handler) { - typeof self._after !== "undefined" && (self._after = []); - self._after.push(handler); - - return self; - }; - - this.catch = function (handler) { - typeof self._catchers !== "undefined" && (self._catchers = []); - self._catchers.push(handler); - - return self; - }; - - this.finally = function (handler) { - typeof self._finally !== "undefined" && (self._finally = []); - self._finally.push(handler); - - return self; - }; - - Worker.runInBackground["promise__" + (++Promise.__promiseCounter)] = function () { - try { - callback(resolve, reject); - } catch (e) { - reject(e); - } - return !self.stopped; - }; - }; - - /** - * @description wait for an array of promises to be ran. - * @param promises Array - */ - Promise.all = function (promises) { - while (promises.some(x => !x.stopped)) { - delay(); - } - }; - - Promise.allSettled = (promises) => new Promise(resolve => promises.every(x => x.stopped) && resolve(promises)); + const Worker = require("Worker"); + /** + * + * @param {function({resolve},{reject}):boolean} callback + * @constructor + */ + const Promise = module.exports = function (callback) { + typeof Promise.__promiseCounter === "undefined" && (Promise.__promiseCounter = 0); + + this._after = []; + this._catchers = []; + this._finally = []; + this.stopped = false; + this.value = this; + const self = this; + + const final = function () { + typeof self._finally !== "undefined" && self._finally.forEach(function (callback) { + return callback(self.value); + }); + }, resolve = function (result) { + self.value = result; + self.stopped = true; + typeof self._after !== "undefined" && self._after.forEach(callback => Worker.push(function () { + return callback(result); + })); + Worker.push(final); + }, + reject = function (e) { + self.stopped = true; + typeof self._catchers !== "undefined" && self._catchers.forEach(callback => Worker.push(function () { + return callback(e); + })); + if (!Array.isArray(self._catchers) || !self._catchers.length) Misc.errorReport(e || (new Error)); + Worker.push(final); + }; + + + if (this.__proto__.constructor !== Promise) { + print((new Error).stack); + throw new Error("Promise must be called with 'new' operator!"); + } + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf + // override the valueOf as primitive value function + this.valueOf = () => self.stopped ? self.value : self; + + this.then = function (handler) { + typeof self._after !== "undefined" && (self._after = []); + self._after.push(handler); + + return self; + }; + + this.catch = function (handler) { + typeof self._catchers !== "undefined" && (self._catchers = []); + self._catchers.push(handler); + + return self; + }; + + this.finally = function (handler) { + typeof self._finally !== "undefined" && (self._finally = []); + self._finally.push(handler); + + return self; + }; + + Worker.runInBackground["promise__" + (++Promise.__promiseCounter)] = function () { + try { + callback(resolve, reject); + } catch (e) { + reject(e); + } + return !self.stopped; + }; + }; + + /** + * @description wait for an array of promises to be ran. + * @param promises Array + */ + Promise.all = function (promises) { + while (promises.some(x => !x.stopped)) { + delay(); + } + }; + + Promise.allSettled = (promises) => new Promise(resolve => promises.every(x => x.stopped) && resolve(promises)); })(module, require); diff --git a/d2bs/kolbot/libs/modules/SimpleParty.js b/d2bs/kolbot/libs/modules/SimpleParty.js deleted file mode 100644 index 5c3eb3939..000000000 --- a/d2bs/kolbot/libs/modules/SimpleParty.js +++ /dev/null @@ -1,155 +0,0 @@ -(function (module, require) { - const Worker = require("Worker"); - const NO_PARTY = 65535; - const PARTY_MEMBER = 1; - const ACCEPTABLE = 2; - const INVITED = 4; - const BUTTON_INVITE_ACCEPT_CANCEL = 2; - const BUTTON_LEAVE_PARTY = 3; - const BUTTON_HOSTILE = 4; - - print("ÿc2Kolbotÿc0 :: Simple party running"); - - const SimpleParty = {}; - - SimpleParty.biggestPartyId = function () { - let uniqueParties = []; - // Or add it and return the value - for (let party = getParty(); party.getNext();) { - ( - // Find this party - uniqueParties.find(u => u.partyid === party.partyid) - // Or create an instance of it - || ((uniqueParties.push({ - partyid: party.partyid, - used: 0 - }) && false) || uniqueParties[uniqueParties.length - 1]) - // Once we have the party object, increase field used - ).used++; - } - - // Filter out no party, if another party is found - uniqueParties.some(u => u.partyid !== NO_PARTY) && (uniqueParties = uniqueParties.filter(u => u.partyid !== NO_PARTY)); - - return (uniqueParties.sort((a, b) => b.used - a.used /*b-a = desc*/).first() || {partyid: -1}).partyid; - }; - - SimpleParty.acceptFirst = function () { - const toMd5Int = what => parseInt(md5(what).substr(0, 4), 16); //ToDo; do something with game number here - const names = []; - for (let party = getParty(); party.getNext();) { - if (party.partyid === NO_PARTY) { - names.push(party.name); - } - } - return names.filter(n => n !== me.name /*cant accept yourself ;)*/).sort((a, b) => toMd5Int(a) - toMd5Int(b)).first(); - }; - - SimpleParty.getFirstPartyMember = function () { - let myPartyId = ((() => (getParty() || {partyid: 0}).partyid))(); - for (let party = getParty(); party.getNext();) { - if (party.partyid === myPartyId && party.name !== me.charname) { - return party; - } - } - return undefined; - }; - - SimpleParty.invite = function (name) { - - for (let party = getParty(); party.getNext();) { - // If party member is - if (party.name === name && party.partyflag !== ACCEPTABLE && party.partyflag !== PARTY_MEMBER && party.partyid === NO_PARTY) { - clickParty(party, BUTTON_INVITE_ACCEPT_CANCEL); // Press the invite button - return true; - } - } - return false; - }; - - SimpleParty.timer = 0; - - if (getScript(true).name.toLowerCase() === "default.dbj") { - (Worker.runInBackground.party = (function () {// For now, we gonna do this in game with a single party - SimpleParty.timer = getTickCount(); - return function () { - // Set timer back on 3 seconds, or reset it and continue - if ((getTickCount() - SimpleParty.timer) < 3000 || (SimpleParty.timer = getTickCount()) && false) { - return true; - } - - if (Config.PublicMode !== true) { // Public mode 1/2/3 dont count. This is SimplyParty - return true; - } - - const myPartyId = ((() => (getParty() || {partyid: 0}).partyid))(); - if (!myPartyId) { - return true; // party ain't up yet - } - - const biggestPartyId = SimpleParty.biggestPartyId(); - - for (let party = getParty(), acceptFirst; party && party.getNext();) { - if (!(party && typeof party === "object")) { - continue; - } - - if (!(party.hasOwnProperty("life"))) { - continue; - } // Somehow not a party member - - // Deal with inviting - if ( // If no party is formed, or im member of the biggest party - party.partyflag !== INVITED && // Already invited - party.partyflag !== ACCEPTABLE && // Need to accept invite, so cant invite - party.partyflag !== PARTY_MEMBER && // cant party again with soemone - party.partyid === NO_PARTY // Can only invite someone that isnt in a party - && ( // If im not in a party, only if there is no party - myPartyId === NO_PARTY && biggestPartyId === NO_PARTY - // OR, if im part of the biggest party - || biggestPartyId === myPartyId - ) - ) { - // if player isn't invited, invite - clickParty(party, BUTTON_INVITE_ACCEPT_CANCEL); - } - - // Deal with accepting - if ( - party.partyflag === ACCEPTABLE - && myPartyId === NO_PARTY // Can only accept if we are not in a party - && party.partyid === biggestPartyId // Only accept if it is an invite to the biggest party - ) { - // Try to make all bots accept the same char first, to avoid confusion with multiple parties - if (biggestPartyId === NO_PARTY) { - // if acceptFirst isnt set, create it (to cache it, yet generate on demand) - if (!acceptFirst) { - acceptFirst = SimpleParty.acceptFirst(); - } - - if (acceptFirst !== party.name) { - continue; // Ignore party acceptation - } - } - - clickParty(party, BUTTON_INVITE_ACCEPT_CANCEL); - } - - // Deal with being in the wrong party. (we want to be in the biggest party) - if ( - party.partyflag === PARTY_MEMBER // We are in the same party - && biggestPartyId !== party.partyid // yet this party isnt the biggest party available - && biggestPartyId !== NO_PARTY // And the biggest party isnt no party - ) { - clickParty(party, BUTTON_LEAVE_PARTY); - } - - } - return true; - }; - })()); - } - - module.exports = SimpleParty; - -})(module, require); diff --git a/d2bs/kolbot/libs/modules/Socket.js b/d2bs/kolbot/libs/modules/Socket.js index 7dbadabad..6c4ea5065 100644 --- a/d2bs/kolbot/libs/modules/Socket.js +++ b/d2bs/kolbot/libs/modules/Socket.js @@ -5,53 +5,53 @@ (function (module, require, buildinSock) { - const Worker = require("Worker"); - const Events = require("Events"); - - /** @constructor Socket*/ - function Socket(hostname, port) { - typeof Socket.__socketCounter === "undefined" && (Socket.__socketCounter = 0); - this.connected = false; - const myEvents = new Events; - this.connect = () => (this.socket = buildinSock.open(hostname, port)) && (this.connected = true) && this; - - this.on = myEvents.on; - this.off = myEvents.off; - this.once = myEvents.once; - - const close = () => { - this.socket = null; - this.connected = false; - myEvents.emit("close", this); - }; - - this.recv = () => { - if (!this.connected || !this.socket || !this.socket.readable) return; - - const data = (() => { - try { - return this.socket.read(); - } catch (e) { - close(); - } - return undefined; - })(); - - data && myEvents.emit("data", data); - }; - - this.send = (data) => { - if (!data || !this.socket) return; - - try { - this.socket.send(data); - } catch (e) { - close(); - } - }; - - Worker.runInBackground["__socket__" + (++Socket.__socketCounter)] = () => this.recv() || this.send() || true; - } - - module.exports = Socket; + const Worker = require("Worker"); + const Events = require("Events"); + + /** @constructor Socket*/ + function Socket(hostname, port) { + typeof Socket.__socketCounter === "undefined" && (Socket.__socketCounter = 0); + this.connected = false; + const myEvents = new Events; + this.connect = () => (this.socket = buildinSock.open(hostname, port)) && (this.connected = true) && this; + + this.on = myEvents.on; + this.off = myEvents.off; + this.once = myEvents.once; + + const close = () => { + this.socket = null; + this.connected = false; + myEvents.emit("close", this); + }; + + this.recv = () => { + if (!this.connected || !this.socket || !this.socket.readable) return; + + const data = (() => { + try { + return this.socket.read(); + } catch (e) { + close(); + } + return undefined; + })(); + + data && myEvents.emit("data", data); + }; + + this.send = (data) => { + if (!data || !this.socket) return; + + try { + this.socket.send(data); + } catch (e) { + close(); + } + }; + + Worker.runInBackground["__socket__" + (++Socket.__socketCounter)] = () => this.recv() || this.send() || true; + } + + module.exports = Socket; }).call(null, module, require, Socket); diff --git a/d2bs/kolbot/libs/modules/Team.js b/d2bs/kolbot/libs/modules/Team.js index 4fa1488d6..773df9db5 100644 --- a/d2bs/kolbot/libs/modules/Team.js +++ b/d2bs/kolbot/libs/modules/Team.js @@ -5,164 +5,168 @@ */ !isIncluded("require.js") && include("require.js"); // load the require.js -(function (threadType) { - - const others = []; - - const myEvents = new (require("Events")); - const Worker = require("Worker"); - const Messaging = require("Messaging"); - const defaultCopyDataMode = 0xC0FFFEE; - - const Team = { - on: myEvents.on, - off: myEvents.off, - once: myEvents.once, - send: function (who, what, mode = defaultCopyDataMode) { - what.profile = me.windowtitle; - return sendCopyData(null, who, mode || defaultCopyDataMode, JSON.stringify(what)); - }, - broadcast: (what, mode) => { - what.profile = me.windowtitle; - return others.forEach(other => sendCopyData(null, other.profile, mode || defaultCopyDataMode, JSON.stringify(what))); - }, - broadcastInGame: (what, mode) => { - what.profile = me.windowtitle; - others.forEach(function (other) { - for (const party = getParty(); party && party.getNext();) { - typeof party === "object" && party && party.hasOwnProperty("name") && party.name === other.name && sendCopyData(null, other.profile, mode || defaultCopyDataMode, JSON.stringify(what)); - } - }); - } - }; - - if (threadType === "thread") { - print("ÿc2Kolbotÿc0 :: Team thread started"); - - Messaging.on("Team", data => { - return typeof data === "object" && data && data.hasOwnProperty("call") && Team[data.call].apply(Team, data.hasOwnProperty("args") && data.args || []); - }); - - Worker.runInBackground.copydata = (new function () { - const workBench = []; - const updateOtherProfiles = function () { - const fileList = dopen("data/").getFiles(); - fileList && fileList.forEach(function (filename) { - let newContent, obj, profile = filename.split("").reverse().splice(5).reverse().join(""); // strip the last 5 chars (.json) = 5 chars - - - if (profile === me.windowtitle || !filename.endsWith(".json")) return; - try { - newContent = FileTools.readText("data/" + filename); - if (!newContent) return; // no content - } catch (e) { - print("Can't read: `" + "data/" + filename + "`"); - } - - - try { // try to convert to an object - obj = JSON.parse(newContent); - } catch (e) { - return; - } - - let other; - for (let i = 0, tmp; i < others.length; i++) { - tmp = others[i]; - if (tmp.hasOwnProperty("profile") && tmp.profile === profile) { - other = tmp; - break; - } - } - - if (!other) { - others.push(obj); - other = others[others.length - 1]; - } - - other.profile = profile; - Object.keys(obj).map(key => other[key] = obj[key]); - }); - }; - addEventListener("copydata", (mode, data) => workBench.push({mode: mode, data: data})); - - let timer = getTickCount() - Math.round((Math.random() * 2500) + 1000); // start with 3 seconds off - this.update = function () { - if (!((getTickCount() - timer) < 3500)) { // only ever 3 seconds update the entire team - timer = getTickCount(); - updateOtherProfiles(); - } - - // nothing to do? next - if (!workBench.length) return true; - const emit = workBench.splice(0, workBench.length).map( - function (obj) { // Convert to object, if we can - let data = obj.data; - try { - data = JSON.parse(data); - } catch (e) { - /* Dont care if we cant*/ - return {}; - } - return {mode: obj.mode, data: data}; - }) - .filter(obj => typeof obj === "object" && obj) - .filter(obj => typeof obj.data === "object" && obj.data) - .filter(obj => typeof obj.mode === "number" && obj.mode); - emit.length && Messaging.send({ - Team: { - emit: emit - } - }); - return true; // always, to keep looping; - }; - }).update; - - while (true) { - delay(1000); - } - - } else { - (function (module, require) { - const localTeam = module.exports = Team; // <-- some get overridden, but this still works for auto completion in your IDE - - // Filter out all Team functions that are linked to myEvent - Object.keys(Team) - .filter(key => !myEvents.hasOwnProperty(key) && typeof Team[key] === "function") - .forEach(key => { - return module.exports[key] = (...args) => { - return Messaging.send({ - Team: { - call: key, - args: args - } - }); - }; - }); - - Messaging.on("Team", msg => - typeof msg === "object" - && msg - && msg.hasOwnProperty("emit") - && Array.isArray(msg.emit) - && msg.emit.forEach(function (obj) { - - // Registered events on the mode - myEvents.emit(obj.mode, obj.data); - - // Only if data is set - typeof obj.data === "object" && obj.data && Object.keys(obj.data).forEach(function (item) { - - // For each item in the object, trigger an event - obj.data[item].reply = (what, mode) => localTeam.send(obj.data.profile, what, mode); - - // Registered events on a data item - myEvents.emit(item, obj.data[item]); - }); - }) - ); - })(module, require); - } - - -})(getScript.startAsThread()); +(function (threadType, globalThis) { + const others = []; + + const myEvents = new (require("Events")); + const Worker = require("Worker"); + const Messaging = require("Messaging"); + const defaultCopyDataMode = 0xC0FFFEE; + + const Team = { + on: myEvents.on, + off: myEvents.off, + once: myEvents.once, + send: function (who, what, mode = defaultCopyDataMode) { + what.profile = me.windowtitle; + return sendCopyData(null, who, mode || defaultCopyDataMode, JSON.stringify(what)); + }, + broadcast: (what, mode) => { + what.profile = me.windowtitle; + return others + .forEach(other => sendCopyData(null, other.profile, mode || defaultCopyDataMode, JSON.stringify(what))); + }, + broadcastInGame: (what, mode) => { + what.profile = me.windowtitle; + others.forEach(function (other) { + for (const party = getParty(); party && party.getNext();) { + if (typeof party === "object" && party && party.hasOwnProperty("name") && party.name === other.name) { + sendCopyData(null, other.profile, mode || defaultCopyDataMode, JSON.stringify(what)); + } + } + }); + } + }; + + if (threadType === "thread") { + print("ÿc2Kolbotÿc0 :: Team thread started"); + + Messaging.on("Team", data => ( + typeof data === "object" && data + && data.hasOwnProperty("call") + && Team[data.call].apply(Team, data.hasOwnProperty("args") + && data.args || []) + )); + + Worker.runInBackground.copydata = (new function () { + const workBench = []; + const updateOtherProfiles = function () { + const fileList = dopen("data/").getFiles(); + fileList && fileList.forEach(function (filename) { + let newContent, obj, profile = filename.split("").reverse().splice(5).reverse().join(""); // strip the last 5 chars (.json) = 5 chars + + + if (profile === me.windowtitle || !filename.endsWith(".json")) return; + try { + newContent = FileTools.readText("data/" + filename); + if (!newContent) return; // no content + } catch (e) { + print("Can't read: `" + "data/" + filename + "`"); + } + + + try { // try to convert to an object + obj = JSON.parse(newContent); + } catch (e) { + return; + } + + let other; + for (let i = 0, tmp; i < others.length; i++) { + tmp = others[i]; + if (tmp.hasOwnProperty("profile") && tmp.profile === profile) { + other = tmp; + break; + } + } + + if (!other) { + others.push(obj); + other = others[others.length - 1]; + } + + other.profile = profile; + Object.keys(obj).map(key => other[key] = obj[key]); + }); + }; + addEventListener("copydata", (mode, data) => workBench.push({ mode: mode, data: data })); + + let timer = getTickCount() - Math.round((Math.random() * 2500) + 1000); // start with 3 seconds off + this.update = function () { + if (!((getTickCount() - timer) < 3500)) { // only ever 3 seconds update the entire team + timer = getTickCount(); + updateOtherProfiles(); + } + + // nothing to do? next + if (!workBench.length) return true; + const emit = workBench.splice(0, workBench.length).map( + function (obj) { // Convert to object, if we can + let data = obj.data; + try { + data = JSON.parse(data); + } catch (e) { + /* Dont care if we cant*/ + return {}; + } + return { mode: obj.mode, data: data }; + }) + .filter(obj => typeof obj === "object" && obj) + .filter(obj => typeof obj.data === "object" && obj.data) + .filter(obj => typeof obj.mode === "number" && obj.mode); + emit.length && Messaging.send({ + Team: { + emit: emit + } + }); + return true; // always, to keep looping; + }; + }).update; + + let quiting = false; + addEventListener("scriptmsg", data => data === "quit" && (quiting = true)); + + // eslint-disable-next-line dot-notation + globalThis["main"] = function () { + while (!quiting) delay(3); + //@ts-ignore + getScript(true).stop(); + }; + } else { + (function (module) { + const localTeam = module.exports = Team; // <-- some get overridden, but this still works for auto completion in your IDE + + // Filter out all Team functions that are linked to myEvent + Object.keys(Team) + .filter(key => !myEvents.hasOwnProperty(key) && typeof Team[key] === "function") + .forEach(key => module.exports[key] = (...args) => Messaging.send({ + Team: { + call: key, + args: args + } + })); + + Messaging.on("Team", msg => + typeof msg === "object" + && msg + && msg.hasOwnProperty("emit") + && Array.isArray(msg.emit) + && msg.emit.forEach(function (obj) { + + // Registered events on the mode + myEvents.emit(obj.mode, obj.data); + + // Only if data is set + typeof obj.data === "object" && obj.data && Object.keys(obj.data).forEach(function (item) { + + // For each item in the object, trigger an event + obj.data[item].reply = (what, mode) => localTeam.send(obj.data.profile, what, mode); + + // Registered events on a data item + myEvents.emit(item, obj.data[item]); + }); + }) + ); + })(module); + } +})(getScript.startAsThread(), [].filter.constructor("return this")()); diff --git a/d2bs/kolbot/libs/modules/UnitInfo.js b/d2bs/kolbot/libs/modules/UnitInfo.js new file mode 100644 index 000000000..115a298af --- /dev/null +++ b/d2bs/kolbot/libs/modules/UnitInfo.js @@ -0,0 +1,260 @@ +/* eslint-disable max-len */ +/** +* @filename UnitInfo.js +* @author kolton, theBGuy +* @desc Display unit info +* +*/ + +include("core/prototypes.js"); + +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + const v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define([], factory); + } else { + root.UnitInfo = factory(); + } +}(this, function () { + /** + * @constructor + */ + function UnitInfo () { + /** + * screen coordinate for info box + * @private + * @type {number} + */ + this.x = 200; + + /** + * screen coordinate for info box + * @private + * @type {number} + */ + this.y = 250; + + /** + * @private + * @type {any[]} + */ + this.hooks = []; + + /** + * @private + * @type {number | null} + */ + this.currentGid = null; + + /** + * @private + * @type {boolean} + */ + this.cleared = true; + + /** + * @private + * @type {{ x: number, y: number }} + */ + this.resfix = { + x: (me.screensize ? 0 : -160), + y: (me.screensize ? 0 : -120) + }; + } + + /** + * Create info based on unit type + * @param {Unit} unit + */ + UnitInfo.prototype.createInfo = function (unit) { + if (typeof unit === "undefined") { + this.remove(); + } else { + // same unit, no changes so skip + if (this.currentGid === unit.gid) return; + // some hooks left over, remove them + this.hooks.length > 0 && this.remove(); + // set new gid + this.currentGid = unit.gid; + + switch (unit.type) { + case sdk.unittype.Player: + this.playerInfo(unit); + + break; + case sdk.unittype.Monster: + this.monsterInfo(unit); + + break; + case sdk.unittype.Object: + case sdk.unittype.Stairs: + this.objectInfo(unit); + + break; + case sdk.unittype.Item: + this.itemInfo(unit); + + break; + } + } + + }; + + /** + * Check that selected unit is still valid + */ + UnitInfo.prototype.check = function () { + // make sure things got cleaned up properly if we are supposedly cleared + if (this.hooks.length === 0 && this.cleared) return; + // don't deal with this when an item is on our cursor + if (me.itemoncursor) return; + let unit = Game.getSelectedUnit(); + if (typeof unit === "undefined" || unit.gid !== this.currentGid) { + this.remove(); + } + }; + + /** + * @private + * @param {Player} unit + */ + UnitInfo.prototype.playerInfo = function (unit) { + let string; + let frameXsize = 0; + let frameYsize = 20; + let items = unit.getItemsEx(); + + this.hooks.push(new Text("Classid: ÿc0" + unit.classid, this.x, this.y, 4, 13, 2)); + + if (items.length) { + this.hooks.push(new Text("Equipped items:", this.x, this.y + 15, 4, 13, 2)); + frameYsize += 15; + + items.forEach((item, i) => { + if (item.runeword) { + string = item.fname.split("\n")[1] + "ÿc0 " + item.fname.split("\n")[0]; + } else { + string = Item.color(item, false) + (item.quality > sdk.items.quality.Magic && item.identified + ? item.fname.split("\n").reverse()[0].replace("ÿc4", "") + : item.name); + } + + this.hooks.push(new Text(string, this.x, this.y + (i + 2) * 15, 0, 13, 2)); + string.length > frameXsize && (frameXsize = string.length); + frameYsize += 15; + }); + } + + this.cleared = false; + + this.hooks.push(new Box(this.x + 2, this.y - 15, Math.round(frameXsize * 7.5) - 4, frameYsize, 0x0, 1, 2)); + this.hooks.push(new Frame(this.x, this.y - 15, Math.round(frameXsize * 7.5), frameYsize, 2)); + this.hooks[this.hooks.length - 2].zorder = 0; + }; + + /** + * @private + * @param {Monster} unit + */ + UnitInfo.prototype.monsterInfo = function (unit) { + let frameYsize = 125; + + this.hooks.push(new Text("Classid: ÿc0" + unit.classid, this.x, this.y, 4, 13, 2)); + this.hooks.push(new Text("HP percent: ÿc0" + Math.round(unit.hp * 100 / 128), this.x, this.y + 15, 4, 13, 2)); + this.hooks.push(new Text("Fire resist: ÿc0" + unit.getStat(sdk.stats.FireResist), this.x, this.y + 30, 4, 13, 2)); + this.hooks.push(new Text("Cold resist: ÿc0" + unit.getStat(sdk.stats.ColdResist), this.x, this.y + 45, 4, 13, 2)); + this.hooks.push(new Text("Lightning resist: ÿc0" + unit.getStat(sdk.stats.LightResist), this.x, this.y + 60, 4, 13, 2)); + this.hooks.push(new Text("Poison resist: ÿc0" + unit.getStat(sdk.stats.PoisonResist), this.x, this.y + 75, 4, 13, 2)); + this.hooks.push(new Text("Physical resist: ÿc0" + unit.getStat(sdk.stats.DamageResist), this.x, this.y + 90, 4, 13, 2)); + this.hooks.push(new Text("Magic resist: ÿc0" + unit.getStat(sdk.stats.MagicResist), this.x, this.y + 105, 4, 13, 2)); + + this.cleared = false; + + this.hooks.push(new Box(this.x + 2, this.y - 15, 136, frameYsize, 0x0, 1, 2)); + this.hooks.push(new Frame(this.x, this.y - 15, 140, frameYsize, 2)); + this.hooks[this.hooks.length - 2].zorder = 0; + }; + + /** + * @private + * @param {ItemUnit} unit + */ + UnitInfo.prototype.itemInfo = function (unit) { + let xpos = 60; + let ypos = (me.getMerc() ? 80 : 20) + (-1 * this.resfix.y); + let frameYsize = 65; + + this.hooks.push(new Text("Code: ÿc0" + unit.code, xpos, ypos + 0, 4, 13, 2)); + this.hooks.push(new Text("Classid: ÿc0" + unit.classid, xpos, ypos + 15, 4, 13, 2)); + this.hooks.push(new Text("Item Type: ÿc0" + unit.itemType, xpos, ypos + 30, 4, 13, 2)); + this.hooks.push(new Text("Item level: ÿc0" + unit.ilvl, xpos, ypos + 45, 4, 13, 2)); + + this.cleared = false; + this.socketedItems = unit.getItemsEx(); + + if (this.socketedItems.length) { + this.hooks.push(new Text("Socketed with:", xpos, ypos + 60, 4, 13, 2)); + frameYsize += 15; + + for (let i = 0; i < this.socketedItems.length; i += 1) { + this.hooks.push(new Text(this.socketedItems[i].fname.split("\n").reverse().join(" "), xpos, ypos + (i + 5) * 15, 0, 13, 2)); + + frameYsize += 15; + } + } + + if (unit.magic && unit.identified) { + this.hooks.push(new Text("Prefix: ÿc0" + unit.prefixnum, xpos, ypos + frameYsize - 5, 4, 13, 2)); + this.hooks.push(new Text("Suffix: ÿc0" + unit.suffixnum, xpos, ypos + frameYsize + 10, 4, 13, 2)); + + frameYsize += 30; + } + + if (unit.runeword) { + this.hooks.push(new Text("Prefix: ÿc0" + unit.prefixnum, xpos, ypos + frameYsize - 5, 4, 13, 2)); + + frameYsize += 15; + } + + this.hooks.push(new Box(xpos + 2, ypos - 15, 116, frameYsize, 0x0, 1, 2)); + this.hooks.push(new Frame(xpos, ypos - 15, 120, frameYsize, 2)); + this.hooks[this.hooks.length - 2].zorder = 0; + }; + + /** + * @private + * @param {ObjectUnit} unit + */ + UnitInfo.prototype.objectInfo = function (unit) { + let frameYsize = 35; + + this.hooks.push(new Text("Type: ÿc0" + unit.type, this.x, this.y, 4, 13, 2)); + this.hooks.push(new Text("Classid: ÿc0" + unit.classid, this.x, this.y + 15, 4, 13, 2)); + + if (!!unit.objtype) { + this.hooks.push(new Text((unit.name.toLowerCase().includes("shrine") ? "Type" : "Destination") + ": ÿc0" + unit.objtype, this.x, this.y + 30, 4, 13, 2)); + + frameYsize += 15; + } + + this.cleared = false; + + this.hooks.push(new Box(this.x + 2, this.y - 15, 116, frameYsize, 0x0, 1, 2)); + this.hooks.push(new Frame(this.x, this.y - 15, 120, frameYsize, 2)); + this.hooks[this.hooks.length - 2].zorder = 0; + }; + + UnitInfo.prototype.remove = function () { + while (this.hooks.length > 0) { + this.hooks.shift().remove(); + } + + this.currentGid = null; + this.cleared = true; + }; + + return UnitInfo; +})); + diff --git a/d2bs/kolbot/libs/modules/Worker.js b/d2bs/kolbot/libs/modules/Worker.js index e35be283f..51f431324 100644 --- a/d2bs/kolbot/libs/modules/Worker.js +++ b/d2bs/kolbot/libs/modules/Worker.js @@ -3,104 +3,141 @@ * @author Jaenster */ -(function(module, require) { - const recursiveCheck = function (stackNumber) { - let stack = new Error().stack.match(/[^\r\n]+/g); - let functionName = stack[stackNumber || 1].substr(0, stack[stackNumber || 1].indexOf("@")); - - for (let i = (stackNumber || 1) + 1; i < stack.length; i++) { - let curFunc = stack[i].substr(0, stack[i].indexOf("@")); - - if (functionName === curFunc) { - return true; - } // recursion appeared - } - - return false; - }; - - let Worker = module.exports = new (function () { - let work = [], workLowPrio = [], self = this; - - this.push = function (newWork) { - return work.push(newWork); - }; - - this.pushLowPrio = function (newWork) { - return workLowPrio.push(newWork); - }; - - const checker = function (val) { - try { - !self.workDisabled && val.length && val.splice(0, val.length).forEach(self.work); - } catch (error) { - if (!error.message.endsWith("too much recursion")) { - throw error; - } // keep on throwing - - print("[ÿc9Warningÿc0] Too much recursion"); - } - }; - - this.check = function () { - return checker(work); - }; - - this.checkLowPrio = function () { - return checker(workLowPrio); - }; - - this.work = function (what) { - return typeof what === "function" && what(self) || (Array.isArray(what) && what.forEach(self.work)); - }; - - /** - * - * @param {function({Worker}):boolean} callback - */ - this.runInBackground = new Proxy({processes: {}}, { - set: function (target, name, callback) { - target.processes[name] = {callback: callback, running: true}; - - let proxyCallback = function () { - target.processes.running = (callback() && self.pushLowPrio(proxyCallback) > -1); - if (!target.processes.running) { - delete target.processes[name]; - } - }; - - self.pushLowPrio(proxyCallback); - }, - }); - - global.await = function (promise) { - while (delay() && !promise.stopped) { - // - } - - return promise.value; - }; - - this.workDisabled = 0; - - global._delay = delay; // The original delay function - - // Override the delay function, to check for background work while we wait anyway - global.delay = function (amount) { - - let recursive = recursiveCheck(); - let start = getTickCount(); - amount = amount || 0; - - do { - self.check(); - global._delay(getTickCount() - start > 3 ? 3 : 1); - !recursive && self.checkLowPrio(); - } while (getTickCount() - start <= amount); - - return true; // always return true - }; - - this.recursiveCheck = recursiveCheck; - })(); -})(module, require); +(function (module) { + const recursiveCheck = function (stackNumber) { + let stack = new Error().stack.match(/[^\r\n]+/g); + let functionName = stack[stackNumber || 1].substr(0, stack[stackNumber || 1].indexOf("@")); + + for (let i = (stackNumber || 1) + 1; i < stack.length; i++) { + let curFunc = stack[i].substr(0, stack[i].indexOf("@")); + + if (functionName === curFunc) { + return true; + } // recursion appeared + } + + return false; + }; + + const Worker = new (function () { + const self = this; + const work = []; + const workLowPrio = []; + /** @private */ + this.workDisabled = false; + + this.push = function (newWork) { + return work.push(newWork); + }; + + this.pushLowPrio = function (newWork) { + return workLowPrio.push(newWork); + }; + + const checker = function (val) { + if (self.workDisabled) return; + try { + val.length && val.splice(0, val.length).forEach(self.work); + } catch (error) { + if (!error.message.endsWith("too much recursion")) { + throw error; + } // keep on throwing + + print("[ÿc9Warningÿc0] Too much recursion"); + } + }; + + this.check = function () { + return checker(work); + }; + + this.checkLowPrio = function () { + return checker(workLowPrio); + }; + + this.work = function (what) { + return typeof what === "function" && what(self) || (Array.isArray(what) && what.forEach(self.work)); + }; + + /** + * @param {function({Worker}):boolean} callback + */ + this.runInBackground = new Proxy({ processes: {} }, { + set: function (target, name, callback) { + if (target.processes.hasOwnProperty(name)) { + throw new Error("Process " + name + " already exists."); + } + target.processes[name] = { + callback: callback, + running: true, + name: name + }; + let proxyCallback = function () { + if (!target.processes[name]) return; + target.processes[name].running = callback(); + if (!target.processes[name].running) { + delete target.processes[name]; + } else { + self.pushLowPrio(proxyCallback); + } + }; + self.pushLowPrio(proxyCallback); + }, + deleteProperty: function (target, name) { + if (!target.processes.hasOwnProperty(name)) { + throw new Error("Process " + name + " does not exists."); + } + target.processes[name].running = false; + delete target.processes[name]; + return true; + } + }); + + this.stopProcess = function (name) { + if (typeof self.runInBackground === "undefined" + || typeof self.runInBackground.processes === "undefined") { + return; + } + if (typeof self.runInBackground.processes[name] === "undefined") { + return; + } + delete self.runInBackground.processes[name]; + }; + + /** @param {Promise<*>} promise */ + global.await = function (promise) { + while (delay(1) && !promise.stopped) { + // + } + return promise.value; + }; + + global._delay = delay; // The original delay function + + /** + * Just makes it easier to peform a delay + * @param {number} amount + */ + this.timeout = function (amount) { + return global._delay(amount); + }; + + // Override the delay function, to check for background work while we wait anyway + global.delay = function (amount) { + let recursive = recursiveCheck(); + let start = getTickCount(); + amount = amount || 0; + + do { + self.check(); + global._delay(getTickCount() - start > 3 ? 3 : 1); + !recursive && self.checkLowPrio(); + } while (getTickCount() - start <= amount); + + return true; // always return true + }; + + this.recursiveCheck = recursiveCheck; + })(); + module.exports = Worker; +})(module); diff --git a/d2bs/kolbot/libs/modules/sdk.js b/d2bs/kolbot/libs/modules/sdk.js index dbda6f715..1870d538b 100644 --- a/d2bs/kolbot/libs/modules/sdk.js +++ b/d2bs/kolbot/libs/modules/sdk.js @@ -3,4880 +3,5207 @@ * @author jaenster, theBGuy * @desc development tool for readability * @format sdk.objprop.objprop.ObjProp (excludes functions which use sdk.objprop.camelCase) +* @type UMD module * */ -// todo: break this up to make more sense. Example -/* -item: { - mode: { +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + let v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define([], factory); + } else { + root.sdk = factory(); + } +}(this, function () { + "use strict"; + /** + * @exports sdk + */ + const sdk = { + waypoints: { + Ids: [119, 145, 156, 157, 237, 238, 288, 323, 324, 398, 402, 429, 494, 496, 511, 539], + Act1: [0x01, 0x03, 0x04, 0x05, 0x06, 0x1b, 0x1d, 0x20, 0x23], + Act2: [0x28, 0x30, 0x2a, 0x39, 0x2b, 0x2c, 0x34, 0x4a, 0x2e], + Act3: [0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x65], + Act4: [0x67, 0x6a, 0x6b], + Act5: [0x6d, 0x6f, 0x70, 0x71, 0x73, 0x7b, 0x75, 0x76, 0x81] + }, - }, - class: { + difficulty: { + Normal: 0, + Nightmare: 1, + Hell: 2, + Difficulties: ["Normal", "Nightmare", "Hell"], - }, - quality: { + nameOf: function (difficulty) { + if (difficulty === undefined || typeof difficulty !== "number") return false; + if (difficulty < 0 || difficulty > 2) return false; + return ["Normal", "Nightmare", "Hell"][difficulty]; + } + }, - }, - type: { + party: { + NoParty: 65535, + flag: { + Invite: 0, + InParty: 1, + Accept: 2, + Cancel: 4 + }, + controls: { + Hostile: 1, + InviteOrCancel: 2, + Leave: 3, + Ignore: 4, + Squelch: 5, + }, + }, - }, - classid: { + clicktypes: { + click: { + item: { + Left: 0, + Right: 1, + ShiftLeft: 2, // For belt + MercFromBelt: 3, // For belt + Mercenary: 4 // Give to merc + }, + map: { + LeftDown: 0, + LeftHold: 1, + LeftUp: 2, + RightDown: 3, + RightHold: 4, + RightUp: 5, + }, + }, + shift: { + NoShift: 0, + Shift: 1 + } + }, + /** + * @notes + * - I get cursortype 3 when I swap an item that is equipped, but I get 4 if I just unequip an item + * - I get cursortype 3 if I pick an item from my inventory then hover it over another item + * - I get cursortype 4 if I pick and item from my inventory and don't hover it over another item + * - If I then hover it over an item it turns to 3 then stays 3 + */ + cursortype: { + Empty: 1, + ItemOnUnitHover: 3, // see notes + ItemOnCursor: 4, // see notes + Identify: 6, + Repair: 7, + }, - }, - locale: { + collision: { + BlockWall: 0x01, + LineOfSight: 0x02, + Ranged: 0x04, + PlayerToWalk: 0x08, + DarkArea: 0x10, + Casting: 0x20, + Unknown: 0x40, + Players: 0x80, + Monsters: 0x100, + Items: 0x200, + Objects: 0x400, + ClosedDoor: 0x800, + IsOnFloor: 0x1000, + MonsterIsOnFloor: 0x1100, + MonsterIsOnFloorDarkArea: 0x1110, // in doorway + FriendlyNPC: 0x2000, + Unknown2: 0x4000, + DeadBodies: 0x8000, + MonsterObject: 0xFFFF, + BlockMissile: 0x80E, + WallOrRanged: 0x5, + BlockWalk: 0x1805, + FriendlyRanged: 0x2004, + BoneWall: 4352, + }, - }, - body and storage? It onlys applies to items - or would it make more sense for player.body? -} -*/ + areas: { + Towns: [1, 40, 75, 103, 109], + None: 0, + + // Act 1 + RogueEncampment: 1, + BloodMoor: 2, + ColdPlains: 3, + StonyField: 4, + DarkWood: 5, + BlackMarsh: 6, + TamoeHighland: 7, + DenofEvil: 8, + CaveLvl1: 9, + UndergroundPassageLvl1: 10, + HoleLvl1: 11, + PitLvl1: 12, + CaveLvl2: 13, + UndergroundPassageLvl2: 14, + HoleLvl2: 15, + PitLvl2: 16, + BurialGrounds: 17, + Crypt: 18, + Mausoleum: 19, + ForgottenTower: 20, + TowerCellarLvl1: 21, + TowerCellarLvl2: 22, + TowerCellarLvl3: 23, + TowerCellarLvl4: 24, + TowerCellarLvl5: 25, + MonasteryGate: 26, + OuterCloister: 27, + Barracks: 28, + JailLvl1: 29, + JailLvl2: 30, + JailLvl3: 31, + InnerCloister: 32, + Cathedral: 33, + CatacombsLvl1: 34, + CatacombsLvl2: 35, + CatacombsLvl3: 36, + CatacombsLvl4: 37, + Tristram: 38, + MooMooFarm: 39, -(function (module) { - const sdk = { - waypoints: { - Ids: [119, 145, 156, 157, 237, 238, 288, 323, 324, 398, 402, 429, 494, 496, 511, 539], - Act1: [0x01, 0x03, 0x04, 0x05, 0x06, 0x1b, 0x1d, 0x20, 0x23], - Act2: [0x28, 0x30, 0x2a, 0x39, 0x2b, 0x2c, 0x34, 0x4a, 0x2e], - Act3: [0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x65], - Act4: [0x67, 0x6a, 0x6b], - Act5: [0x6d, 0x6f, 0x70, 0x71, 0x73, 0x7b, 0x75, 0x76, 0x81] - }, + // Act 2 + LutGholein: 40, + RockyWaste: 41, + DryHills: 42, + FarOasis: 43, + LostCity: 44, + ValleyofSnakes: 45, + CanyonofMagic: 46, + A2SewersLvl1: 47, + A2SewersLvl2: 48, + A2SewersLvl3: 49, + HaremLvl1: 50, + HaremLvl2: 51, + PalaceCellarLvl1: 52, + PalaceCellarLvl2: 53, + PalaceCellarLvl3: 54, + StonyTombLvl1: 55, + HallsoftheDeadLvl1: 56, + HallsoftheDeadLvl2: 57, + ClawViperTempleLvl1: 58, + StonyTombLvl2: 59, + HallsoftheDeadLvl3: 60, + ClawViperTempleLvl2: 61, + MaggotLairLvl1: 62, + MaggotLairLvl2: 63, + MaggotLairLvl3: 64, + AncientTunnels: 65, + TalRashasTomb1: 66, + TalRashasTomb2: 67, + TalRashasTomb3: 68, + TalRashasTomb4: 69, + TalRashasTomb5: 70, + TalRashasTomb6: 71, + TalRashasTomb7: 72, + DurielsLair: 73, + ArcaneSanctuary: 74, - difficulty: { - Normal: 0, - Nightmare: 1, - Hell: 2, - Difficulties: ["Normal", "Nightmare", "Hell"], + // Act 3 + KurastDocktown: 75, + SpiderForest: 76, + GreatMarsh: 77, + FlayerJungle: 78, + LowerKurast: 79, + KurastBazaar: 80, + UpperKurast: 81, + KurastCauseway: 82, + Travincal: 83, + SpiderCave: 84, + SpiderCavern: 85, + SwampyPitLvl1: 86, + SwampyPitLvl2: 87, + FlayerDungeonLvl1: 88, + FlayerDungeonLvl2: 89, + SwampyPitLvl3: 90, + FlayerDungeonLvl3: 91, + A3SewersLvl1: 92, + A3SewersLvl2: 93, + RuinedTemple: 94, + DisusedFane: 95, + ForgottenReliquary: 96, + ForgottenTemple: 97, + RuinedFane: 98, + DisusedReliquary: 99, + DuranceofHateLvl1: 100, + DuranceofHateLvl2: 101, + DuranceofHateLvl3: 102, - nameOf: function (difficulty) { - if (difficulty === undefined || typeof difficulty !== "number") return false; - if (difficulty < 0 || difficulty > 2) return false; - return ["Normal", "Nightmare", "Hell"][difficulty]; - } - }, + // Act 4 + PandemoniumFortress: 103, + OuterSteppes: 104, + PlainsofDespair: 105, + CityoftheDamned: 106, + RiverofFlame: 107, + ChaosSanctuary: 108, - party: { - NoParty: 65535 - }, + // Act 5 + Harrogath: 109, + BloodyFoothills: 110, + FrigidHighlands: 111, + ArreatPlateau: 112, + CrystalizedPassage: 113, + FrozenRiver: 114, + GlacialTrail: 115, + DrifterCavern: 116, + FrozenTundra: 117, + AncientsWay: 118, + IcyCellar: 119, + ArreatSummit: 120, + NihlathaksTemple: 121, + HallsofAnguish: 122, + HallsofPain: 123, + HallsofVaught: 124, + Abaddon: 125, + PitofAcheron: 126, + InfernalPit: 127, + WorldstoneLvl1: 128, + WorldstoneLvl2: 129, + WorldstoneLvl3: 130, + ThroneofDestruction: 131, + WorldstoneChamber: 132, - clicktypes: { - click: { - item: { - Left: 0, - Right: 1, - ShiftLeft: 2, // For belt - MercFromBelt: 3, // For belt - Mercenary: 4 // Give to merc - }, - map: { - LeftDown: 0, - LeftHold: 1, - LeftUp: 2, - RightDown: 3, - RightHold: 4, - RightUp: 5, - }, - }, - shift: { - NoShift: 0, - Shift: 1 - } - }, - /** - * @notes - * - I get cursortype 3 when I swap an item that is equipped, but I get 4 if I just unequip an item - * - I get cursortype 3 if I pick an item from my inventory then hover it over another item - * - I get cursortype 4 if I pick and item from my inventory and don't hover it over another item - * - If I then hover it over an item it turns to 3 then stays 3 - */ - cursortype: { - Empty: 1, - ItemOnUnitHover: 3, // see notes - ItemOnCursor: 4, // see notes - Identify: 6, - Repair: 7, - }, + // Ubers + MatronsDen: 133, + ForgottenSands: 134, + FurnaceofPain: 135, + UberTristram: 136, - collision: { - BlockWall: 0x01, - LineOfSight: 0x02, - Ranged: 0x04, - PlayerToWalk: 0x08, - DarkArea: 0x10, - Casting: 0x20, - Unknown: 0x40, - Players: 0x80, - Monsters: 0x100, - Items: 0x200, - Objects: 0x400, - ClosedDoor: 0x800, - IsOnFloor: 0x1000, - MonsterIsOnFloor: 0x1100, - MonsterIsOnFloorDarkArea: 0x1110, // in doorway - FriendlyNPC: 0x2000, - Unknown2: 0x4000, - DeadBodies: 0x8000, - MonsterObject: 0xFFFF, - BlockMissile: 0x80E, - WallOrRanged: 0x5, - BlockWalk: 0x1805, - FriendlyRanged: 0x2004, - }, + actOf: function (area) { + switch (true) { + case area < sdk.areas.LutGholein: + return 1; + case area < sdk.areas.KurastDocktown: + return 2; + case area < sdk.areas.PandemoniumFortress: + return 3; + case area < sdk.areas.Harrogath: + return 4; + default: + return 5; + } + }, - areas: { - Towns: [1, 40, 75, 103, 109], - None: 0, + townOf: function (area) { + switch (true) { + case area < sdk.areas.LutGholein: + return sdk.areas.RogueEncampment; + case area < sdk.areas.KurastDocktown: + return sdk.areas.LutGholein; + case area < sdk.areas.PandemoniumFortress: + return sdk.areas.KurastDocktown; + case area < sdk.areas.Harrogath: + return sdk.areas.PandemoniumFortress; + default: + return sdk.areas.Harrogath; + } + }, - // Act 1 - RogueEncampment: 1, - BloodMoor: 2, - ColdPlains: 3, - StonyField: 4, - DarkWood: 5, - BlackMarsh: 6, - TamoeHighland: 7, - DenofEvil: 8, - CaveLvl1: 9, - UndergroundPassageLvl1: 10, - HoleLvl1: 11, - PitLvl1: 12, - CaveLvl2: 13, - UndergroundPassageLvl2: 14, - HoleLvl2: 15, - PitLvl2: 16, - BurialGrounds: 17, - Crypt: 18, - Mausoleum: 19, - ForgottenTower: 20, - TowerCellarLvl1: 21, - TowerCellarLvl2: 22, - TowerCellarLvl3: 23, - TowerCellarLvl4: 24, - TowerCellarLvl5: 25, - MonasteryGate: 26, - OuterCloister: 27, - Barracks: 28, - JailLvl1: 29, - JailLvl2: 30, - JailLvl3: 31, - InnerCloister: 32, - Cathedral: 33, - CatacombsLvl1: 34, - CatacombsLvl2: 35, - CatacombsLvl3: 36, - CatacombsLvl4: 37, - Tristram: 38, - MooMooFarm: 39, + townOfAct: function (act) { + switch (act) { + case 1: + return sdk.areas.RogueEncampment; + case 2: + return sdk.areas.LutGholein; + case 3: + return sdk.areas.KurastDocktown; + case 4: + return sdk.areas.PandemoniumFortress; + default: + return sdk.areas.Harrogath; + } + } + }, - // Act 2 - LutGholein: 40, - RockyWaste: 41, - DryHills: 42, - FarOasis: 43, - LostCity: 44, - ValleyofSnakes: 45, - CanyonofMagic: 46, - A2SewersLvl1: 47, - A2SewersLvl2: 48, - A2SewersLvl3: 49, - HaremLvl1: 50, - HaremLvl2: 51, - PalaceCellarLvl1: 52, - PalaceCellarLvl2: 53, - PalaceCellarLvl3: 54, - StonyTombLvl1: 55, - HallsoftheDeadLvl1: 56, - HallsoftheDeadLvl2: 57, - ClawViperTempleLvl1: 58, - StonyTombLvl2: 59, - HallsoftheDeadLvl3: 60, - ClawViperTempleLvl2: 61, - MaggotLairLvl1: 62, - MaggotLairLvl2: 63, - MaggotLairLvl3: 64, - AncientTunnels: 65, - TalRashasTomb1: 66, - TalRashasTomb2: 67, - TalRashasTomb3: 68, - TalRashasTomb4: 69, - TalRashasTomb5: 70, - TalRashasTomb6: 71, - TalRashasTomb7: 72, - DurielsLair: 73, - ArcaneSanctuary: 74, + skills: { + get: { + RightName: 0, + LeftName: 1, + RightId: 2, + LeftId: 3, + AllSkills: 4 + }, + hand: { + Right: 0, + Left: 1, + LeftNoShift: 2, + RightShift: 3, + }, + subindex: { + HardPoints: 0, + SoftPoints: 1 + }, + // General + Attack: 0, + Kick: 1, + Throw: 2, + Unsummon: 3, + LeftHandThrow: 4, + LeftHandSwing: 5, - // Act 3 - KurastDocktown: 75, - SpiderForest: 76, - GreatMarsh: 77, - FlayerJungle: 78, - LowerKurast: 79, - KurastBazaar: 80, - UpperKurast: 81, - KurastCauseway: 82, - Travincal: 83, - SpiderCave: 84, - SpiderCavern: 85, - SwampyPitLvl1: 86, - SwampyPitLvl2: 87, - FlayerDungeonLvl1: 88, - FlayerDungeonLvl2: 89, - SwampyPitLvl3: 90, - FlayerDungeonLvl3: 91, - A3SewersLvl1: 92, - A3SewersLvl2: 93, - RuinedTemple: 94, - DisusedFane: 95, - ForgottenReliquary: 96, - ForgottenTemple: 97, - RuinedFane: 98, - DisusedReliquary: 99, - DuranceofHateLvl1: 100, - DuranceofHateLvl2: 101, - DuranceofHateLvl3: 102, + // Amazon + MagicArrow: 6, + FireArrow: 7, + InnerSight: 8, + CriticalStrike: 9, + Jab: 10, + ColdArrow: 11, + MultipleShot: 12, + Dodge: 13, + PowerStrike: 14, + PoisonJavelin: 15, + ExplodingArrow: 16, + SlowMissiles: 17, + Avoid: 18, + Impale: 19, + LightningBolt: 20, + IceArrow: 21, + GuidedArrow: 22, + Penetrate: 23, + ChargedStrike: 24, + PlagueJavelin: 25, + Strafe: 26, + ImmolationArrow: 27, + Dopplezon: 28, + Decoy: 28, + Evade: 29, + Fend: 30, + FreezingArrow: 31, + Valkyrie: 32, + Pierce: 33, + LightningStrike: 34, + LightningFury: 35, - // Act 4 - PandemoniumFortress: 103, - OuterSteppes: 104, - PlainsofDespair: 105, - CityoftheDamned: 106, - RiverofFlame: 107, - ChaosSanctuary: 108, + // Sorc + FireBolt: 36, + Warmth: 37, + ChargedBolt: 38, + IceBolt: 39, + FrozenArmor: 40, + Inferno: 41, + StaticField: 42, + Telekinesis: 43, + FrostNova: 44, + IceBlast: 45, + Blaze: 46, + FireBall: 47, + Nova: 48, + Lightning: 49, + ShiverArmor: 50, + FireWall: 51, + Enchant: 52, + ChainLightning: 53, + Teleport: 54, + GlacialSpike: 55, + Meteor: 56, + ThunderStorm: 57, + EnergyShield: 58, + Blizzard: 59, + ChillingArmor: 60, + FireMastery: 61, + Hydra: 62, + LightningMastery: 63, + FrozenOrb: 64, + ColdMastery: 65, - // Act 5 - Harrogath: 109, - BloodyFoothills: 110, - FrigidHighlands: 111, - ArreatPlateau: 112, - CrystalizedPassage: 113, - FrozenRiver: 114, - GlacialTrail: 115, - DrifterCavern: 116, - FrozenTundra: 117, - AncientsWay: 118, - IcyCellar: 119, - ArreatSummit: 120, - NihlathaksTemple: 121, - HallsofAnguish: 122, - HallsofPain: 123, - HallsofVaught: 124, - Abaddon: 125, - PitofAcheron: 126, - InfernalPit: 127, - WorldstoneLvl1: 128, - WorldstoneLvl2: 129, - WorldstoneLvl3: 130, - ThroneofDestruction: 131, - WorldstoneChamber: 132, + // Necro + AmplifyDamage: 66, + Teeth: 67, + BoneArmor: 68, + SkeletonMastery: 69, + RaiseSkeleton: 70, + DimVision: 71, + Weaken: 72, + PoisonDagger: 73, + CorpseExplosion: 74, + ClayGolem: 75, + IronMaiden: 76, + Terror: 77, + BoneWall: 78, + GolemMastery: 79, + RaiseSkeletalMage: 80, + Confuse: 81, + LifeTap: 82, + PoisonExplosion: 83, + BoneSpear: 84, + BloodGolem: 85, + Attract: 86, + Decrepify: 87, + BonePrison: 88, + SummonResist: 89, + IronGolem: 90, + LowerResist: 91, + PoisonNova: 92, + BoneSpirit: 93, + FireGolem: 94, + Revive: 95, - // Ubers - MatronsDen: 133, - ForgottenSands: 134, - FurnaceofPain: 135, - UberTristram: 136, + // Paladin + Sacrifice: 96, + Smite: 97, + Might: 98, + Prayer: 99, + ResistFire: 100, + HolyBolt: 101, + HolyFire: 102, + Thorns: 103, + Defiance: 104, + ResistCold: 105, + Zeal: 106, + Charge: 107, + BlessedAim: 108, + Cleansing: 109, + ResistLightning: 110, + Vengeance: 111, + BlessedHammer: 112, + Concentration: 113, + HolyFreeze: 114, + Vigor: 115, + Conversion: 116, + HolyShield: 117, + HolyShock: 118, + Sanctuary: 119, + Meditation: 120, + FistoftheHeavens: 121, + Fanaticism: 122, + Conviction: 123, + Redemption: 124, + Salvation: 125, - actOf: function (area) { - switch (true) { - case area < sdk.areas.LutGholein: - return 1; - case area < sdk.areas.KurastDocktown: - return 2; - case area < sdk.areas.PandemoniumFortress: - return 3; - case area < sdk.areas.Harrogath: - return 4; - default: - return 5; - } - }, + // Barb + Bash: 126, + SwordMastery: 127, + AxeMastery: 128, + MaceMastery: 129, + Howl: 130, + FindPotion: 131, + Leap: 132, + DoubleSwing: 133, + PoleArmMastery: 134, + ThrowingMastery: 135, + SpearMastery: 136, + Taunt: 137, + Shout: 138, + Stun: 139, + DoubleThrow: 140, + IncreasedStamina: 141, + FindItem: 142, + LeapAttack: 143, + Concentrate: 144, + IronSkin: 145, + BattleCry: 146, + Frenzy: 147, + IncreasedSpeed: 148, + BattleOrders: 149, + GrimWard: 150, + Whirlwind: 151, + Berserk: 152, + NaturalResistance: 153, + WarCry: 154, + BattleCommand: 155, - townOf: function (area) { - switch (true) { - case area < sdk.areas.LutGholein: - return sdk.areas.RogueEncampment; - case area < sdk.areas.KurastDocktown: - return sdk.areas.LutGholein; - case area < sdk.areas.PandemoniumFortress: - return sdk.areas.KurastDocktown; - case area < sdk.areas.Harrogath: - return sdk.areas.PandemoniumFortress; - default: - return sdk.areas.Harrogath; - } - }, + // General stuff + IdentifyScroll: 217, + BookofIdentify: 218, + TownPortalScroll: 219, + BookofTownPortal: 220, - townOfAct: function (act) { - switch (act) { - case 1: - return sdk.areas.RogueEncampment; - case 2: - return sdk.areas.LutGholein; - case 3: - return sdk.areas.KurastDocktown; - case 4: - return sdk.areas.PandemoniumFortress; - default: - return sdk.areas.Harrogath; - } - } - }, + // Druid + Raven: 221, + PoisonCreeper: 222, // External + PlaguePoppy: 222, // Internal + Werewolf: 223, // External + Wearwolf: 223, // Internal + Lycanthropy: 224, // External + ShapeShifting: 224, // Internal + Firestorm: 225, + OakSage: 226, + SpiritWolf: 227, // External + SummonSpiritWolf: 227, // Internal + Werebear: 228, // External + Wearbear: 228, // Internal + MoltenBoulder: 229, + ArcticBlast: 230, + CarrionVine: 231, // External + CycleofLife: 231, // Internal + FeralRage: 232, + Maul: 233, + Fissure: 234, // Internal + Eruption: 234, // Internal + CycloneArmor: 235, + HeartofWolverine: 236, + SummonDireWolf: 237, // External + SummonFenris: 237, // Internal + Rabies: 238, + FireClaws: 239, + Twister: 240, + SolarCreeper: 241, // External + Vines: 241, // Internal + Hunger: 242, + ShockWave: 243, + Volcano: 244, + Tornado: 245, + SpiritofBarbs: 246, + Grizzly: 247, // External + SummonGrizzly: 247, // Internal + Fury: 248, + Armageddon: 249, + Hurricane: 250, - skills: { - get: { - RightName: 0, - LeftName: 1, - RightId: 2, - LeftId: 3, - AllSkills: 4 - }, - hand: { - Right: 0, - Left: 1, - LeftNoShift: 2, - RightShift: 3, - }, - subindex: { - HardPoints: 0, - SoftPoints: 1 - }, - // General - Attack: 0, - Kick: 1, - Throw: 2, - Unsummon: 3, - LeftHandThrow: 4, - LeftHandSwing: 5, + // Assa + FireBlast: 251, // External + FireTrauma: 251, // Internal + ClawMastery: 252, + PsychicHammer: 253, + TigerStrike: 254, + DragonTalon: 255, + ShockWeb: 256, // External + ShockField: 256, // Internal + BladeSentinel: 257, + Quickness: 258, // Internal name + BurstofSpeed: 258, // Shown name + FistsofFire: 259, + DragonClaw: 260, + ChargedBoltSentry: 261, + WakeofFire: 262, // External + WakeofFireSentry: 262, // Internal + WeaponBlock: 263, + CloakofShadows: 264, + CobraStrike: 265, + BladeFury: 266, + Fade: 267, + ShadowWarrior: 268, + ClawsofThunder: 269, + DragonTail: 270, + LightningSentry: 271, + WakeofInferno: 272, // External + InfernoSentry: 272, // Internal + MindBlast: 273, + BladesofIce: 274, + DragonFlight: 275, + DeathSentry: 276, + BladeShield: 277, + Venom: 278, + ShadowMaster: 279, + PhoenixStrike: 280, // External + RoyalStrike: 280, // Internal + WakeofDestructionSentry: 281, // Not used? + Summoner: 500, // special + tabs: { + // Ama + BowandCrossbow: 0, + PassiveandMagic: 1, + JavelinandSpear: 2, - // Amazon - MagicArrow: 6, - FireArrow: 7, - InnerSight: 8, - CriticalStrike: 9, - Jab: 10, - ColdArrow: 11, - MultipleShot: 12, - Dodge: 13, - PowerStrike: 14, - PoisonJavelin: 15, - ExplodingArrow: 16, - SlowMissiles: 17, - Avoid: 18, - Impale: 19, - LightningBolt: 20, - IceArrow: 21, - GuidedArrow: 22, - Penetrate: 23, - ChargedStrike: 24, - PlagueJavelin: 25, - Strafe: 26, - ImmolationArrow: 27, - Dopplezon: 28, - Decoy: 28, - Evade: 29, - Fend: 30, - FreezingArrow: 31, - Valkyrie: 32, - Pierce: 33, - LightningStrike: 34, - LightningFury: 35, + // Sorc + Fire: 8, + Lightning: 9, + Cold: 10, - // Sorc - FireBolt: 36, - Warmth: 37, - ChargedBolt: 38, - IceBolt: 39, - FrozenArmor: 40, - Inferno: 41, - StaticField: 42, - Telekinesis: 43, - FrostNova: 44, - IceBlast: 45, - Blaze: 46, - FireBall: 47, - Nova: 48, - Lightning: 49, - ShiverArmor: 50, - FireWall: 51, - Enchant: 52, - ChainLightning: 53, - Teleport: 54, - GlacialSpike: 55, - Meteor: 56, - ThunderStorm: 57, - EnergyShield: 58, - Blizzard: 59, - ChillingArmor: 60, - FireMastery: 61, - Hydra: 62, - LightningMastery: 63, - FrozenOrb: 64, - ColdMastery: 65, + // Necro + Curses: 16, + PoisonandBone: 17, + NecroSummoning: 18, - // Necro - AmplifyDamage: 66, - Teeth: 67, - BoneArmor: 68, - SkeletonMastery: 69, - RaiseSkeleton: 70, - DimVision: 71, - Weaken: 72, - PoisonDagger: 73, - CorpseExplosion: 74, - ClayGolem: 75, - IronMaiden: 76, - Terror: 77, - BoneWall: 78, - GolemMastery: 79, - RaiseSkeletalMage: 80, - Confuse: 81, - LifeTap: 82, - PoisonExplosion: 83, - BoneSpear: 84, - BloodGolem: 85, - Attract: 86, - Decrepify: 87, - BonePrison: 88, - SummonResist: 89, - IronGolem: 90, - LowerResist: 91, - PoisonNova: 92, - BoneSpirit: 93, - FireGolem: 94, - Revive: 95, + // Pala + PalaCombat: 24, + Offensive: 25, + Defensive: 26, - // Paladin - Sacrifice: 96, - Smite: 97, - Might: 98, - Prayer: 99, - ResistFire: 100, - HolyBolt: 101, - HolyFire: 102, - Thorns: 103, - Defiance: 104, - ResistCold: 105, - Zeal: 106, - Charge: 107, - BlessedAim: 108, - Cleansing: 109, - ResistLightning: 110, - Vengeance: 111, - BlessedHammer: 112, - Concentration: 113, - HolyFreeze: 114, - Vigor: 115, - Conversion: 116, - HolyShield: 117, - HolyShock: 118, - Sanctuary: 119, - Meditation: 120, - FistoftheHeavens: 121, - Fanaticism: 122, - Conviction: 123, - Redemption: 124, - Salvation: 125, + // Barb + BarbCombat: 32, + Masteries: 33, + Warcries: 34, - // Barb - Bash: 126, - SwordMastery: 127, - AxeMastery: 128, - MaceMastery: 129, - Howl: 130, - FindPotion: 131, - Leap: 132, - DoubleSwing: 133, - PoleArmMastery: 134, - ThrowingMastery: 135, - SpearMastery: 136, - Taunt: 137, - Shout: 138, - Stun: 139, - DoubleThrow: 140, - IncreasedStamina: 141, - FindItem: 142, - LeapAttack: 143, - Concentrate: 144, - IronSkin: 145, - BattleCry: 146, - Frenzy: 147, - IncreasedSpeed: 148, - BattleOrders: 149, - GrimWard: 150, - Whirlwind: 151, - Berserk: 152, - NaturalResistance: 153, - WarCry: 154, - BattleCommand: 155, + // Druid + DruidSummon: 40, + ShapeShifting: 41, + Elemental: 42, - // General stuff - IdentifyScroll: 217, - BookofIdentify: 218, - TownPortalScroll: 219, - BookofTownPortal: 220, + // Assa + Traps: 48, + ShadowDisciplines: 49, + MartialArts: 50, + } + }, + skillTabs: undefined, - // Druid - Raven: 221, - PoisonCreeper: 222, // External - PlaguePoppy: 222, // Internal - Werewolf: 223, // External - Wearwolf: 223, // Internal - Lycanthropy: 224, // External - ShapeShifting: 224, // Internal - Firestorm: 225, - OakSage: 226, - SpiritWolf: 227, // External - SummonSpiritWolf: 227, // Internal - Werebear: 228, // External - Wearbear: 228, // Internal - MoltenBoulder: 229, - ArcticBlast: 230, - CarrionVine: 231, // External - CycleofLife: 231, // Internal - FeralRage: 232, - Maul: 233, - Fissure: 234, // Internal - Eruption: 234, // Internal - CycloneArmor: 235, - HeartofWolverine: 236, - SummonDireWolf: 237, // External - SummonFenris: 237, // Internal - Rabies: 238, - FireClaws: 239, - Twister: 240, - SolarCreeper: 241, // External - Vines: 241, // Internal - Hunger: 242, - ShockWave: 243, - Volcano: 244, - Tornado: 245, - SpiritofBarbs: 246, - Grizzly: 247, // External - SummonGrizzly: 247, // Internal - Fury: 248, - Armageddon: 249, - Hurricane: 250, + quest: { + item: { + // Act 1 + WirtsLeg: 88, + HoradricMalus: 89, + ScrollofInifuss: 524, + KeytotheCairnStones: 525, + // Act 2 + FinishedStaff: 91, + "HoradricStaff": 91, + IncompleteStaff: 92, + "ShaftoftheHoradricStaff": 92, + ViperAmulet: 521, + "TopoftheHoradricStaff": 521, + Cube: 549, + BookofSkill: 552, + // Act 3 + "DecoyGidbinn": 86, + "TheGidbinn": 87, + KhalimsFlail: 173, + KhalimsWill: 174, + PotofLife: 545, + "AJadeFigurine": 546, + JadeFigurine: 546, + TheGoldenBird: 547, + LamEsensTome: 548, + KhalimsEye: 553, + KhalimsHeart: 554, + KhalimsBrain: 555, + // Act 4 + HellForgeHammer: 90, + Soulstone: 551, + "MephistosSoulstone": 551, + // Act 5 + MalahsPotion: 644, + "ScrollofKnowledge": 645, + ScrollofResistance: 646, + // Pandemonium Event + KeyofTerror: 647, + KeyofHate: 648, + KeyofDestruction: 649, + DiablosHorn: 650, + BaalsEye: 651, + MephistosBrain: 652, + StandardofHeroes: 658, + // Essences/Token + TokenofAbsolution: 653, + TwistedEssenceofSuffering: 654, + ChargedEssenceofHatred: 655, + BurningEssenceofTerror: 656, + FesteringEssenceofDestruction: 657, + // Misc + "TheBlackTowerKey": 544, + }, + items: [ + // act 1 + 88, 89, 524, 525, + // act 2 + 91, 92, 521, 549, 552, + // act 3 + 86, 87, 173, 174, 545, 546, 547, 548, 553, 554, 555, + // act 4 + 90, 551, + // act 5 + 644, 645, 646, + ], + chest: { + // act1 + StoneAlpha: 17, + StoneBeta: 18, + StoneGamma: 19, + StoneDelta: 20, + StoneLambda: 21, + StoneTheta: 22, // ? + CainsJail: 26, + InifussTree: 30, + MalusHolder: 108, + Wirt: 268, - // Assa - FireBlast: 251, // External - FireTrauma: 251, // Internal - ClawMastery: 252, - PsychicHammer: 253, - TigerStrike: 254, - DragonTalon: 255, - ShockWeb: 256, // External - ShockField: 256, // Internal - BladeSentinel: 257, - Quickness: 258, // Internal name - BurstofSpeed: 258, // Shown name - FistsofFire: 259, - DragonClaw: 260, - ChargedBoltSentry: 261, - WakeofFire: 262, // External - WakeofFireSentry: 262, // Internal - WeaponBlock: 263, - CloakofShadows: 264, - CobraStrike: 265, - BladeFury: 266, - Fade: 267, - ShadowWarrior: 268, - ClawsofThunder: 269, - DragonTail: 270, - LightningSentry: 271, - WakeofInferno: 272, // External - InfernoSentry: 272, // Internal - MindBlast: 273, - BladesofIce: 274, - DragonFlight: 275, - DeathSentry: 276, - BladeShield: 277, - Venom: 278, - ShadowMaster: 279, - PhoenixStrike: 280, // External - RoyalStrike: 280, // Internal - WakeofDestructionSentry: 281, // Not used? - Summoner: 500, // special - tabs: { - // Ama - BowandCrossbow: 0, - PassiveandMagic: 1, - JavelinandSpear: 2, + // act 2 + ViperAmuletChest: 149, + HoradricStaffHolder: 152, + HoradricCubeChest: 354, + HoradricScrollChest: 355, + ShaftoftheHoradricStaffChest: 356, + Journal: 357, - // Sorc - Fire: 8, - Lightning: 9, - Cold: 10, + // act 3 + ForestAltar: 81, + LamEsensTomeHolder: 193, + GidbinnAltar: 252, + KhalimsHeartChest: 405, + KhalimsBrainChest: 406, + KhalimsEyeChest: 407, - // Necro - Curses: 16, - PoisonandBone: 17, - NecroSummoning: 18, + // act 4 + HellForge: 376, - // Pala - PalaCombat: 24, - Offensive: 25, - Defensive: 26, + // act 5 + BarbCage: 473, + FrozenAnya: 558, + AncientsAltar: 546, + }, + chests: [ + // act 1 + 17, 18, 19, 20, 21, 22, 26, 30, 108, + // act 2 + 149, 152, 354, 355, 356, 357, + // act 3 + 81, 193, 405, 406, 407, + // act 4 + 376, + // act 5 + 434, 558, 546 + ], + id: { + SpokeToWarriv: 0, + DenofEvil: 1, + SistersBurialGrounds: 2, + TheSearchForCain: 4, + ForgottenTower: 5, + ToolsoftheTrade: 3, + SistersToTheSlaughter: 6, + AbleToGotoActII: 7, + SpokeToJerhyn: 8, + RadamentsLair: 9, + TheHoradricStaff: 10, + TheTaintedSun: 11, + TheArcaneSanctuary: 12, + TheSummoner: 13, + TheSevenTombs: 14, + AbleToGotoActIII: 15, + SpokeToHratli: 16, + TheGoldenBird: 20, + BladeoftheOldReligion: 19, + KhalimsWill: 18, + LamEsensTome: 17, + TheBlackenedTemple: 21, + TheGuardian: 22, + AbleToGotoActIV: 23, + SpokeToTyrael: 24, + TheFallenAngel: 25, + HellsForge: 27, + TerrorsEnd: 26, + AbleToGotoActV: 28, + SiegeOnHarrogath: 35, + RescueonMountArreat: 36, + PrisonofIce: 37, + BetrayalofHarrogath: 38, + RiteofPassage: 39, + EyeofDestruction: 40, + Respec: 41, + }, + // just common states for now + states: { + Completed: 0, + ReqComplete: 1, + GreyedOut: 12, + PartyMemberComplete: 13, + CannotComplete: 14, + } + }, - // Barb - BarbCombat: 32, - Masteries: 33, - Warcries: 34, + // in game data + uiflags: { + Inventory: 0x01, + StatsWindow: 0x02, + QuickSkill: 0x03, + SkillWindow: 0x04, + ChatBox: 0x05, + NPCMenu: 0x08, + EscMenu: 0x09, + KeytotheCairnStonesScreen: 0x10, + AutoMap: 0x0A, + ConfigControls: 0x0B, + Shop: 0x0C, + ShowItem: 0x0D, + SubmitItem: 0x0E, + Quest: 0x0F, + QuestLog: 0x11, + StatusArea: 0x12, + Waypoint: 0x14, + MiniPanel: 0x15, + Party: 0x16, + TradePrompt: 0x17, + Msgs: 0x18, + Stash: 0x19, + Cube: 0x1A, + ShowBelt: 0x1F, + Help: 0x21, + MercScreen: 0x24, + ScrollWindow: 0x25 + }, - // Druid - DruidSummon: 40, - ShapeShifting: 41, - Elemental: 42, + menu: { + Respec: 0x2BA0, + Ok: 0x0D49, + Talk: 0x0D35, + Trade: 0x0D44, + TradeRepair: 0x0D06, + Imbue: 0x0FB1, + Gamble: 0x0D46, + Hire: 0x0D45, + GoEast: 0x0D36, + GoWest: 0x0D37, + IdentifyItems: 0x0FB4, + SailEast: 0x0D38, + SailWest: 0x0D39, + RessurectMerc: 0x1507, + AddSockets: 0x58DC, + Personalize: 0x58DD, + TravelToHarrogath: 0x58D2, + }, - // Assa - Traps: 48, - ShadowDisciplines: 49, - MartialArts: 50, - } - }, - skillTabs: undefined, + // shrine types + shrines: { + Presets: [2, 81, 83, 170, 344, 197, 202], + Ids: [ + 2, 77, 81, 83, 84, 85, 93, 96, 97, 109, 116, 123, 124, + 133, 134, 135, 136, 150, 151, 164, 165, 166, 167, 168, + 170, 172, 173, 184, 190, 191, 197, 199, 200, 201, 202, + 206, 226, 231, 232, 236, 249, 260, 262, 263, 264, 265, + 275, 276, 277, 278, 279, 280, 281, 282, 299, 300, 302, + 303, 320, 325, 343, 344, 361, 414, 415, 421, 422, 423, + 427, 428, 464, 465, 472, 479, 483, 484, 488, 491, 492, + 495, 497, 499, 503, 509, 512, 520, 521, 522 + ], + None: 0, + Refilling: 1, + Health: 2, + Mana: 3, + HealthExchange: 4, + ManaExchange: 5, + Armor: 6, + Combat: 7, + ResistFire: 8, + ResistCold: 9, + ResistLightning: 10, + ResistPoison: 11, + Skill: 12, + ManaRecharge: 13, + Stamina: 14, + Experience: 15, + Enirhs: 16, + Portal: 17, + Gem: 18, + Fire: 19, + Monster: 20, + Exploding: 21, + Poison: 22 + }, - quest: { - item: { - // Act 1 - WirtsLeg: 88, - HoradricMalus: 89, - ScrollofInifuss: 524, - KeytotheCairnStones: 525, - // Act 2 - FinishedStaff: 91, - "HoradricStaff": 91, - IncompleteStaff: 92, - "ShaftoftheHoradricStaff": 92, - ViperAmulet: 521, - "TopoftheHoradricStaff": 521, - Cube: 549, - BookofSkill: 552, - // Act 3 - "DecoyGidbinn": 86, - "TheGidbinn": 87, - KhalimsFlail: 173, - KhalimsWill: 174, - PotofLife: 545, - "AJadeFigurine": 546, - JadeFigurine: 546, - TheGoldenBird: 547, - LamEsensTome: 548, - KhalimsEye: 553, - KhalimsHeart: 554, - KhalimsBrain: 555, - // Act 4 - HellForgeHammer: 90, - Soulstone: 551, - "MephistosSoulstone": 551, - // Act 5 - MalahsPotion: 644, - "ScrollofKnowledge": 645, - ScrollofResistance: 646, - // Pandemonium Event - KeyofTerror: 647, - KeyofHate: 648, - KeyofDestruction: 649, - DiablosHorn: 650, - BaalsEye: 651, - MephistosBrain: 652, - StandardofHeroes: 658, - // Essences/Token - TokenofAbsolution: 653, - TwistedEssenceofSuffering: 654, - ChargedEssenceofHatred: 655, - BurningEssenceofTerror: 656, - FesteringEssenceofDestruction: 657, - // Misc - "TheBlackTowerKey": 544, - }, - items: [ - // act 1 - 88, 89, 524, 525, - // act 2 - 91, 92, 521, 549, 552, - // act 3 - 86, 87, 173, 174, 545, 546, 547, 548, 553, 554, 555, - // act 4 - 90, 551, - // act 5 - 644, 645, 646, - ], - chest: { - // act1 - StoneAlpha: 17, - StoneBeta: 18, - StoneGamma: 19, - StoneDelta: 20, - StoneLambda: 21, - StoneTheta: 22, // ? - CainsJail: 26, - InifussTree: 30, - MalusHolder: 108, - Wirt: 268, + // unit states + states: { + None: 0, + FrozenSolid: 1, + Poison: 2, + ResistFire: 3, + ResistCold: 4, + ResistLightning: 5, + ResistMagic: 6, + PlayerBody: 7, + ResistAll: 8, + AmplifyDamage: 9, + FrozenArmor: 10, + Frozen: 11, + Inferno: 12, + Blaze: 13, + BoneArmor: 14, + Concentrate: 15, + Enchant: 16, + InnerSight: 17, + SkillMove: 18, + Weaken: 19, + ChillingArmor: 20, + Stunned: 21, + SpiderLay: 22, + DimVision: 23, + Slowed: 24, + FetishAura: 25, + Shout: 26, + Taunt: 27, + Conviction: 28, + Convicted: 29, + EnergyShield: 30, + Venom: 31, + BattleOrders: 32, + Might: 33, + Prayer: 34, + HolyFire: 35, + Thorns: 36, + Defiance: 37, + ThunderStorm: 38, + LightningBolt: 39, + BlessedAim: 40, + Stamina: 41, + Concentration: 42, + Holywind: 43, + HolyFreeze: 43, + HolywindCold: 44, + HolyFreezeCold: 44, + Cleansing: 45, + HolyShock: 46, + Sanctuary: 47, + Meditation: 48, + Fanaticism: 49, + Redemption: 50, + BattleCommand: 51, + PreventHeal: 52, + Conversion: 53, + Uninterruptable: 54, + IronMaiden: 55, + Terror: 56, + Attract: 57, + LifeTap: 58, + Confuse: 59, + Decrepify: 60, + LowerResist: 61, + OpenWounds: 62, + Dopplezon: 63, + Decoy: 63, + CriticalStrike: 64, + Dodge: 65, + Avoid: 66, + Penetrate: 67, + Evade: 68, + Pierce: 69, + Warmth: 70, + FireMastery: 71, + LightningMastery: 72, + ColdMastery: 73, + SwordMastery: 74, + AxeMastery: 75, + MaceMastery: 76, + PoleArmMastery: 77, + ThrowingMastery: 78, + SpearMastery: 79, + IncreasedStamina: 80, + IronSkin: 81, + IncreasedSpeed: 82, + NaturalResistance: 83, + FingerMageCurse: 84, + NoManaReg: 85, + JustHit: 86, + SlowMissiles: 87, + ShiverArmor: 88, + BattleCry: 89, + Blue: 90, + Red: 91, + DeathDelay: 92, + Valkyrie: 93, + Frenzy: 94, + Berserk: 95, + Revive: 96, + ItemFullSet: 97, + SourceUnit: 98, + Redeemed: 99, + HealthPot: 100, + HolyShield: 101, + JustPortaled: 102, + MonFrenzy: 103, + CorpseNoDraw: 104, + Alignment: 105, + ManaPot: 106, + Shatter: 107, + SyncWarped: 108, + ConversionSave: 109, + Pregnat: 110, + Rabies: 112, + DefenceCurse: 113, + BloodMana: 114, + Burning: 115, + DragonFlight: 116, + Maul: 117, + CorpseNoSelect: 118, + ShadowWarrior: 119, + FeralRage: 120, + SkillDelay: 121, + ProgressiveDamage: 122, + ProgressiveSteal: 123, + ProgressiveOther: 124, + ProgressiveFire: 125, + ProgressiveCold: 126, + ProgressiveLighting: 127, + ShrineArmor: 128, + ShrineCombat: 129, + ShrineResLighting: 130, + ShrineResFire: 131, + ShrineResCold: 132, + ShrineResPoison: 133, + ShrineSkill: 134, + ShrineManaRegen: 135, + ShrineStamina: 136, + ShrineExperience: 137, + FenrisRage: 138, + Wolf: 139, + Wearwolf: 139, + Bear: 140, + Wearbear: 140, + Bloodlust: 141, + ChangeClass: 142, + Attached: 143, + Hurricane: 144, + Armageddon: 145, + Invis: 146, + Barbs: 147, + HeartofWolverine: 148, + OakSage: 149, + VineBeast: 150, + CycloneArmor: 151, + ClawMastery: 152, + CloakofShadows: 153, + Recyled: 154, + WeaponBlock: 155, + Cloaked: 156, + Quickness: 157, // Internal name + BurstofSpeed: 157, // External name + BladeShield: 158, + Fade: 159, + RestInPeace: 172, + Glowing: 175, + Delerium: 177, + Antidote: 178, + Thawing: 179, + StaminaPot: 180, + }, - // act 2 - ViperAmuletChest: 149, - HoradricStaffHolder: 152, - HoradricCubeChest: 354, - HoradricScrollChest: 355, - ShaftoftheHoradricStaffChest: 356, - Journal: 357, + enchant: { + RandName: 1, + HpMultiply: 2, + AddLightRadius: 3, + AddMLvl: 4, + ExtraStrong: 5, + ExtraFast: 6, + Cursed: 7, + MagicResistant: 8, + FireEnchanted: 9, + PoisonDeath: 10, + InsectDeath: 11, + ChainLightingDeath: 12, + IgnoreTargetDefense: 13, + UnknownMod: 14, + KillMinionsDeath: 15, + ChampMods: 16, + LightningEnchanted: 17, + ColdEnchanted: 18, + UnusedMercMod: 19, + ChargedBoltWhenStruck: 20, + TempSummoned: 21, + QuestMod: 22, + PoisonField: 23, + Thief: 24, + ManaBurn: 25, + Teleportation: 26, + SpectralHit: 27, + StoneSkin: 28, + MultipleShots: 29, + Aura: 30, + CorpseExplosion: 31, + FireExplosionOnDeath: 32, // not sure what the difference is between this and 9 + FreezeOnDeath: 33, + SelfResurrect: 34, + IceShatter: 35, + ChampStoned: 36, + ChampStats: 37, + ChampCurseImmune: 38, + }, - // act 3 - ForestAltar: 81, - LamEsensTomeHolder: 193, - GidbinnAltar: 252, - KhalimsHeartChest: 405, - KhalimsBrainChest: 406, - KhalimsEyeChest: 407, + // unit stats + stats: { + StunLength: 66, + VelocityPercent: 67, + OtherAnimrate: 69, + HpRegen: 74, - // act 4 - HellForge: 376, + LastBlockFrame: 95, + State: 98, + MonsterPlayerCount: 100, - // act 5 - BarbCage: 473, - FrozenAnya: 558, - AncientsAltar: 546, - }, - chests: [ - // act 1 - 17, 18, 19, 20, 21, 22, 26, 30, 108, - // act 2 - 149, 152, 354, 355, 356, 357, - // act 3 - 81, 193, 405, 406, 407, - // act 4 - 376, - // act 5 - 434, 558, 546 - ], - id: { - SpokeToWarriv: 0, - DenofEvil: 1, - SistersBurialGrounds: 2, - TheSearchForCain: 4, - ForgottenTower: 5, - ToolsoftheTrade: 3, - SistersToTheSlaughter: 6, - AbleToGotoActII: 7, - SpokeToJerhyn: 8, - RadamentsLair: 9, - TheHoradricStaff: 10, - TheTaintedSun: 11, - TheArcaneSanctuary: 12, - TheSummoner: 13, - TheSevenTombs: 14, - AbleToGotoActIII: 15, - SpokeToHratli: 16, - TheGoldenBird: 20, - BladeoftheOldReligion: 19, - KhalimsWill: 18, - LamEsensTome: 17, - TheBlackenedTemple: 21, - TheGuardian: 22, - AbleToGotoActIV: 23, - SpokeToTyrael: 24, - TheFallenAngel: 25, - HellsForge: 27, - TerrorsEnd: 26, - AbleToGotoActV: 28, - SiegeOnHarrogath: 35, - RescueonMountArreat: 36, - PrisonofIce: 37, - BetrayalofHaggorath: 38, - RiteofPassage: 39, - EyeofDestruction: 40, - Respec: 41, - }, - // just common states for now - states: { - Completed: 0, - ReqComplete: 1, - GreyedOut: 12, - PartyMemberComplete: 13, - CannotComplete: 14, - } - }, + CurseResistance: 109, + IronMaidenLevel: 129, + LifeTapLevel: 130, - // in game data - uiflags: { - Inventory: 0x01, - StatsWindow: 0x02, - QuickSkill: 0x03, - SkillWindow: 0x04, - ChatBox: 0x05, - NPCMenu: 0x08, - EscMenu: 0x09, - KeytotheCairnStonesScreen: 0x10, - AutoMap: 0x0A, - ConfigControls: 0x0B, - Shop: 0x0C, - ShowItem: 0x0D, - SubmitItem: 0x0E, - Quest: 0x0F, - QuestLog: 0x11, - StatusArea: 0x12, - Waypoint: 0x14, - MiniPanel: 0x15, - Party: 0x16, - TradePrompt: 0x17, - Msgs: 0x18, - Stash: 0x19, - Cube: 0x1A, - ShowBelt: 0x1F, - Help: 0x21, - MercScreen: 0x24, - ScrollWindow: 0x25 - }, + Alignment: 172, + Target0: 173, + Target1: 174, + GoldLost: 175, + MinimumRequiredLevel: 176, + ConversionLevel: 176, + ConversionMaxHp: 177, + UnitDooverlay: 178, + AttackVsMontype: 179, + DamageVsMontype: 180, - menu: { - Respec: 0x2BA0, - Ok: 0x0D49, - Talk: 0x0D35, - Trade: 0x0D44, - TradeRepair: 0x0D06, - Imbue: 0x0FB1, - Gamble: 0x0D46, - Hire: 0x0D45, - GoEast: 0x0D36, - GoWest: 0x0D37, - IdentifyItems: 0x0FB4, - SailEast: 0x0D38, - SailWest: 0x0D39, - RessurectMerc: 0x1507, - AddSockets: 0x58DC, - Personalize: 0x58DD, - TravelToHarrogath: 0x58D2, - }, + ArmorOverridePercent: 182, + FireLength: 315, + BurningMin: 316, + BurningMax: 317, + ProgressiveDamage: 318, + ProgressiveSteal: 319, + ProgressiveOther: 320, + ProgressiveFire: 321, + ProgressiveCold: 322, + ProgressiveLightning: 323, + ProgressiveTohit: 325, + PoisonCount: 326, + DamageFramerate: 327, + PierceIdx: 328, - // shrine types - shrines: { - Ids: [2, 81, 83, 170, 344, 197, 202], - None: 0, - Refilling: 1, - Health: 2, - Mana: 3, - HealthExchange: 4, - ManaExchange: 5, - Armor: 6, - Combat: 7, - ResistFire: 8, - ResistCold: 9, - ResistLightning: 10, - ResistPoison: 11, - Skill: 12, - ManaRecharge: 13, - Stamina: 14, - Experience: 15, - Enirhs: 16, - Portal: 17, - Gem: 18, - Fire: 19, - Monster: 20, - Exploding: 21, - Poison: 22 - }, + ModifierListSkill: 350, + ModifierListLevel: 351, - // unit states - states: { - None: 0, - FrozenSolid: 1, - Poison: 2, - ResistFire: 3, - ResistCold: 4, - ResistLightning: 5, - ResistMagic: 6, - PlayerBody: 7, - ResistAll: 8, - AmplifyDamage: 9, - FrozenArmor: 10, - Frozen: 11, - Inferno: 12, - Blaze: 13, - BoneArmor: 14, - Concentrate: 15, - Enchant: 16, - InnerSight: 17, - SkillMove: 18, - Weaken: 19, - ChillingArmor: 20, - Stunned: 21, - SpiderLay: 22, - DimVision: 23, - Slowed: 24, - FetishAura: 25, - Shout: 26, - Taunt: 27, - Conviction: 28, - Convicted: 29, - EnergyShield: 30, - Venom: 31, - BattleOrders: 32, - Might: 33, - Prayer: 34, - HolyFire: 35, - Thorns: 36, - Defiance: 37, - ThunderStorm: 38, - LightningBolt: 39, - BlessedAim: 40, - Stamina: 41, - Concentration: 42, - Holywind: 43, - HolyFreeze: 43, - HolywindCold: 44, - HolyFreezeCold: 44, - Cleansing: 45, - HolyShock: 46, - Sanctuary: 47, - Meditation: 48, - Fanaticism: 49, - Redemption: 50, - BattleCommand: 51, - PreventHeal: 52, - Conversion: 53, - Uninterruptable: 54, - IronMaiden: 55, - Terror: 56, - Attract: 57, - LifeTap: 58, - Confuse: 59, - Decrepify: 60, - LowerResist: 61, - OpenWounds: 62, - Dopplezon: 63, - Decoy: 63, - CriticalStrike: 64, - Dodge: 65, - Avoid: 66, - Penetrate: 67, - Evade: 68, - Pierce: 69, - Warmth: 70, - FireMastery: 71, - LightningMastery: 72, - ColdMastery: 73, - SwordMastery: 74, - AxeMastery: 75, - MaceMastery: 76, - PoleArmMastery: 77, - ThrowingMastery: 78, - SpearMastery: 79, - IncreasedStamina: 80, - IronSkin: 81, - IncreasedSpeed: 82, - NaturalResistance: 83, - FingerMageCurse: 84, - NoManaReg: 85, - JustHit: 86, - SlowMissiles: 87, - ShiverArmor: 88, - BattleCry: 89, - Blue: 90, - Red: 91, - DeathDelay: 92, - Valkyrie: 93, - Frenzy: 94, - Berserk: 95, - Revive: 96, - ItemFullSet: 97, - SourceUnit: 98, - Redeemed: 99, - HealthPot: 100, - HolyShield: 101, - JustPortaled: 102, - MonFrenzy: 103, - CorpseNoDraw: 104, - Alignment: 105, - ManaPot: 106, - Shatter: 107, - SyncWarped: 108, - ConversionSave: 109, - Pregnat: 110, - Rabies: 112, - DefenceCurse: 113, - BloodMana: 114, - Burning: 115, - DragonFlight: 116, - Maul: 117, - CorpseNoSelect: 118, - ShadowWarrior: 119, - FeralRage: 120, - SkillDelay: 121, - ProgressiveDamage: 122, - ProgressiveSteal: 123, - ProgressiveOther: 124, - ProgressiveFire: 125, - ProgressiveCold: 126, - ProgressiveLighting: 127, - ShrineArmor: 128, - ShrineCombat: 129, - ShrineResLighting: 130, - ShrineResFire: 131, - ShrineResCold: 132, - ShrineResPoison: 133, - ShrineSkill: 134, - ShrineManaRegen: 135, - ShrineStamina: 136, - ShrineExperience: 137, - FenrisRage: 138, - Wolf: 139, - Wearwolf: 139, - Bear: 140, - Wearbear: 140, - Bloodlust: 141, - ChangeClass: 142, - Attached: 143, - Hurricane: 144, - Armageddon: 145, - Invis: 146, - Barbs: 147, - HeartofWolverine: 148, - OakSage: 149, - VineBeast: 150, - CycloneArmor: 151, - ClawMastery: 152, - CloakofShadows: 153, - Recyled: 154, - WeaponBlock: 155, - Cloaked: 156, - Quickness: 157, // Internal name - BurstofSpeed: 157, // External name - BladeShield: 158, - Fade: 159, - RestInPeace: 172, - Glowing: 175, - Delerium: 177, - Antidote: 178, - Thawing: 179, - StaminaPot: 180, - }, + LastSentHpPct: 352, + SourceUnitType: 353, + SourceUnitId: 354, - enchant: { - ExtraStrong: 5, - ExtraFast: 6, - Cursed: 7, - MagicResistant: 8, - FireEnchanted: 9, - LightningEnchanted: 17, - ColdEnchanted: 18, - ManaBurn: 25, - Teleportation: 26, - SpectralHit: 27, - StoneSkin: 28, - MultipleShots: 29, - }, + SkillThornsPercent: 131, + SkillBoneArmor: 132, + SkillCycloneArmor: 132, + SkillBoneArmorMax: 133, + SkillCycloneArmorMax: 133, + SkillFade: 181, + SkillPoisonOverrideLength: 101, + SkillBypassUndead: 103, + SkillBypassDemons: 104, + SkillBypassBeasts: 106, + SkillHandofAthena: 161, + SkillStaminaPercent: 162, + SkillPassiveStaminaPercent: 163, + SkillConcentration: 164, + SkillEnchant: 165, + SkillPierce: 166, + SkillConviction: 167, + SkillChillingArmor: 168, + SkillFrenzy: 169, + SkillDecrepify: 170, + SkillArmorPercent: 171, - // unit stats - stats: { - StunLength: 66, - VelocityPercent: 67, - OtherAnimrate: 69, - HpRegen: 74, + Strength: 0, + Energy: 1, + Dexterity: 2, + Vitality: 3, + StatPts: 4, + NewSkills: 5, + HitPoints: 6, + MaxHp: 7, + Mana: 8, + MaxMana: 9, + Stamina: 10, + MaxStamina: 11, + Level: 12, + Experience: 13, + Gold: 14, + GoldBank: 15, + ArmorPercent: 16, + MaxDamagePercent: 17, + MinDamagePercent: 18, + EnhancedDamage: 18, + ToHit: 19, + ToBlock: 20, + MinDamage: 21, + MaxDamage: 22, + SecondaryMinDamage: 23, + SecondaryMaxDamage: 24, + DamagePercent: 25, + ManaRecovery: 26, + ManaRecoveryBonus: 27, + StaminaRecoveryBonus: 28, + LastExp: 29, + NextExp: 30, + ArmorClass: 31, + Defense: 31, + ArmorClassVsMissile: 32, + ArmorClassVsHth: 33, + NormalDamageReduction: 34, + MagicDamageReduction: 35, + DamageResist: 36, + MagicResist: 37, + MaxMagicResist: 38, + FireResist: 39, + MaxFireResist: 40, + LightResist: 41, + LightningResist: 41, + MaxLightResist: 42, + ColdResist: 43, + MaxColdResist: 44, + PoisonResist: 45, + MaxPoisonResist: 46, + DamageAura: 47, + FireMinDamage: 48, + FireMaxDamage: 49, + LightMinDamage: 50, + LightMaxDamage: 51, + MagicMinDamage: 52, + MagicMaxDamage: 53, + ColdMinDamage: 54, + ColdMaxDamage: 55, + ColdLength: 56, + PoisonMinDamage: 57, + PoisonMaxDamage: 58, + PoisonLength: 59, + LifeDrainMinDamage: 60, + LifeLeech: 60, + LifeDrainMaxDamage: 61, + ManaDrainMinDamage: 62, + ManaLeech: 62, + ManaDrainMaxDamage: 63, + StaminaDrainMinDamage: 64, + StaminaDrainMaxDamage: 65, + AttackRate: 68, + PreviousSkillRight: 181, + PreviousSkillMiddle: 182, + PreviousSkillLeft: 183, + PassiveFireMastery: 329, + PassiveLightningMastery: 330, + PassiveColdMastery: 331, + PassivePoisonMastery: 332, + PassiveFirePierce: 333, + PassiveLightningPierce: 334, + PassiveColdPierce: 335, + PassivePoisonPierce: 336, + PassiveCriticalStrike: 337, + PassiveDodge: 338, + PassiveAvoid: 339, + PassiveEvade: 340, + PassiveWarmth: 341, + PassiveMasteryMeleeTh: 342, + PassiveMasteryMeleeDmg: 343, + PassiveMasteryMeleeCrit: 344, + PassiveMasteryThrowTh: 345, + PassiveMasteryThrowDmg: 346, + PassiveMasteryThrowCrit: 347, + PassiveWeaponBlock: 348, + PassiveSummonResist: 349, + PassiveMagMastery: 357, + PassiveMagPierce: 358, + Quantity: 70, + Value: 71, + Durability: 72, + MaxDurability: 73, + MaxDurabilityPercent: 75, + MaxHpPercent: 76, + MaxManaPercent: 77, + AttackerTakesDamage: 78, + GoldBonus: 79, + MagicBonus: 80, + Knockback: 81, + TimeDuration: 82, + AddClassSkills: 83, + AddExperience: 85, + HealAfterKill: 86, + ReducedPrices: 87, + DoubleHerbDuration: 88, + LightRadius: 89, + LightColor: 90, + ReqPercent: 91, + LevelReq: 92, + FasterAttackRate: 93, + IAS: 93, + LevelReqPct: 94, + FasterMoveVelocity: 96, + FRW: 96, + NonClassSkill: 97, + OSkill: 97, + FasterGetHitRate: 99, + FHR: 99, + FasterBlockRate: 102, + FBR: 102, + FasterCastRate: 105, + FCR: 105, + SingleSkill: 107, + RestinPeace: 108, + PoisonLengthResist: 110, + NormalDamage: 111, + Howl: 112, + Stupidity: 113, + DamagetoMana: 114, + IgnoreTargetAc: 115, + IgnoreTargetDefense: 115, + FractionalTargetAc: 116, + PreventHeal: 117, + HalfFreezeDuration: 118, + ToHitPercent: 119, + DamageTargetAc: 120, + DemonDamagePercent: 121, + UndeadDamagePercent: 122, + DemontoHit: 123, + UndeadtoHit: 124, + Throwable: 125, + ElemSkill: 126, + AllSkills: 127, + AttackerTakesLightDamage: 128, + Freeze: 134, + OpenWounds: 135, + CrushingBlow: 136, + KickDamage: 137, + ManaAfterKill: 138, + HealAfterDemonKill: 139, + ExtraBlood: 140, + DeadlyStrike: 141, + AbsorbFirePercent: 142, + AbsorbFire: 143, + AbsorbLightPercent: 144, + AbsorbLight: 145, + AbsorbMagicPercent: 146, + AbsorbMagic: 147, + AbsorbColdPercent: 148, + AbsorbCold: 149, + AbsorbSlash: 262, + AbsorbCrush: 263, + AbsorbThrust: 264, + AbsorbSlashPercent: 265, + AbsorbCrushPercent: 266, + AbsorbThrustPercent: 267, + Slow: 150, + Indestructible: 152, + CannotbeFrozen: 153, + StaminaDrainPct: 154, + Reanimate: 155, + Pierce: 156, + MagicArrow: 157, + ExplosiveArrow: 158, + ThrowMinDamage: 159, + ThrowMaxDamage: 160, + AddSkillTab: 188, + NumSockets: 194, + SkillOnAura: 151, + SkillOnAttack: 195, + SkillOnKill: 196, + SkillOnDeath: 197, + SkillOnHit: 198, + SkillOnStrike: 198, + SkillOnLevelUp: 199, + SkillOnGetHit: 201, + SkillWhenStruck: 201, + ChargedSkill: 204, + PerLevelArmor: 214, + PerLevelArmorPercent: 215, + PerLevelHp: 216, + PerLevelMana: 217, + PerLevelMaxDamage: 218, + PerLevelMaxDamagePercent: 219, + PerLevelStrength: 220, + PerLevelDexterity: 221, + PerLevelEnergy: 222, + PerLevelVitality: 223, + PerLevelTohit: 224, + PerLevelTohitPercent: 225, + PerLevelColdDamageMax: 226, + PerLevelFireDamageMax: 227, + PerLevelLtngDamageMax: 228, + PerLevelPoisDamageMax: 229, + PerLevelResistCold: 230, + PerLevelResistFire: 231, + PerLevelResistLtng: 232, + PerLevelResistPois: 233, + PerLevelAbsorbCold: 234, + PerLevelAbsorbFire: 235, + PerLevelAbsorbLtng: 236, + PerLevelAbsorbPois: 237, + PerLevelThorns: 238, + PerLevelFindGold: 239, + PerLevelFindMagic: 240, + PerLevelRegenstamina: 241, + PerLevelStamina: 242, + PerLevelDamageDemon: 243, + PerLevelDamageUndead: 244, + PerLevelTohitDemon: 245, + PerLevelTohitUndead: 246, + PerLevelCrushingblow: 247, + PerLevelOpenwounds: 248, + PerLevelKickDamage: 249, + PerLevelDeadlystrike: 250, + PerLevelFindGems: 251, + ReplenishDurability: 252, + ReplenishQuantity: 253, + ExtraStack: 254, + Find: 255, + SlashDamage: 256, + SlashDamagePercent: 257, + CrushDamage: 258, + CrushDamagePercent: 259, + ThrustDamage: 260, + ThrustDamagePercent: 261, + ArmorByTime: 268, + ArmorPercentByTime: 269, + HpByTime: 270, + ManaByTime: 271, + MaxDamageByTime: 272, + MaxDamagePercentByTime: 273, + StrengthByTime: 274, + DexterityByTime: 275, + EnergyByTime: 276, + VitalityByTime: 277, + TohitByTime: 278, + TohitPercentByTime: 279, + ColdDamageMaxByTime: 280, + FireDamageMaxByTime: 281, + LtngDamageMaxByTime: 282, + PoisDamageMaxByTime: 283, + ResistColdByTime: 284, + ResistFireByTime: 285, + ResistLtngByTime: 286, + ResistPoisByTime: 287, + AbsorbColdByTime: 288, + AbsorbFireByTime: 289, + AbsorbLtngByTime: 290, + AbsorbPoisByTime: 291, + FindGoldByTime: 292, + FindMagicByTime: 293, + RegenstaminaByTime: 294, + StaminaByTime: 295, + DamageDemonByTime: 296, + DamageUndeadByTime: 297, + TohitDemonByTime: 298, + TohitUndeadByTime: 299, + CrushingBlowByTime: 300, + OpenWoundsByTime: 301, + KickDamageByTime: 302, + DeadlyStrikeByTime: 303, + FindGemsByTime: 304, + PierceCold: 305, + PierceFire: 306, + PierceLtng: 307, + PiercePois: 308, + DamageVsMonster: 309, + DamagePercentVsMonster: 310, + TohitVsMonster: 311, + TohitPercentVsMonster: 312, + AcVsMonster: 313, + AcPercentVsMonster: 314, + ExtraCharges: 324, + QuestDifficulty: 356, - LastBlockFrame: 95, - State: 98, - MonsterPlayerCount: 100, + // doesn't exist but define for prototypes + AllRes: 555, + }, - CurseResistance: 109, - IronMaidenLevel: 129, - LifeTapLevel: 130, + // unit info + unittype: { + Player: 0, + NPC: 1, + Monster: 1, + Object: 2, + Missile: 3, + Item: 4, + Stairs: 5, // ToDo: might be more as stairs + }, - Alignment: 172, - Target0: 173, - Target1: 174, - GoldLost: 175, - MinimumRequiredLevel: 176, - ConversionLevel: 176, - ConversionMaxHp: 177, - UnitDooverlay: 178, - AttackVsMontype: 179, - DamageVsMontype: 180, + player: { + flag: { + Ignore: 2, + Hostile: 8, + }, + slot: { + Main: 0, + Secondary: 1 + }, + move: { + Walk: 0, + Run: 1 + }, + mode: { // sdk.player.mode. + Death: 0, + StandingOutsideTown: 1, + Walking: 2, + Running: 3, + GettingHit: 4, + StandingInTown: 5, + WalkingInTown: 6, + Attacking1: 7, + Attacking2: 8, + Blocking: 9, + CastingSkill: 10, + ThrowingItem: 11, + Kicking: 12, + UsingSkill1: 13, + UsingSkill2: 14, + UsingSkill3: 15, + UsingSkill4: 16, + Dead: 17, + SkillActionSequence: 18, + KnockedBack: 19, + }, + class: { + Amazon: 0, + Sorceress: 1, + Necromancer: 2, + Paladin: 3, + Barbarian: 4, + Druid: 5, + Assassin: 6, - ArmorOverridePercent: 182, - FireLength: 315, - BurningMin: 316, - BurningMax: 317, - ProgressiveDamage: 318, - ProgressiveSteal: 319, - ProgressiveOther: 320, - ProgressiveFire: 321, - ProgressiveCold: 322, - ProgressiveLightning: 323, - ProgressiveTohit: 325, - PoisonCount: 326, - DamageFramerate: 327, - PierceIdx: 328, + nameOf: function (classid) { + if (classid === undefined || typeof classid !== "number") return false; + if (classid < 0 || classid > 6) return false; + return ["Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin"][classid]; + } + } + }, - ModifierListSkill: 350, - ModifierListLevel: 351, + npcs: { + // same as monsters but more clear to use units.npcs.mode + mode: { + Death: 0, + Standing: 1, + Walking: 2, + GettingHit: 3, + Attacking1: 4, + Attacking2: 5, + Blocking: 6, + CastingSkill: 7, + UsingSkill1: 8, + UsingSkill2: 9, + UsingSkill3: 10, + UsingSkill4: 11, + Dead: 12, + KnockedBack: 13, + Spawning: 14, + Running: 15 + }, - LastSentHpPct: 352, - SourceUnitType: 353, - SourceUnitId: 354, + Akara: 148, + Alkor: 254, + Asheara: 252, + WarrivAct1: 155, + WarrivAct2: 175, + Atma: 176, + Tyrael: 367, + Tyrael2: 251, + Tyrael3: 521, + Charsi: 154, + DeckardCain1: 146, + DeckardCain2: 244, + DeckardCain3: 245, + DeckardCain4: 246, + DeckardCain5: 265, + DeckardCain6: 520, + Drognan: 177, + Elzix: 199, + Fara: 178, + Gheed: 147, + Greiz: 198, + Halbu: 257, + Hratli: 253, + Jamella: 405, + Jerhyn: 201, + Kaelan: 331, + Kashya: 150, + Larzuk: 511, + Lysander: 202, + Malah: 513, + Meshif: 210, + Meshif2: 264, + Natalya: 297, + Ormus: 255, + NihlathakNPC: 526, + Qualkehk: 515, + RogueScout: 270, + TempleGuard1: 52, + TempleGuard2: 665, + TempleGuard3: 666, + Townguard1: 535, + Townguard2: 536, + }, - SkillThornsPercent: 131, - SkillBoneArmor: 132, - SkillBoneArmorMax: 133, - SkillFade: 181, - SkillPoisonOverrideLength: 101, - SkillBypassUndead: 103, - SkillBypassDemons: 104, - SkillBypassBeasts: 106, - SkillHandofAthena: 161, - SkillStaminaPercent: 162, - SkillPassiveStaminaPercent: 163, - SkillConcentration: 164, - SkillEnchant: 165, - SkillPierce: 166, - SkillConviction: 167, - SkillChillingArmor: 168, - SkillFrenzy: 169, - SkillDecrepify: 170, - SkillArmorPercent: 171, + objects: { + mode: { + Inactive: 0, + Interacted: 1, + Active: 2, + }, - Strength: 0, - Energy: 1, - Dexterity: 2, - Vitality: 3, - StatPts: 4, - NewSkills: 5, - HitPoints: 6, - MaxHp: 7, - Mana: 8, - MaxMana: 9, - Stamina: 10, - MaxStamina: 11, - Level: 12, - Experience: 13, - Gold: 14, - GoldBank: 15, - ArmorPercent: 16, - MaxDamagePercent: 17, - MinDamagePercent: 18, - EnhancedDamage: 18, - ToHit: 19, - ToBlock: 20, - MinDamage: 21, - MaxDamage: 22, - SecondaryMinDamage: 23, - SecondaryMaxDamage: 24, - DamagePercent: 25, - ManaRecovery: 26, - ManaRecoveryBonus: 27, - StaminaRecoveryBonus: 28, - LastExp: 29, - NextExp: 30, - ArmorClass: 31, - Defense: 31, - ArmorClassVsMissile: 32, - ArmorClassVsHth: 33, - NormalDamageReduction: 34, - MagicDamageReduction: 35, - DamageResist: 36, - MagicResist: 37, - MaxMagicResist: 38, - FireResist: 39, - MaxFireResist: 40, - LightResist: 41, - LightningResist: 41, - MaxLightResist: 42, - ColdResist: 43, - MaxColdResist: 44, - PoisonResist: 45, - MaxPoisonResist: 46, - DamageAura: 47, - FireMinDamage: 48, - FireMaxDamage: 49, - LightMinDamage: 50, - LightMaxDamage: 51, - MagicMinDamage: 52, - MagicMaxDamage: 53, - ColdMinDamage: 54, - ColdMaxDamage: 55, - ColdLength: 56, - PoisonMinDamage: 57, - PoisonMaxDamage: 58, - PoisonLength: 59, - LifeDrainMinDamage: 60, - LifeLeech: 60, - LifeDrainMaxDamage: 61, - ManaDrainMinDamage: 62, - ManaLeech: 62, - ManaDrainMaxDamage: 63, - StaminaDrainMinDamage: 64, - StaminaDrainMaxDamage: 65, - AttackRate: 68, - PreviousSkillRight: 181, - PreviousSkillMiddle: 182, - PreviousSkillLeft: 183, - PassiveFireMastery: 329, - PassiveLightningMastery: 330, - PassiveColdMastery: 331, - PassivePoisonMastery: 332, - PassiveFirePierce: 333, - PassiveLightningPierce: 334, - PassiveColdPierce: 335, - PassivePoisonPierce: 336, - PassiveCriticalStrike: 337, - PassiveDodge: 338, - PassiveAvoid: 339, - PassiveEvade: 340, - PassiveWarmth: 341, - PassiveMasteryMeleeTh: 342, - PassiveMasteryMeleeDmg: 343, - PassiveMasteryMeleeCrit: 344, - PassiveMasteryThrowTh: 345, - PassiveMasteryThrowDmg: 346, - PassiveMasteryThrowCrit: 347, - PassiveWeaponBlock: 348, - PassiveSummonResist: 349, - PassiveMagMastery: 357, - PassiveMagPierce: 358, - Quantity: 70, - Value: 71, - Durability: 72, - MaxDurability: 73, - MaxDurabilityPercent: 75, - MaxHpPercent: 76, - MaxManaPercent: 77, - AttackerTakesDamage: 78, - GoldBonus: 79, - MagicBonus: 80, - Knockback: 81, - TimeDuration: 82, - AddClassSkills: 83, - AddExperience: 85, - HealAfterKill: 86, - ReducedPrices: 87, - DoubleHerbDuration: 88, - LightRadius: 89, - LightColor: 90, - ReqPercent: 91, - LevelReq: 92, - FasterAttackRate: 93, - IAS: 93, - LevelReqPct: 94, - FasterMoveVelocity: 96, - FRW: 96, - NonClassSkill: 97, - OSkill: 97, - FasterGetHitRate: 99, - FHR: 99, - FasterBlockRate: 102, - FBR: 102, - FasterCastRate: 105, - FCR: 105, - SingleSkill: 107, - RestinPeace: 108, - PoisonLengthResist: 110, - NormalDamage: 111, - Howl: 112, - Stupidity: 113, - DamagetoMana: 114, - IgnoreTargetAc: 115, - IgnoreTargetDefense: 115, - FractionalTargetAc: 116, - PreventHeal: 117, - HalfFreezeDuration: 118, - ToHitPercent: 119, - DamageTargetAc: 120, - DemonDamagePercent: 121, - UndeadDamagePercent: 122, - DemontoHit: 123, - UndeadtoHit: 124, - Throwable: 125, - ElemSkill: 126, - AllSkills: 127, - AttackerTakesLightDamage: 128, - Freeze: 134, - OpenWounds: 135, - CrushingBlow: 136, - KickDamage: 137, - ManaAfterKill: 138, - HealAfterDemonKill: 139, - ExtraBlood: 140, - DeadlyStrike: 141, - AbsorbFirePercent: 142, - AbsorbFire: 143, - AbsorbLightPercent: 144, - AbsorbLight: 145, - AbsorbMagicPercent: 146, - AbsorbMagic: 147, - AbsorbColdPercent: 148, - AbsorbCold: 149, - AbsorbSlash: 262, - AbsorbCrush: 263, - AbsorbThrust: 264, - AbsorbSlashPercent: 265, - AbsorbCrushPercent: 266, - AbsorbThrustPercent: 267, - Slow: 150, - Indestructible: 152, - CannotbeFrozen: 153, - StaminaDrainPct: 154, - Reanimate: 155, - Pierce: 156, - MagicArrow: 157, - ExplosiveArrow: 158, - ThrowMinDamage: 159, - ThrowMaxDamage: 160, - AddSkillTab: 188, - NumSockets: 194, - SkillOnAura: 151, - SkillOnAttack: 195, - SkillOnKill: 196, - SkillOnDeath: 197, - SkillOnHit: 198, - SkillOnStrike: 198, - SkillOnLevelUp: 199, - SkillOnGetHit: 201, - SkillWhenStruck: 201, - ChargedSkill: 204, - PerLevelArmor: 214, - PerLevelArmorPercent: 215, - PerLevelHp: 216, - PerLevelMana: 217, - PerLevelMaxDamage: 218, - PerLevelMaxDamagePercent: 219, - PerLevelStrength: 220, - PerLevelDexterity: 221, - PerLevelEnergy: 222, - PerLevelVitality: 223, - PerLevelTohit: 224, - PerLevelTohitPercent: 225, - PerLevelColdDamageMax: 226, - PerLevelFireDamageMax: 227, - PerLevelLtngDamageMax: 228, - PerLevelPoisDamageMax: 229, - PerLevelResistCold: 230, - PerLevelResistFire: 231, - PerLevelResistLtng: 232, - PerLevelResistPois: 233, - PerLevelAbsorbCold: 234, - PerLevelAbsorbFire: 235, - PerLevelAbsorbLtng: 236, - PerLevelAbsorbPois: 237, - PerLevelThorns: 238, - PerLevelFindGold: 239, - PerLevelFindMagic: 240, - PerLevelRegenstamina: 241, - PerLevelStamina: 242, - PerLevelDamageDemon: 243, - PerLevelDamageUndead: 244, - PerLevelTohitDemon: 245, - PerLevelTohitUndead: 246, - PerLevelCrushingblow: 247, - PerLevelOpenwounds: 248, - PerLevelKickDamage: 249, - PerLevelDeadlystrike: 250, - PerLevelFindGems: 251, - ReplenishDurability: 252, - ReplenishQuantity: 253, - ExtraStack: 254, - Find: 255, - SlashDamage: 256, - SlashDamagePercent: 257, - CrushDamage: 258, - CrushDamagePercent: 259, - ThrustDamage: 260, - ThrustDamagePercent: 261, - ArmorByTime: 268, - ArmorPercentByTime: 269, - HpByTime: 270, - ManaByTime: 271, - MaxDamageByTime: 272, - MaxDamagePercentByTime: 273, - StrengthByTime: 274, - DexterityByTime: 275, - EnergyByTime: 276, - VitalityByTime: 277, - TohitByTime: 278, - TohitPercentByTime: 279, - ColdDamageMaxByTime: 280, - FireDamageMaxByTime: 281, - LtngDamageMaxByTime: 282, - PoisDamageMaxByTime: 283, - ResistColdByTime: 284, - ResistFireByTime: 285, - ResistLtngByTime: 286, - ResistPoisByTime: 287, - AbsorbColdByTime: 288, - AbsorbFireByTime: 289, - AbsorbLtngByTime: 290, - AbsorbPoisByTime: 291, - FindGoldByTime: 292, - FindMagicByTime: 293, - RegenstaminaByTime: 294, - StaminaByTime: 295, - DamageDemonByTime: 296, - DamageUndeadByTime: 297, - TohitDemonByTime: 298, - TohitUndeadByTime: 299, - CrushingBlowByTime: 300, - OpenWoundsByTime: 301, - KickDamageByTime: 302, - DeadlyStrikeByTime: 303, - FindGemsByTime: 304, - PierceCold: 305, - PierceFire: 306, - PierceLtng: 307, - PiercePois: 308, - DamageVsMonster: 309, - DamagePercentVsMonster: 310, - TohitVsMonster: 311, - TohitPercentVsMonster: 312, - AcVsMonster: 313, - AcPercentVsMonster: 314, - ExtraCharges: 324, - QuestDifficulty: 356, + chestIds: [ + 5, 6, 87, 104, 105, 106, 107, 143, 140, 141, 144, 146, 147, 148, 176, 177, 181, 183, 198, 240, 241, + 242, 243, 329, 330, 331, 332, 333, 334, 335, 336, 354, 355, 356, 371, 387, 389, 390, 391, 397, 405, + 406, 407, 413, 420, 424, 425, 430, 431, 432, 433, 454, 455, 501, 502, 504, 505, 580, 581 + ], - // doesn't exist but define for prototypes - AllRes: 555, - }, + // act1 + MoldyTome: 8, + A1TownFire: 39, + A1Waypoint: 119, + StoneAlpha: 17, + StoneBeta: 18, + StoneGamma: 19, + StoneDelta: 20, + StoneLambda: 21, + StoneTheta: 22, + CainsJail: 26, + InifussTree: 30, + Malus: 108, - // unit info - unittype: { - Player: 0, - NPC: 1, - Monster: 1, - Object: 2, - Missile: 3, - Item: 4, - Stairs: 5, // ToDo: might be more as stairs - }, + // act 2 + A2Waypoint: 156, + A2UndergroundUpStairs: 22, + TrapDoorA2: 74, // ancienttunnel/sewers act 2 + DoorbyDockAct2: 75, // incorrect ? TODO: figure out what 75 really corresponds to since the door is obj type 5 with classid 20 + PortaltoDurielsLair: 100, + HoradricStaffHolder: 152, + ArcaneSanctuaryPortal: 298, + HoradricCubeChest: 354, + HoradricScrollChest: 355, + Journal: 357, - player: { - slot: { - Main: 0, - Secondary: 1 - }, - move: { - Walk: 0, - Run: 1 - }, - mode: { // sdk.player.mode. - Death: 0, - StandingOutsideTown: 1, - Walking: 2, - Running: 3, - GettingHit: 4, - StandingInTown: 5, - WalkingInTown: 6, - Attacking1: 7, - Attacking2: 8, - Blocking: 9, - CastingSkill: 10, - ThrowingItem: 11, - Kicking: 12, - UsingSkill1: 13, - UsingSkill2: 14, - UsingSkill3: 15, - UsingSkill4: 16, - Dead: 17, - SkillActionSequence: 18, - KnockedBack: 19, - }, - class: { - Amazon: 0, - Sorceress: 1, - Necromancer: 2, - Paladin: 3, - Barbarian: 4, - Druid: 5, - Assassin: 6, + // act 3 + A3Waypoint: 237, + ForestAltar: 81, + LamEsensTome: 193, + SewerStairsA3: 366, + SewerLever: 367, + DuranceEntryStairs: 386, + RedPortalToAct4: 342, + CompellingOrb: 404, - nameOf: function (classid) { - if (classid === undefined || typeof classid !== "number") return false; - if (classid < 0 || classid > 6) return false; - return ["Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin"][classid]; - } - } - }, + // act 4 + A4Waypoint: 398, + SealGlow: 131, + DiabloStar: 255, + DiabloSealInfector: 392, + DiabloSealInfector2: 393, + DiabloSealSeis: 394, + DiabloSealVizier: 396, + DiabloSealVizier2: 395, + RedPortalToAct5: 566, // The one of tyreal - npcs: { - // same as monsters but more clear to use units.npcs.mode - mode: { - Death: 0, - Standing: 1, - Walking: 2, - GettingHit: 3, - Attacking1: 4, - Attacking2: 5, - Blocking: 6, - CastingSkill: 7, - UsingSkill1: 8, - UsingSkill2: 9, - UsingSkill3: 10, - UsingSkill4: 11, - Dead: 12, - KnockedBack: 13, - Spawning: 14, - Running: 15 - }, + // act 5 + A5Waypoint: 429, + SideCavesA5: 75, // FrozenRiver, DrifterCavern, IcyCellar + Act5Gate: 449, + KorlictheProtectorStatue: 474, + TalictheDefenderStatue: 475, + MadawctheGuardianStatue: 476, + AncientsAltar: 546, + ArreatEnterAncientsWay: 564, + ArreatEnterWorldstone: 547, + //AncientsDoor: 547, + AncientsDoor: 547, // Worldstone keep lvl 1 + FrozenAnya: 558, + FrozenAnyasPlatform: 460, + NihlathaksPlatform: 462, + WorldstonePortal: 563, - Akara: 148, - Alkor: 254, - Asheara: 252, - WarrivAct1: 155, - WarrivAct2: 175, - Atma: 176, - Tyrael: 367, - Tyrael2: 251, - Tyrael3: 521, - Charsi: 154, - DeckardCain1: 146, - DeckardCain2: 244, - DeckardCain3: 245, - DeckardCain4: 246, - DeckardCain5: 265, - DeckardCain6: 520, - Drognan: 177, - Elzix: 199, - Fara: 178, - Gheed: 147, - Greiz: 198, - Halbu: 257, - Hratli: 253, - Jamella: 405, - Jerhyn: 201, - Kaelan: 331, - Kashya: 150, - Larzuk: 511, - Lysander: 202, - Malah: 513, - Meshif: 210, - Meshif2: 264, - Natalya: 297, - Ormus: 255, - NihlathakNPC: 526, - Qualkehk: 515, - RogueScout: 270, - TempleGuard1: 52, - TempleGuard2: 665, - TempleGuard3: 666, - Townguard1: 535, - Townguard2: 536, - }, + FrigidHighlandsChest: 455, + IcyCellarChest: 397, - objects: { - mode: { - Inactive: 0, - Interacted: 1, - Active: 2, - }, + SmallSparklyChest: 397, + LargeSparklyChest: 455, + SuperChest: 580, - // act1 - A1TownFire: 39, - A1Waypoint: 119, - StoneAlpha: 17, - StoneBeta: 18, - StoneGamma: 19, - StoneDelta: 20, - StoneLambda: 21, - StoneTheta: 22, - CainsJail: 26, - InifussTree: 30, - Malus: 108, + // misc + BubblingPoolofBlood: 82, + HornShrine: 83, + Stash: 267, + BluePortal: 59, + RedPortal: 60, + Smoke: 401, + Well: 132, + Wells: [130, 132, 322], + }, - // act 2 - A2Waypoint: 156, - A2UndergroundUpStairs: 22, - TrapDoorA2: 74, // ancienttunnel/sewers act 2 - DoorbyDockAct2: 75, // incorrect ? TODO: figure out what 75 really corresponds to since the door is obj type 5 with classid 20 - PortaltoDurielsLair: 100, - HoradricStaffHolder: 152, - ArcaneSanctuaryPortal: 298, - HoradricCubeChest: 354, - HoradricScrollChest: 355, - Journal: 357, + exits: { + type: { + WalkThrough: 1, + Stairs: 2, + RedPortal: 60, + }, + preset: { + AreaEntrance: 0, // special + // act 1 + CaveHoleUp: 4, + CaveHoleLvl2: 5, + Crypt: 6, + Mausoleum: 7, + CryptMausExit: 8, + JailUpStairs: 13, + JailDownStairs: 14, + CathedralDownStairs: 15, + CathedralUpStairs: 16, + CatacombsUpStairs: 17, + CatacombsDownStairs: 18, - // act 3 - A3Waypoint: 237, - ForestAltar: 81, - LamEsensTome: 193, - SewerStairsA3: 366, - SewerLever: 367, - DuranceEntryStairs: 386, - RedPortalToAct4: 342, - CompellingOrb: 404, + // act 2 + A2SewersTrapDoor: 19, + A2EnterSewersDoor: 20, + A2ExitSewersDoor: 21, + A2UndergroundUpStairs: 22, + A2DownStairs: 23, + EnterHaremStairs: 24, + ExitHaremStairs: 25, + PreviousLevelHaremRight: 26, + PreviousLevelHaremLeft: 27, + NextLevelHaremRight: 28, + NextLevelHaremLeft: 29, + PreviousPalaceRight: 30, + PreviousPalaceLeft: 31, + NextLevelPalace: 32, + EnterStonyTomb: 33, + EnterHalls: 36, + EnterTalTomb1: 38, + EnterTalTomb2: 39, + EnterTalTomb3: 40, + EnterTalTomb4: 41, + EnterTalTomb5: 42, + EnterTalTomb6: 43, + EnterTalTomb7: 44, + PreviousAreaTomb: 45, + NextLevelTomb: 46, + EnterMaggotLair: 47, + PreviousAreaMaggotLair: 48, + NextLevelMaggotLair: 49, + AncientTunnelsTrapDoor: 50, + EntrancetoDurielsLair: 100, - // act 4 - A4Waypoint: 398, - SealGlow: 131, - DiabloStar: 255, - DiabloSealInfector: 392, - DiabloSealInfector2: 393, - DiabloSealSeis: 394, - DiabloSealVizier: 396, - DiabloSealVizier2: 395, - RedPortalToAct5: 566, // The one of tyreal + // act 3 + EnterSpiderHole: 51, + ExitSpiderHole: 52, + EnterPit: 53, + EnterDungeon: 54, + PreviousAreaDungeon: 55, + NextLevelDungeon: 56, + A3EnterSewers: 57, + A3ExitSewersUpperK: 58, + A3SewersPreviousArea: 58, + A3ExitSewers: 59, + A3NextLevelSewers: 60, + EnterTemple: 61, + ExitTemple: 63, + EnterDurance: 64, + PreviousLevelDurance: 65, + NextLevelDurance: 68, + SewerStairsA3: 366, + DuranceEntryStairs: 386, - // act 5 - A5Waypoint: 429, - SideCavesA5: 75, // FrozenRiver, DrifterCavern, IcyCellar - Act5Gate: 449, - KorlictheProtectorStatue: 474, - TalictheDefenderStatue: 475, - MadawctheGuardianStatue: 476, - AncientsAltar: 546, - ArreatEnterAncientsWay: 564, - ArreatEnterWorldstone: 547, - //AncientsDoor: 547, - AncientsDoor: 547, // Worldstone keep lvl 1 - FrozenAnya: 558, - FrozenAnyasPlatform: 460, - NihlathaksPlatform: 462, - WorldstonePortal: 563, + // act 4 + EnterRiverStairs: 69, + ExitRiverStairs: 70, + // act 5 + EnterCrystal: 71, + A5ExitCave: 73, + A5NextLevelCave: 74, + EnterSubLevelCave: 75, + EnterNithsTemple: 76, + PreviousAreaNithsTemple: 77, + NextAreaNithsTemple: 78, + ArreatEnterAncientsWay: 79, + ArreatEnterWorldstone: 80, + PreviousAreaWorldstone: 81, + NextAreaWorldstone: 82, + }, + }, + + monsters: { + preset: { + // Confirmed + Izual: 256, + Bishibosh: 734, + Bonebreak: 735, + Coldcrow: 736, + Rakanishu: 737, + TreeheadWoodFist: 738, + Griswold: 739, + TheCountess: 740, + PitspawnFouldog: 741, + FlamespiketheCrawler: 742, + BoneAsh: 743, + Radament: 744, + BloodwitchtheWild: 745, + Fangskin: 746, + Beetleburst: 747, + CreepingFeature: 748, + ColdwormtheBurrower: 749, + FireEye: 750, + DarkElder: 751, + TheSummoner: 752, + AncientKaatheSoulless: 753, + TheSmith: 754, + SszarktheBurning: 755, + WitchDoctorEndugu: 756, + Stormtree: 757, + BattlemaidSarina: 758, + IcehawkRiftwing: 759, + IsmailVilehand: 760, + GelebFlamefinger: 761, + BremmSparkfist: 762, + ToorcIcefist: 763, + WyandVoidfinger: 764, + MafferDragonhand: 765, + WingedDeath: 766, + Taintbreeder: 768, + RiftwraiththeCannibal: 769, + InfectorofSouls: 770, + LordDeSeis: 771, + GrandVizierofChaos: 772, + TheCowKing: 773, + Corpsefire: 774, + Hephasto: 775, + ShenktheOverseer: 776, + TalictheDefender: 777, + MadawctheGuardian: 778, + KorlictheProtector: 779, + AxeDweller: 780, + BonesawBreaker: 781, + DacFarren: 782, + EldritchtheRectifier: 783, + EyebacktheUnleashed: 784, + ThreshSocket: 785, + Pindleskin: 786, + SnapchipShatter: 787, + AnodizedElite: 788, + VinvearMolech: 789, + SharpToothSayer: 790, + MagmaTorquer: 791, + BlazeRipper: 792, + Frozenstein: 793, + Nihlathak: 794, + ColenzotheAnnihilator: 795, + AchmeltheCursed: 796, + BartuctheBloody: 797, + VentartheUnholy: 798, + ListertheTormentor: 799, + BloodRaven: 805, - FrigidHighlandsChest: 455, - IcyCellarChest: 397, + // Unconfirmed + // Questionable + GriefGrumble: 741, // JailLvl2 + UniqueJailLvl3: 273, + UniqueArcaneSanctuary: 371, + }, + mode: { + Death: 0, + Standing: 1, + Walking: 2, + GettingHit: 3, + Attacking1: 4, + Attacking2: 5, + Blocking: 6, + CastingSkill: 7, + UsingSkill1: 8, + UsingSkill2: 9, + UsingSkill3: 10, + UsingSkill4: 11, + Dead: 12, + KnockedBack: 13, + Spawning: 14, + Running: 15 + }, + spectype: { + All: 0, + Super: 1, + Champion: 2, + Unique: 4, + SuperUnique: 5, + Magic: 6, + Minion: 8, + }, + // todo - determine what all these correlate to + type: { + Undead: 1, + Demon: 2, + Insect: 3, + Human: 4, + Construct: 5, + LowUndead: 6, + HighUndead: 7, + Skeleton: 8, + Zombie: 9, + BigHead: 10, + FoulCrow: 11, + Fallen: 12, + Brute: 13, + SandRaider: 14, + Wraith: 15, + CorruptRogue: 16, + Baboon: 17, + GoatMan: 18, + QuillRat: 19, + SandMaggot: 20, + Viper: 21, + SandLeaper: 22, + PantherWoman: 23, + Swarm: 24, + Scarab: 25, + Mummy: 26, + Unraveler: 27, + Vulture: 28, + Mosquito: 29, + WillowWisp: 30, + Arach: 31, + ThornHulk: 32, + Vampire: 33, + BatDemon: 34, + Fetish: 35, + Blunderbore: 36, + UndeadFetish: 37, + Zakarum: 38, + FrogDemon: 39, + Tentacle: 40, + FingerMage: 41, + Golem: 42, + Vilekind: 43, + Regurgitator: 44, + DoomKnight: 45, + CouncilMember: 46, + MegaDemon: 47, + Bovine: 48, + SeigeBeast: 49, + SnowYeti: 50, + Minion: 51, + Succubus: 52, + Overseer: 53, + Imp: 54, + FrozenHorror: 55, + BloodLord: 56, + DeathMauler: 57, + PutridDefiler: 58, + }, + DiablosBoneCage: 340, + DiablosBoneCage2: 342, + Dummy1: 149, + Dummy2: 268, + AbyssKnight: 311, + Afflicted: 10, + Afflicted2: 580, + AlbinoRoach: 95, + Ancient1: 104, + Ancient2: 669, + Ancient3: 670, + Apparition: 41, + Arach1: 122, + Arach2: 685, + Assailant: 33, + Assailant2: 603, + BaalColdMage: 381, + Balrog1: 360, + Balrog2: 686, + Banished: 135, + Barbs: 422, + Bear1: 428, + Bear2: 431, + Beast: 441, + BerserkSlayer: 462, + BlackArcher: 163, + BlackLancer1: 168, + BlackLancer2: 617, + BlackLocusts: 88, + BlackRaptor1: 17, + BlackRaptor2: 592, + BlackRogue: 46, + BlackSoul1: 121, + BlackSoul2: 640, + BlackVultureNest: 208, + BloodBoss: 482, + BloodBringer: 443, + BloodClan1: 55, + BloodClan2: 588, + BloodDiver: 139, + BloodGolem: 290, + BloodHawk1: 16, + BloodHawk2: 591, + BloodHawkNest: 207, + BloodHook: 116, + BloodHookNest: 336, + BloodLord1: 134, + BloodLord2: 695, + BloodWing: 117, + BloodWingNest: 337, + Blunderbore1: 186, + Blunderbore2: 618, + BoneArcher1: 172, + BoneArcher2: 576, + BoneMage1: 275, + BoneMage2: 380, + BoneMage3: 384, + BoneMage4: 388, + BoneMage5: 624, + BoneWarrior1: 2, + BoneWarrior2: 648, + HellBovine: 391, + BrambleHulk: 128, + Brute: 24, + Bunny: 556, + BurningDead: 3, + BurningDeadArcher1: 173, + BurningDeadArcher2: 575, + BurningDeadArcher3: 577, + BurningDeadMage1: 276, + BurningDeadMage2: 385, + BurningDeadMage3: 389, + BurningDeadMage4: 621, + BurningSoul1: 641, + BurningSoul2: 120, + Cadaver1: 100, + Cadaver2: 703, + Cantor: 239, + CarrionBird1: 110, + CarrionBird2: 608, + Carver1: 642, + Carver2: 20, + CarverShaman: 645, + CarverShaman2: 59, + CaveLeaper1: 79, + CaveLeaper2: 629, + ClawViper1: 74, + ClawViper2: 594, + CloudStalker1: 18, + CloudStalker2: 593, + CloudStalkerNest: 209, + Combatant1: 522, + Combatant2: 523, + ConsumedFireBoar: 464, + ConsumedIceBoar: 463, + CorpseSpitter: 308, + Corpulent: 307, + Creature1: 248, + Creature2: 427, + Creeper: 413, + CrushBiest: 442, + Crusher: 26, + Damned1: 14, + Damned2: 584, + DarkArcher1: 162, + DarkArcher2: 614, + DarkFamiliar: 140, + DarkHunter: 43, + DarkLancer1: 167, + DarkLancer2: 616, + DarkLord1: 133, + DarkLord2: 697, + DarkOne1: 22, + DarkOne2: 644, + DarkRanger: 160, + DarkShaman1: 61, + DarkShaman2: 647, + DarkShape: 42, + DarkSpearwoman: 165, + DarkStalker: 45, + DeamonSteed: 445, + DeathClan1: 57, + DeathClan2: 589, + Decayed: 97, + DefiledWarrior: 440, + Defiler1: 546, + Defiler2: 547, + Defiler3: 548, + Defiler4: 549, + Defiler5: 550, + DesertWing: 136, + Destruction: 410, + Devilkin: 643, + Devilkin2: 21, + DevilkinShaman: 646, + DevilkinShaman2: 60, + Devourer: 70, + DevourerEgg: 192, + DevourerQueen: 286, + DevourerYoung: 182, + Disfigured: 13, + Disfigured2: 583, + Dominus1: 474, + Dominus2: 636, + DoomApe: 51, + DoomKnight: 310, + DoomKnight1: 699, + DoomKnight2: 700, + Drehya1: 512, + Drehya2: 527, + DriedCorpse: 96, + DrownedCarcass: 8, + DuneBeast: 48, + DungSoldier: 91, + Dweller: 247, + Eagle: 429, + Embalmed: 98, + Faithful: 236, + Fallen: 19, + FallenShaman: 58, + FanaticMinion: 461, + Feeder: 115, + FeederNest: 335, + Fenris: 421, + Fetish1: 142, + BoneFetish2: 213, + Fetish3: 397, + FetishShaman: 279, + Fiend1: 137, + Fiend2: 651, + FireBoar: 456, + FireTower: 372, + FlameSpider: 125, + Flayer1: 143, + BoneFetish3: 214, + Flayer3: 398, + Flayer4: 659, + Flayer5: 656, + FlayerShaman1: 280, + FlayerShaman2: 662, + FleshArcher: 164, + FleshBeast1: 301, + FleshBeast2: 678, + FleshHunter: 47, + FleshLancer: 169, + FleshSpawner1: 298, + FleshSpawner2: 676, + FlyingScimitar: 234, + FoulCrow: 15, + FoulCrow2: 590, + FoulCrowNest: 206, + FrenziedHellSpawn: 465, + FrenziedIceSpawn: 466, + GargantuanBeast: 28, + Geglash: 200, + Ghost1: 38, + Ghost2: 631, + Ghoul: 7, + GhoulLord1: 131, + GhoulLord2: 696, + GiantLamprey: 71, + GiantLampreyEgg: 193, + GiantLampreyQueen: 287, + GiantLampreyYoung: 183, + GiantUrchin: 317, + Gloam1: 118, + Gloam2: 639, + Gloombat1: 138, + Gloombat2: 650, + Gorbelly: 187, + GoreBearer: 444, + GreaterHellSpawn1: 459, + GreaterHellSpawn2: 684, + GreaterIceSpawn: 460, + Groper: 304, + Grotesque1: 300, + Grotesque2: 675, + GrotesqueWyrm1: 303, + GrotesqueWyrm2: 677, + Guardian1: 102, + Guardian2: 667, + Hawk: 419, + Heirophant1: 240, + Heirophant2: 241, + Heirophant3: 673, + Heirophant4: 674, + HellBuzzard: 112, + HellCat: 86, + HellClan1: 56, + HellClan2: 587, + HellSlinger: 376, + HellSpawn1: 457, + HellSpawn2: 683, + HellSwarm: 90, + HellWhip: 483, + HollowOne: 101, + Horror: 4, + Horror1: 501, + Horror2: 502, + Horror3: 503, + Horror4: 504, + Horror5: 505, + HorrorArcher1: 174, + HorrorArcher2: 579, + HorrorMage1: 277, + HorrorMage2: 382, + HorrorMage3: 386, + HorrorMage4: 390, + HorrorMage5: 623, + HorrorMage6: 625, + HorrorMage7: 626, + Hs1: 560, + HungryDead: 6, + Huntress1: 83, + Huntress2: 627, + Hut: 528, + Hydra1: 351, + Hydra2: 352, + Hydra3: 353, + IceBoar: 455, + IceSpawn: 458, + Imp1: 492, + Imp2: 493, + Imp3: 494, + Imp4: 495, + Imp5: 496, + Imp6: 688, + Imp7: 689, + Infidel1: 32, + Infidel2: 600, + InsaneHellSpawn: 467, + InsaneIceSpawn: 468, + Invader1: 31, + Invader2: 602, + Itchies: 87, + JungleHunter: 50, + JungleUrchin: 67, + Larva: 283, + Lasher: 480, + LightningSpire: 371, + Lord1: 506, + Lord2: 507, + Lord3: 508, + Lord4: 509, + Lord5: 510, + Lord6: 652, + Lord7: 653, + Maggot: 227, + Malachai: 408, + Marauder: 30, + Marauder2: 599, + Master: 418, + Mauler: 188, + Mauler1: 529, + Mauler12: 604, + Mauler2: 530, + Mauler3: 531, + Mauler4: 532, + Mauler5: 533, + Mauler6: 619, + MawFiend: 694, + MawFiend2: 309, + Council1: 345, + Council2: 346, + Council3: 347, + Council4: 557, + Minion1: 572, + Minion2: 573, + Enslaved: 453, + MinionSlayerSpawner: 485, + MinionSpawner: 484, + Misshapen1: 12, + Misshapen2: 582, + MoonClan1: 53, + MoonClan2: 585, + BaalSubjectMummy: 105, + Navi: 266, + Flavie: 266, + NightClan1: 54, + NightClan2: 586, + NightLord: 132, + NightMarauder: 295, + NightSlinger1: 375, + NightSlinger2: 395, + NightTiger: 85, + OblivionKnight1: 312, + OblivionKnight2: 701, + OblivionKnight3: 702, + OverLord: 481, + OverSeer: 479, + PitLord1: 361, + PitLord2: 687, + PitViper1: 76, + PitViper2: 595, + PlagueBearer: 9, + PlagueBugs: 89, + PoisonSpinner: 124, + PreservedDead: 99, + ProwlingDead: 438, + QuillBear: 313, + QuillRat1: 63, + QuillRat2: 605, + RatMan1: 141, + RatMan2: 396, + BoneFetish1: 212, + RatMan4: 407, + RatManShaman: 278, + RazorBeast: 316, + RazorPitDemon: 82, + RazorSpine1: 66, + RazorSpine2: 607, + ReanimatedHorde: 437, + Returned1: 1, + Returned2: 649, + ReturnedArcher1: 171, + ReturnedArcher2: 578, + ReturnedMage: 274, + ReturnedMage1: 379, + ReturnedMage2: 383, + ReturnedMage3: 387, + ReturnedMage4: 620, + ReturnedMage5: 622, + RiverStalkerHead: 262, + RiverStalkerLimb: 259, + RockDweller: 49, + RockWorm: 69, + RockWormEgg: 191, + RockWormQueen: 285, + RockWormYoung: 181, + RotWalker: 436, + SaberCat1: 84, + SaberCat2: 628, + Salamander1: 75, + Salamander2: 596, + SandFisher: 123, + SandLeaper: 78, + SandMaggot: 68, + SandMaggotEgg: 190, + SandMaggotYoung: 180, + SandRaider1: 29, + SandRaider2: 601, + DeathBeetle: 92, + Scarab1: 93, + Scarab2: 654, + Sentry1: 411, + Sentry2: 412, + Sentry3: 415, + Sentry4: 416, + SerpentMagus1: 77, + SerpentMagus2: 598, + Sexton: 238, + Skeleton: 0, + SkeletonArcher: 170, + Slayerexp1: 454, + Slayerexp2: 682, + Slinger1: 373, + Slinger2: 610, + Slinger3: 611, + Slinger4: 612, + SnowYeti1: 446, + SnowYeti2: 447, + SnowYeti3: 448, + SnowYeti4: 449, + SoulKiller: 691, + SoulKiller1: 399, + SoulKiller2: 144, + SoulKiller3: 215, + SoulKiller4: 658, + SoulKiller5: 661, + SoulKillerShaman1: 664, + SoulKillerShaman2: 281, + SpearCat: 394, + SpearCat1: 374, + Specter1: 40, + Specter2: 633, + SpiderMagus: 126, + SpikeFiend1: 64, + SpikeFiend2: 606, + Spikefist: 130, + SpikeGiant: 314, + SteelWeevil1: 94, + SteelWeevil2: 655, + StormCaster1: 306, + StormCaster2: 693, + Strangler1: 305, + Strangler2: 692, + StygianDog: 302, + StygianDoll1: 145, + StygianDoll2: 216, + StygianDoll3: 400, + StygianDoll4: 660, + StygianDoll5: 657, + StygianDoll6: 690, + StygianDollShaman1: 663, + StygianDollShaman2: 282, + StygianFury: 476, + StygianHag: 299, + StygianHarlot: 471, + StygianWatcherHead: 263, + StygianWatcherLimb: 260, + Succubusexp1: 469, + Succubusexp2: 634, + Sucker: 114, + SuckerNest: 334, + Summoner: 250, + SwampGhost: 119, + Tainted: 11, + Tainted2: 581, + Taunt: 545, + Temptress1: 472, + Temptress2: 473, + Temptress3: 635, + Tentacle1: 562, + Tentacle2: 563, + Tentacle3: 564, + Tentacle4: 565, + Tentacle5: 566, + ThornBeast: 65, + ThornBrute: 315, + ThornedHulk1: 127, + ThornedHulk2: 609, + Thrasher: 129, + TombCreeper1: 80, + TombCreeper2: 630, + TombViper1: 73, + TombViper2: 597, + TrappedSoul1: 403, + TrappedSoul2: 404, + TreeLurker: 81, + UndeadScavenger: 111, + UnholyCorpse1: 439, + UnholyCorpse2: 698, + Unraveler1: 103, + Unraveler2: 668, + Urdar: 189, + VenomLord1: 362, + VenomLord2: 558, + VileArcher1: 161, + VileArcher2: 613, + VileHunter: 44, + VileLancer1: 166, + VileLancer2: 615, + VileTemptress: 470, + VileWitch1: 475, + VileWitch2: 638, + WailingBeast: 27, + WarpedFallen: 23, + WarpedShaman: 62, + Warrior: 417, + WaterWatcherHead: 261, + WaterWatcherLimb: 258, + WingedNightmare: 113, + Witch1: 637, + Witch2: 477, + Witch3: 478, + Wolf1: 359, + Wolf2: 420, + Wolf3: 430, + WolfRider1: 450, + WolfRider2: 451, + WolfRider3: 452, + WorldKiller1: 679, + WorldKiller2: 72, + WorldKillerEgg1: 681, + WorldKillerEgg2: 194, + WorldKillerQueen: 288, + WorldKillerYoung1: 680, + WorldKillerYoung2: 184, + Worm1: 551, + Worm2: 552, + Worm3: 553, + Worm4: 554, + Worm5: 555, + Wraith1: 39, + Wraith2: 632, + Yeti: 25, + Zakarumite: 235, + Zealot1: 237, + Zealot2: 671, + Zealot3: 672, + Zombie: 5, - SmallSparklyChest: 397, - LargeSparklyChest: 455, - SuperChest: 580, + // Bosses/Ubers + Andariel: 156, + Duriel: 211, + Mephisto: 242, + Diablo: 243, + DiabloClone: 333, + ThroneBaal: 543, + Baal: 544, + BaalClone: 570, + UberMephisto: 704, + UberBaal: 705, + UberIzual: 706, + Lilith: 707, + UberDuriel: 708, + UberDiablo: 709, - // misc - BubblingPoolofBlood: 82, - HornShrine: 83, - Stash: 267, - BluePortal: 59, - RedPortal: 60, - Smoke: 401, - }, + // Mini-Bosses + TheSmith: 402, + BloodRaven: 267, + Radament: 229, + TheSummoner: 250, + Griswold: 365, + Izual: 256, + Hephasto: 409, + KorlictheProtector: 540, + TalictheDefender: 541, + MadawctheGuardian: 542, + ListerTheTormenter: 571, + TheCowKing: 743, // 773? + ColdwormtheBurrower: 284, + Nihlathak: 526, - exits: { - preset: { - AreaEntrance: 0, // special - // act 1 - CaveHoleUp: 4, - CaveHoleLvl2: 5, - Crypt: 6, - Mausoleum: 7, - CryptMausExit: 8, - JailUpStairs: 13, - JailDownStairs: 14, - CathedralDownStairs: 15, - CathedralUpStairs: 16, - CatacombsUpStairs: 17, - CatacombsDownStairs: 18, + // Objects + Turret1: 348, + Turret2: 349, + Turret3: 350, + CatapultS: 497, + CatapultE: 498, + CatapultSiege: 499, + CatapultW: 500, + Compellingorb: 366, + GargoyleTrap: 273, + MummyGenerator: 228, + Stairs: 559, + BarricadeDoor1: 432, + BarricadeDoor2: 433, + PrisonDoor: 434, + BarricadeTower: 435, + BarricadeWall1: 524, + BarricadeWall2: 525, - // act 2 - A2SewersTrapDoor: 19, - A2EnterSewersDoor: 20, - A2ExitSewersDoor: 21, - A2UndergroundUpStairs: 22, - A2DownStairs: 23, - EnterHaremStairs: 24, - ExitHaremStairs: 25, - PreviousLevelHaremRight: 26, - PreviousLevelHaremLeft: 27, - NextLevelHaremRight: 28, - NextLevelHaremLeft: 29, - PreviousPalaceRight: 30, - PreviousPalaceLeft: 31, - NextLevelPalace: 32, - EnterStonyTomb: 33, - EnterHalls: 36, - EnterTalTomb1: 38, - EnterTalTomb2: 39, - EnterTalTomb3: 40, - EnterTalTomb4: 41, - EnterTalTomb5: 42, - EnterTalTomb6: 43, - EnterTalTomb7: 44, - PreviousAreaTomb: 45, - NextLevelTomb: 46, - EnterMaggotLair: 47, - PreviousAreaMaggotLair: 48, - NextLevelMaggotLair: 49, - AncientTunnelsTrapDoor: 50, - EntrancetoDurielsLair: 100, + // Misc? + Youngdiablo: 368, + Left: 525, + Life: 426, + Effect: 574, + Pet: 414, + Prince: 249, + POW: 534, + Right: 524, + Sage: 424, + Town: 514, + Cow: 179, + }, - // act 3 - EnterSpiderHole: 51, - ExitSpiderHole: 52, - EnterPit: 53, - EnterDungeon: 54, - PreviousAreaDungeon: 55, - NextLevelDungeon: 56, - A3EnterSewers: 57, - A3ExitSewersUpperK: 58, - A3SewersPreviousArea: 58, - A3ExitSewers: 59, - A3NextLevelSewers: 60, - EnterTemple: 61, - ExitTemple: 63, - EnterDurance: 64, - PreviousLevelDurance: 65, - NextLevelDurance: 68, - SewerStairsA3: 366, - DuranceEntryStairs: 386, + summons: { + type: { + "Valkyrie": 2, + "Golem": 3, + "Skeleton": 4, + "SkeletonMage": 5, + "Revive": 6, + "Mercenary": 7, + "Dopplezon": 8, + "Raven": 10, + "SpiritWolf": 11, + "Fenris": 12, + "DireWolf": 12, + "Totem": 13, + "Spirit": 13, + "Vine": 14, + "Grizzly": 15, + "ShadowWarrior": 16, + "Shadow": 16, + "AssassinTrap": 17, + "Hydra": 19, + }, - // act 4 - EnterRiverStairs: 69, - ExitRiverStairs: 70, - // act 5 - EnterCrystal: 71, - A5ExitCave: 73, - A5NextLevelCave: 74, - EnterSubLevelCave: 75, - EnterNithsTemple: 76, - PreviousAreaNithsTemple: 77, - NextAreaNithsTemple: 78, - ArreatEnterAncientsWay: 79, - ArreatEnterWorldstone: 80, - PreviousAreaWorldstone: 81, - NextAreaWorldstone: 82, - }, - }, - - monsters: { - preset: { - // Confirmed - TheSummoner: 250, - Bishibosh: 734, - Coldcrow: 736, - Rakanishu: 737, - TreeheadWoodFist: 738, - TheCountess: 740, - BoneAsh: 743, - Beetleburst: 747, - TheSmith: 754, - Radament: 744, - CreepingFeature: 748, - FireEye: 750, - DarkElder: 751, - Corpsefire: 774, - TheCowKing: 773, - BloodRaven: 805, - Izual: 406, - DacFarren: 782, - EyebacktheUnleashed: 784, - SharpToothSayer: 790, - // Unconfirmed - Bonebreak: 705, - Griswold: 709, - PitspawnFouldog: 711, - FlamespiketheCrawler: 712, - BloodwitchtheWild: 715, - Fangskin: 716, - Leatherarm: 718, - ColdwormtheBurrower: 719, - AncientKaatheSoulless: 723, - WebMagetheBurning: 725, - WitchDoctorEndugu: 726, - Stormtree: 727, - SarinatheBattlemaid: 728, - IcehawkRiftwing: 729, - IsmailVilehand: 730, - GelebFlamefinger: 731, - BremmSparkfist: 732, - ToorcIcefist: 733, - WyandVoidfinger: 734, - MafferDragonhand: 735, - WingedDeath: 736, - ListertheTormentor: 737, - Taintbreeder: 738, - RiftwraiththeCannibal: 739, - InfectorofSouls: 740, - LordDeSeis: 741, - GrandVizierofChaos: 742, - Hephasto: 745, - SiegeBoss: 746, - AxeDweller: 750, - BonesawBreaker: 751, - EldritchtheRectifier: 753, - ThreshSocket: 755, - Pindleskin: 756, - SnapchipShatter: 757, - AnodizedElite: 758, - VinvearMolech: 759, - MagmaTorquer: 761, - BlazeRipper: 762, - Frozenstein: 763, - Nihlathak: 764, - Wave1Spawn: 765, - Wave2Spawn: 766, - Wave3Spawn: 767, - Wave4Spawn: 768, - Wave5Spawn: 769, + mode: { + Death: 0, + Standing: 1, + Walking: 2, + GettingHit: 3, + Attacking1: 4, + Attacking2: 5, + Blocking: 6, + CastingSkill: 7, + UsingSkill1: 8, + UsingSkill2: 9, + UsingSkill3: 10, + UsingSkill4: 11, + Dead: 12, + KnockedBack: 13, + Spawning: 14, + Running: 15 + }, - // Questionable - GriefGrumble: 741, // JailLvl2 - UniqueJailLvl3: 273, - UniqueArcaneSanctuary: 371, - }, - mode: { - Death: 0, - Standing: 1, - Walking: 2, - GettingHit: 3, - Attacking1: 4, - Attacking2: 5, - Blocking: 6, - CastingSkill: 7, - UsingSkill1: 8, - UsingSkill2: 9, - UsingSkill3: 10, - UsingSkill4: 11, - Dead: 12, - KnockedBack: 13, - Spawning: 14, - Running: 15 - }, - spectype: { - All: 0, - Super: 1, - Champion: 2, - Unique: 4, - SuperUnique: 5, - Magic: 6, - Minion: 8, - }, - // todo - determine what all these correlate to - type: { - Undead: 1, - Demon: 2, - Insect: 3, - Human: 4, - Construct: 5, - LowUndead: 6, - HighUndead: 7, - Skeleton: 8, - Zombie: 9, - BigHead: 10, - FoulCrow: 11, - Fallen: 12, - Brute: 13, - SandRaider: 14, - Wraith: 15, - CorruptRogue: 16, - Baboon: 17, - GoatMan: 18, - QuillRat: 19, - SandMaggot: 20, - Viper: 21, - SandLeaper: 22, - PantherWoman: 23, - Swarm: 24, - Scarab: 25, - Mummy: 26, - Unraveler: 27, - Vulture: 28, - Mosquito: 29, - WillowWisp: 30, - Arach: 31, - ThornHulk: 32, - Vampire: 33, - BatDemon: 34, - Fetish: 35, - Blunderbore: 36, - UndeadFetish: 37, - Zakarum: 38, - FrogDemon: 39, - Tentacle: 40, - FingerMage: 41, - Golem: 42, - Vilekind: 43, - Regurgitator: 44, - DoomKnight: 45, - CouncilMember: 46, - MegaDemon: 47, - Bovine: 48, - SeigeBeast: 49, - SnowYeti: 50, - Minion: 51, - Succubus: 52, - Overseer: 53, - Imp: 54, - FrozenHorror: 55, - BloodLord: 56, - DeathMauler: 57, - PutridDefiler: 58, - }, - DiablosBoneCage: 340, - Dummy1: 149, - Dummy2: 268, - AbyssKnight: 311, - Afflicted: 10, - Afflicted2: 580, - AlbinoRoach: 95, - Ancient1: 104, - Ancient2: 669, - Ancient3: 670, - Apparition: 41, - Arach1: 122, - Arach2: 685, - Assailant: 33, - Assailant2: 603, - BaalColdMage: 381, - Balrog1: 360, - Balrog2: 686, - Banished: 135, - Barbs: 422, - Bear1: 428, - Bear2: 431, - Beast: 441, - BerserkSlayer: 462, - BlackArcher: 163, - BlackLancer1: 168, - BlackLancer2: 617, - BlackLocusts: 88, - BlackRaptor1: 17, - BlackRaptor2: 592, - BlackRogue: 46, - BlackSoul1: 121, - BlackSoul2: 640, - BlackVultureNest: 208, - BloodBoss: 482, - BloodBringer: 443, - BloodClan1: 55, - BloodClan2: 588, - BloodDiver: 139, - BloodGolem: 290, - BloodHawk1: 16, - BloodHawk2: 591, - BloodHawkNest: 207, - BloodHook: 116, - BloodHookNest: 336, - BloodLord1: 134, - BloodLord2: 695, - BloodWing: 117, - BloodWingNest: 337, - Blunderbore1: 186, - Blunderbore2: 618, - BoneArcher1: 172, - BoneArcher2: 576, - BoneMage1: 275, - BoneMage2: 380, - BoneMage3: 384, - BoneMage4: 388, - BoneMage5: 624, - BoneWarrior1: 2, - BoneWarrior2: 648, - HellBovine: 391, - BrambleHulk: 128, - Brute: 24, - Bunny: 556, - BurningDead: 3, - BurningDeadArcher1: 173, - BurningDeadArcher2: 575, - BurningDeadArcher3: 577, - BurningDeadMage1: 276, - BurningDeadMage2: 385, - BurningDeadMage3: 389, - BurningDeadMage4: 621, - BurningSoul1: 641, - BurningSoul2: 120, - Cadaver1: 100, - Cadaver2: 703, - Cantor: 239, - CarrionBird1: 110, - CarrionBird2: 608, - Carver1: 642, - Carver2: 20, - CarverShaman: 645, - CarverShaman2: 59, - CaveLeaper1: 79, - CaveLeaper2: 629, - ClawViper1: 74, - ClawViper2: 594, - CloudStalker1: 18, - CloudStalker2: 593, - CloudStalkerNest: 209, - Combatant1: 522, - Combatant2: 523, - ConsumedFireBoar: 464, - ConsumedIceBoar: 463, - CorpseSpitter: 308, - Corpulent: 307, - Creature1: 248, - Creature2: 427, - Creeper: 413, - CrushBiest: 442, - Crusher: 26, - Damned1: 14, - Damned2: 584, - DarkArcher1: 162, - DarkArcher2: 614, - DarkFamiliar: 140, - DarkHunter: 43, - DarkLancer1: 167, - DarkLancer2: 616, - DarkLord1: 133, - DarkLord2: 697, - DarkOne1: 22, - DarkOne2: 644, - DarkRanger: 160, - DarkShaman1: 61, - DarkShaman2: 647, - DarkShape: 42, - DarkSpearwoman: 165, - DarkStalker: 45, - DeamonSteed: 445, - DeathClan1: 57, - DeathClan2: 589, - Decayed: 97, - DefiledWarrior: 440, - Defiler1: 546, - Defiler2: 547, - Defiler3: 548, - Defiler4: 549, - Defiler5: 550, - DesertWing: 136, - Destruction: 410, - Devilkin: 643, - Devilkin2: 21, - DevilkinShaman: 646, - DevilkinShaman2: 60, - Devourer: 70, - DevourerEgg: 192, - DevourerQueen: 286, - DevourerYoung: 182, - Disfigured: 13, - Disfigured2: 583, - Dominus1: 474, - Dominus2: 636, - DoomApe: 51, - DoomKnight: 310, - DoomKnight1: 699, - DoomKnight2: 700, - Drehya1: 512, - Drehya2: 527, - DriedCorpse: 96, - DrownedCarcass: 8, - DuneBeast: 48, - DungSoldier: 91, - Dweller: 247, - Eagle: 429, - Embalmed: 98, - Faithful: 236, - Fallen: 19, - FallenShaman: 58, - FanaticMinion: 461, - Feeder: 115, - FeederNest: 335, - Fenris: 421, - Fetish1: 142, - BoneFetish2: 213, - Fetish3: 397, - FetishShaman: 279, - Fiend1: 137, - Fiend2: 651, - FireBoar: 456, - FireTower: 372, - FlameSpider: 125, - Flayer1: 143, - BoneFetish3: 214, - Flayer3: 398, - Flayer4: 659, - Flayer5: 656, - FlayerShaman1: 280, - FlayerShaman2: 662, - FleshArcher: 164, - FleshBeast1: 301, - FleshBeast2: 678, - FleshHunter: 47, - FleshLancer: 169, - FleshSpawner1: 298, - FleshSpawner2: 676, - FlyingScimitar: 234, - FoulCrow: 15, - FoulCrow2: 590, - FoulCrowNest: 206, - FrenziedHellSpawn: 465, - FrenziedIceSpawn: 466, - GargantuanBeast: 28, - Geglash: 200, - Ghost1: 38, - Ghost2: 631, - Ghoul: 7, - GhoulLord1: 131, - GhoulLord2: 696, - GiantLamprey: 71, - GiantLampreyEgg: 193, - GiantLampreyQueen: 287, - GiantLampreyYoung: 183, - GiantUrchin: 317, - Gloam1: 118, - Gloam2: 639, - Gloombat1: 138, - Gloombat2: 650, - Gorbelly: 187, - GoreBearer: 444, - GreaterHellSpawn1: 459, - GreaterHellSpawn2: 684, - GreaterIceSpawn: 460, - Groper: 304, - Grotesque1: 300, - Grotesque2: 675, - GrotesqueWyrm1: 303, - GrotesqueWyrm2: 677, - Guardian1: 102, - Guardian2: 667, - Hawk: 419, - Heirophant1: 240, - Heirophant2: 241, - Heirophant3: 673, - Heirophant4: 674, - HellBuzzard: 112, - HellCat: 86, - HellClan1: 56, - HellClan2: 587, - HellSlinger: 376, - HellSpawn1: 457, - HellSpawn2: 683, - HellSwarm: 90, - HellWhip: 483, - HollowOne: 101, - Horror: 4, - Horror1: 501, - Horror2: 502, - Horror3: 503, - Horror4: 504, - Horror5: 505, - HorrorArcher1: 174, - HorrorArcher2: 579, - HorrorMage1: 277, - HorrorMage2: 382, - HorrorMage3: 386, - HorrorMage4: 390, - HorrorMage5: 623, - HorrorMage6: 625, - HorrorMage7: 626, - Hs1: 560, - HungryDead: 6, - Huntress1: 83, - Huntress2: 627, - Hut: 528, - Hydra1: 351, - Hydra2: 352, - Hydra3: 353, - IceBoar: 455, - IceSpawn: 458, - Imp1: 492, - Imp2: 493, - Imp3: 494, - Imp4: 495, - Imp5: 496, - Imp6: 688, - Imp7: 689, - Infidel1: 32, - Infidel2: 600, - InsaneHellSpawn: 467, - InsaneIceSpawn: 468, - Invader1: 31, - Invader2: 602, - Itchies: 87, - JungleHunter: 50, - JungleUrchin: 67, - Larva: 283, - Lasher: 480, - LightningSpire: 371, - Lord1: 506, - Lord2: 507, - Lord3: 508, - Lord4: 509, - Lord5: 510, - Lord6: 652, - Lord7: 653, - Maggot: 227, - Malachai: 408, - Marauder: 30, - Marauder2: 599, - Master: 418, - Mauler: 188, - Mauler1: 529, - Mauler12: 604, - Mauler2: 530, - Mauler3: 531, - Mauler4: 532, - Mauler5: 533, - Mauler6: 619, - MawFiend: 694, - MawFiend2: 309, - Council1: 345, - Council2: 346, - Council3: 347, - Council4: 557, - Minion1: 572, - Minion2: 573, - Enslaved: 453, - MinionSlayerSpawner: 485, - MinionSpawner: 484, - Misshapen1: 12, - Misshapen2: 582, - MoonClan1: 53, - MoonClan2: 585, - BaalSubjectMummy: 105, - Navi: 266, - Flavie: 266, - NightClan1: 54, - NightClan2: 586, - NightLord: 132, - NightMarauder: 295, - NightSlinger1: 375, - NightSlinger2: 395, - NightTiger: 85, - OblivionKnight1: 312, - OblivionKnight2: 701, - OblivionKnight3: 702, - OverLord: 481, - OverSeer: 479, - PitLord1: 361, - PitLord2: 687, - PitViper1: 76, - PitViper2: 595, - PlagueBearer: 9, - PlagueBugs: 89, - PoisonSpinner: 124, - PreservedDead: 99, - ProwlingDead: 438, - QuillBear: 313, - QuillRat1: 63, - QuillRat2: 605, - RatMan1: 141, - RatMan2: 396, - BoneFetish1: 212, - RatMan4: 407, - RatManShaman: 278, - RazorBeast: 316, - RazorPitDemon: 82, - RazorSpine1: 66, - RazorSpine2: 607, - ReanimatedHorde: 437, - Returned1: 1, - Returned2: 649, - ReturnedArcher1: 171, - ReturnedArcher2: 578, - ReturnedMage: 274, - ReturnedMage1: 379, - ReturnedMage2: 383, - ReturnedMage3: 387, - ReturnedMage4: 620, - ReturnedMage5: 622, - RiverStalkerHead: 262, - RiverStalkerLimb: 259, - RockDweller: 49, - RockWorm: 69, - RockWormEgg: 191, - RockWormQueen: 285, - RockWormYoung: 181, - RotWalker: 436, - SaberCat1: 84, - SaberCat2: 628, - Salamander1: 75, - Salamander2: 596, - SandFisher: 123, - SandLeaper: 78, - SandMaggot: 68, - SandMaggotEgg: 190, - SandMaggotYoung: 180, - SandRaider1: 29, - SandRaider2: 601, - SandWarrior: 92, - Scarab1: 93, - Scarab2: 654, - Sentry1: 411, - Sentry2: 412, - Sentry3: 415, - Sentry4: 416, - SerpentMagus1: 77, - SerpentMagus2: 598, - Sexton: 238, - Skeleton: 0, - SkeletonArcher: 170, - Slayerexp1: 454, - Slayerexp2: 682, - Slinger1: 373, - Slinger2: 610, - Slinger3: 611, - Slinger4: 612, - SnowYeti1: 446, - SnowYeti2: 447, - SnowYeti3: 448, - SnowYeti4: 449, - SoulKiller: 691, - SoulKiller1: 399, - SoulKiller2: 144, - SoulKiller3: 215, - SoulKiller4: 658, - SoulKiller5: 661, - SoulKillerShaman1: 664, - SoulKillerShaman2: 281, - SpearCat: 394, - SpearCat1: 374, - Specter1: 40, - Specter2: 633, - SpiderMagus: 126, - SpikeFiend1: 64, - SpikeFiend2: 606, - Spikefist: 130, - SpikeGiant: 314, - SteelWeevil1: 94, - SteelWeevil2: 655, - StormCaster1: 306, - StormCaster2: 693, - Strangler1: 305, - Strangler2: 692, - StygianDog: 302, - StygianDoll1: 145, - StygianDoll2: 216, - StygianDoll3: 400, - StygianDoll4: 660, - StygianDoll5: 657, - StygianDoll6: 690, - StygianDollShaman1: 663, - StygianDollShaman2: 282, - StygianFury: 476, - StygianHag: 299, - StygianHarlot: 471, - StygianWatcherHead: 263, - StygianWatcherLimb: 260, - Succubusexp1: 469, - Succubusexp2: 634, - Sucker: 114, - SuckerNest: 334, - Summoner: 250, - SwampGhost: 119, - Tainted: 11, - Tainted2: 581, - Taunt: 545, - Temptress1: 472, - Temptress2: 473, - Temptress3: 635, - Tentacle1: 562, - Tentacle2: 563, - Tentacle3: 564, - Tentacle4: 565, - Tentacle5: 566, - ThornBeast: 65, - ThornBrute: 315, - ThornedHulk1: 127, - ThornedHulk2: 609, - Thrasher: 129, - TombCreeper1: 80, - TombCreeper2: 630, - TombViper1: 73, - TombViper2: 597, - TrappedSoul1: 403, - TrappedSoul2: 404, - TreeLurker: 81, - UndeadScavenger: 111, - UnholyCorpse1: 439, - UnholyCorpse2: 698, - Unraveler1: 103, - Unraveler2: 668, - Urdar: 189, - VenomLord1: 362, - VenomLord2: 558, - VileArcher1: 161, - VileArcher2: 613, - VileHunter: 44, - VileLancer1: 166, - VileLancer2: 615, - VileTemptress: 470, - VileWitch1: 475, - VileWitch2: 638, - WailingBeast: 27, - WarpedFallen: 23, - WarpedShaman: 62, - Warrior: 417, - WaterWatcherHead: 261, - WaterWatcherLimb: 258, - WingedNightmare: 113, - Witch1: 637, - Witch2: 477, - Witch3: 478, - Wolf1: 359, - Wolf2: 420, - Wolf3: 430, - WolfRider1: 450, - WolfRider2: 451, - WolfRider3: 452, - WorldKiller1: 679, - WorldKiller2: 72, - WorldKillerEgg1: 681, - WorldKillerEgg2: 194, - WorldKillerQueen: 288, - WorldKillerYoung1: 680, - WorldKillerYoung2: 184, - Worm1: 551, - Worm2: 552, - Worm3: 553, - Worm4: 554, - Worm5: 555, - Wraith1: 39, - Wraith2: 632, - Yeti: 25, - Zakarumite: 235, - Zealot1: 237, - Zealot2: 671, - Zealot3: 672, - Zombie: 5, + ClayGolem: 289, + Dopplezon: 356, + Valkyrie: 357, + FireGolem: 292, + IronGolem: 291, + NecroMage: 364, + NecroSkeleton: 363, + Poppy: 425, + Wolverine: 423, + }, - // Bosses/Ubers - Andariel: 156, - Duriel: 211, - Mephisto: 242, - Diablo: 243, - DiabloClone: 333, - ThroneBaal: 543, - Baal: 544, - BaalClone: 570, - UberMephisto: 704, - UberBaal: 705, - UberIzual: 706, - Lilith: 707, - UberDuriel: 708, - UberDiablo: 709, + mercs: { + mode: { + Death: 0, + Standing: 1, + Walking: 2, + GettingHit: 3, + Attacking1: 4, + Attacking2: 5, + Blocking: 6, + CastingSkill: 7, + UsingSkill1: 8, + UsingSkill2: 9, + UsingSkill3: 10, + UsingSkill4: 11, + Dead: 12, + KnockedBack: 13, + Spawning: 14, + Running: 15 + }, - // Mini-Bosses - TheSmith: 402, - BloodRaven: 267, - Radament: 229, - TheSummoner: 250, - Griswold: 365, - Izual: 256, - Hephasto: 409, - KorlictheProtector: 540, - TalictheDefender: 541, - MadawctheGuardian: 542, - ListerTheTormenter: 571, - TheCowKing: 743, - ColdwormtheBurrower: 284, - Nihlathak: 526, + Rogue: 271, + Guard: 338, + IronWolf: 359, + A5Barb: 561, + }, - // Objects - Turret1: 348, - Turret2: 349, - Turret3: 350, - CatapultS: 497, - CatapultE: 498, - CatapultSiege: 499, - CatapultW: 500, - Compellingorb: 366, - GargoyleTrap: 273, - MummyGenerator: 228, - Stairs: 559, - BarricadeDoor1: 432, - BarricadeDoor2: 433, - PrisonDoor: 434, - BarricadeTower: 435, - BarricadeWall1: 524, - BarricadeWall2: 525, + missiles: { + DiabloLightning: 172, + FissureCrack1: 462, + FissureCrack2: 463, + }, - // Misc? - Youngdiablo: 368, - Left: 525, - Life: 426, - Effect: 574, - Pet: 414, - Prince: 249, - POW: 534, - Right: 524, - Sage: 424, - Town: 514, - Cow: 179, - }, + storage: { + Equipped: 1, + Belt: 2, + Inventory: 3, + TradeWindow: 5, + Cube: 6, + Stash: 7, + }, - summons: { - type: { - "Valkyrie": 2, - "Golem": 3, - "Skeleton": 4, - "SkeletonMage": 5, - "Revive": 6, - "Mercenary": 7, - "Dopplezon": 8, - "Raven": 10, - "SpiritWolf": 11, - "Fenris": 12, - "DireWolf": 12, - "Totem": 13, - "Spirit": 13, - "Vine": 14, - "Grizzly": 15, - "ShadowWarrior": 16, - "Shadow": 16, - "AssassinTrap": 17, - "Hydra": 19, - }, + node: { + NotOnPlayer: 0, + Storage: 1, + Belt: 2, + Equipped: 3, + Cursor: 4, + }, - mode: { - Death: 0, - Standing: 1, - Walking: 2, - GettingHit: 3, - Attacking1: 4, - Attacking2: 5, - Blocking: 6, - CastingSkill: 7, - UsingSkill1: 8, - UsingSkill2: 9, - UsingSkill3: 10, - UsingSkill4: 11, - Dead: 12, - KnockedBack: 13, - Spawning: 14, - Running: 15 - }, + // Same apply's for merc with less things available + body: { + None: 0, + Head: 1, + Neck: 2, + Torso: 3, + Armor: 3, + RightArm: 4, + LeftArm: 5, + RingRight: 6, + RingLeft: 7, + Belt: 8, + Feet: 9, + Gloves: 10, + RightArmSecondary: 11, + LeftArmSecondary: 12 + }, - ClayGolem: 289, - Dopplezon: 356, - Valkyrie: 357, - FireGolem: 292, - IronGolem: 291, - NecroMage: 364, - NecroSkeleton: 363, - Poppy: 425, - Wolverine: 423, - }, + items: { + cost: { + ToBuy: 0, + ToSell: 1, + ToRepair: 2, + }, + flags: { + Equipped: 0x00000001, + InSocket: 0x00000008, + Identified: 0x00000010, + OnActiveWeaponSlot: 0x00000040, + OnSwapWeaponSlot: 0x00000080, + Broken: 0x00000100, + FullRejuv: 0x00000400, + Socketed: 0x00000800, + InTradeGamble: 0x00002000, + NotInSocket: 0x00004000, + Ear: 0x00010000, + StartingItem: 0x00020000, + RuneQuestPotion: 0x00200000, + Ethereal: 0x00400000, + IsAnItem: 0x00800000, + Personalized: 0x01000000, + Runeword: 0x04000000, + }, + mode: { + inStorage: 0, //Item inven stash cube store = Item inven stash cube store + Equipped: 1, // Item equipped self or merc + inBelt: 2, // Item in belt + onGround: 3, // Item on ground + onCursor: 4, // Item on cursor + Dropping: 5, // Item being dropped + Socketed: 6 // Item socketed in item + }, + quality: { + LowQuality: 1, + Normal: 2, + Superior: 3, + Magic: 4, + Set: 5, + Rare: 6, + Unique: 7, + Crafted: 8, + }, + class: { + Normal: 0, + Exceptional: 1, + Elite: 2, + }, + type: { + Shield: 2, + Armor: 3, + Gold: 4, + BowQuiver: 5, + CrossbowQuiver: 6, + PlayerBodyPart: 7, + Herb: 8, + Potion: 9, + Ring: 10, + Elixir: 11, + Amulet: 12, + Charm: 13, + notused0: 14, + Boots: 15, + Gloves: 16, + notused1: 17, + Book: 18, + Belt: 19, + Gem: 20, + Torch: 21, + Scroll: 22, + notused2: 23, + Scepter: 24, + Wand: 25, + Staff: 26, + Bow: 27, + Axe: 28, + Club: 29, + Sword: 30, + Hammer: 31, + Knife: 32, + Spear: 33, + Polearm: 34, + Crossbow: 35, + Mace: 36, + Helm: 37, + MissilePotion: 38, + Quest: 39, + Bodypart: 40, + Key: 41, + ThrowingKnife: 42, + ThrowingAxe: 43, + Javelin: 44, + Weapon: 45, + MeleeWeapon: 46, + MissileWeapon: 47, + ThrownWeapon: 48, + ComboWeapon: 49, + AnyArmor: 50, + AnyShield: 51, + Miscellaneous: 52, + SocketFiller: 53, + Secondhand: 54, + StavesandRods: 55, + Missile: 56, + Blunt: 57, + Jewel: 58, + ClassSpecific: 59, + AmazonItem: 60, + BarbarianItem: 61, + NecromancerItem: 62, + PaladinItem: 63, + SorceressItem: 64, + AssassinItem: 65, + DruidItem: 66, + HandtoHand: 67, + Orb: 68, + VoodooHeads: 69, + AuricShields: 70, + PrimalHelm: 71, + Pelt: 72, + Cloak: 73, + Rune: 74, + Circlet: 75, + HealingPotion: 76, + ManaPotion: 77, + RejuvPotion: 78, + StaminaPotion: 79, + AntidotePotion: 80, + ThawingPotion: 81, + SmallCharm: 82, + LargeCharm: 83, + GrandCharm: 84, + AmazonBow: 85, + AmazonSpear: 86, + AmazonJavelin: 87, + AssassinClaw: 88, + MagicBowQuiv: 89, + MagicxBowQuiv: 90, + ChippedGem: 91, + FlawedGem: 92, + StandardGem: 93, + FlawlessGem: 94, + PerfectgGem: 95, + Amethyst: 96, + Diamond: 97, + Emerald: 98, + Ruby: 99, + Sapphire: 100, + Topaz: 101, + Skull: 102, + }, + + // Weapons + "HandAxe": 0, + "Axe": 1, + "DoubleAxe": 2, + "MilitaryPick": 3, + "WarAxe": 4, + "LargeAxe": 5, + "BroadAxe": 6, + "BattleAxe": 7, + "GreatAxe": 8, + "GiantAxe": 9, + "Wand": 10, + "YewWand": 11, + "BoneWand": 12, + "GrimWand": 13, + "Club": 14, + "Scepter": 15, + "GrandScepter": 16, + "WarScepter": 17, + "SpikedClub": 18, + "Mace": 19, + "MorningStar": 20, + "Flail": 21, + "WarHammer": 22, + "Maul": 23, + "GreatMaul": 24, + "ShortSword": 25, + "Scimitar": 26, + "Sabre": 27, + "Falchion": 28, + "CrystalSword": 29, + "BroadSword": 30, + "LongSword": 31, + "WarSword": 32, + "Two_HandedSword": 33, + "Claymore": 34, + "GiantSword": 35, + "BastardSword": 36, + "Flamberge": 37, + "GreatSword": 38, + "Dagger": 39, + "Dirk": 40, + "Kris": 41, + "Blade": 42, + "ThrowingKnife": 43, + "ThrowingAxe": 44, + "BalancedKnife": 45, + "BalancedAxe": 46, + "Javelin": 47, + "Pilum": 48, + "ShortSpear": 49, + "Glaive": 50, + "ThrowingSpear": 51, + "Spear": 52, + "Trident": 53, + "Brandistock": 54, + "Spetum": 55, + "Pike": 56, + "Bardiche": 57, + "Voulge": 58, + "Scythe": 59, + "Poleaxe": 60, + "Halberd": 61, + "WarScythe": 62, + "ShortStaff": 63, + "LongStaff": 64, + "GnarledStaff": 65, + "BattleStaff": 66, + "WarStaff": 67, + "ShortBow": 68, + "HuntersBow": 69, + "LongBow": 70, + "CompositeBow": 71, + "ShortBattleBow": 72, + "LongBattleBow": 73, + "ShortWarBow": 74, + "LongWarBow": 75, + "LightCrossbow": 76, + "Crossbow": 77, + "HeavyCrossbow": 78, + "RepeatingCrossbow": 79, + "Hatchet": 93, + "Cleaver": 94, + "TwinAxe": 95, + "Crowbill": 96, + "Naga": 97, + "MilitaryAxe": 98, + "BeardedAxe": 99, + "Tabar": 100, + "GothicAxe": 101, + "AncientAxe": 102, + "BurntWand": 103, + "PetrifiedWand": 104, + "TombWand": 105, + "GraveWand": 106, + "Cudgel": 107, + "RuneScepter": 108, + "HolyWaterSprinkler": 109, + "DivineScepter": 110, + "BarbedClub": 111, + "FlangedMace": 112, + "JaggedStar": 113, + "Knout": 114, + "BattleHammer": 115, + "WarClub": 116, + "MarteldeFer": 117, + "Gladius": 118, + "Cutlass": 119, + "Shamshir": 120, + "Tulwar": 121, + "DimensionalBlade": 122, + "BattleSword": 123, + "RuneSword": 124, + "AncientSword": 125, + "Espandon": 126, + "DacianFalx": 127, + "TuskSword": 128, + "GothicSword": 129, + "Zweihander": 130, + "ExecutionerSword": 131, + "Poignard": 132, + "Rondel": 133, + "Cinquedeas": 134, + "Stiletto": 135, + "BattleDart": 136, + "Francisca": 137, + "WarDart": 138, + "Hurlbat": 139, + "WarJavelin": 140, + "GreatPilum": 141, + "Simbilan": 142, + "Spiculum": 143, + "Harpoon": 144, + "WarSpear": 145, + "Fuscina": 146, + "WarFork": 147, + "Yari": 148, + "Lance": 149, + "LochaberAxe": 150, + "Bill": 151, + "BattleScythe": 152, + "Partizan": 153, + "Bec_de_Corbin": 154, + "GrimScythe": 155, + "JoStaff": 156, + "Quarterstaff": 157, + "CedarStaff": 158, + "GothicStaff": 159, + "RuneStaff": 160, + "EdgeBow": 161, + "RazorBow": 162, + "CedarBow": 163, + "DoubleBow": 164, + "ShortSiegeBow": 165, + "LargeSiegeBow": 166, + "RuneBow": 167, + "GothicBow": 168, + "Arbalest": 169, + "SiegeCrossbow": 170, + "Ballista": 171, + "Chu_Ko_Nu": 172, + "Katar": 175, + "WristBlade": 176, + "HatchetHands": 177, + "Cestus": 178, + "Claws": 179, + "BladeTalons": 180, + "ScissorsKatar": 181, + "Quhab": 182, + "WristSpike": 183, + "Fascia": 184, + "HandScythe": 185, + "GreaterClaws": 186, + "GreaterTalons": 187, + "ScissorsQuhab": 188, + "Suwayyah": 189, + "WristSword": 190, + "WarFist": 191, + "BattleCestus": 192, + "FeralClaws": 193, + "RunicTalons": 194, + "ScissorsSuwayyah": 195, + "Tomahawk": 196, + "SmallCrescent": 197, + "EttinAxe": 198, + "WarSpike": 199, + "BerserkerAxe": 200, + "FeralAxe": 201, + "Silver_edgedAxe": 202, + "Decapitator": 203, + "ChampionAxe": 204, + "GloriousAxe": 205, + "PolishedWand": 206, + "GhostWand": 207, + "LichWand": 208, + "UnearthedWand": 209, + "Truncheon": 210, + "MightyScepter": 211, + "SeraphRod": 212, + "Caduceus": 213, + "TyrantClub": 214, + "ReinforcedMace": 215, + "DevilStar": 216, + "Scourge": 217, + "LegendaryMallet": 218, + "OgreMaul": 219, + "ThunderMaul": 220, + "Falcata": 221, + "Ataghan": 222, + "ElegantBlade": 223, + "HydraEdge": 224, + "PhaseBlade": 225, + "ConquestSword": 226, + "CrypticSword": 227, + "MythicalSword": 228, + "LegendSword": 229, + "HighlandBlade": 230, + "BalrogBlade": 231, + "ChampionSword": 232, + "ColossusSword": 233, + "ColossusBlade": 234, + "BoneKnife": 235, + "MithrilPoint": 236, + "FangedKnife": 237, + "LegendSpike": 238, + "FlyingKnife": 239, + "FlyingAxe": 240, + "WingedKnife": 241, + "WingedAxe": 242, + "HyperionJavelin": 243, + "StygianPilum": 244, + "BalrogSpear": 245, + "GhostGlaive": 246, + "WingedHarpoon": 247, + "HyperionSpear": 248, + "StygianPike": 249, + "Mancatcher": 250, + "GhostSpear": 251, + "WarPike": 252, + "OgreAxe": 253, + "ColossusVoulge": 254, + "Thresher": 255, + "CrypticAxe": 256, + "GreatPoleaxe": 257, + "GiantThresher": 258, + "WalkingStick": 259, + "Stalagmite": 260, + "ElderStaff": 261, + "Shillelagh": 262, + "ArchonStaff": 263, + "SpiderBow": 264, + "BladeBow": 265, + "ShadowBow": 266, + "GreatBow": 267, + "DiamondBow": 268, + "CrusaderBow": 269, + "WardBow": 270, + "HydraBow": 271, + "PelletBow": 272, + "GorgonCrossbow": 273, + "ColossusCrossbow": 274, + "DemonCrossbow": 275, + "EagleOrb": 276, + "SacredGlobe": 277, + "SmokedSphere": 278, + "ClaspedOrb": 279, + "JaredsStone": 280, + "StagBow": 281, + "ReflexBow": 282, + "MaidenSpear": 283, + "MaidenPike": 284, + "MaidenJavelin": 285, + "GlowingOrb": 286, + "CrystallineGlobe": 287, + "CloudySphere": 288, + "SparklingBall": 289, + "SwirlingCrystal": 290, + "AshwoodBow": 291, + "CeremonialBow": 292, + "CeremonialSpear": 293, + "CeremonialPike": 294, + "CeremonialJavelin": 295, + "HeavenlyStone": 296, + "EldritchOrb": 297, + "DemonHeart": 298, + "VortexOrb": 299, + "DimensionalShard": 300, + "MatriarchalBow": 301, + "GrandMatronBow": 302, + "MatriarchalSpear": 303, + "MatriarchalPike": 304, + "MatriarchalJavelin": 305, + "Cap": 306, + "SkullCap": 307, + "Helm": 308, + "FullHelm": 309, + "GreatHelm": 310, + "Crown": 311, + "Mask": 312, + "QuiltedArmor": 313, + "LeatherArmor": 314, + "HardLeatherArmor": 315, + "StuddedLeather": 316, + "RingMail": 317, + "ScaleMail": 318, + "ChainMail": 319, + "BreastPlate": 320, + "SplintMail": 321, + "PlateMail": 322, + "FieldPlate": 323, + "GothicPlate": 324, + "FullPlateMail": 325, + "AncientArmor": 326, + "LightPlate": 327, + "Buckler": 328, + "SmallShield": 329, + "LargeShield": 330, + "KiteShield": 331, + "TowerShield": 332, + "GothicShield": 333, + "LeatherGloves": 334, + "HeavyGloves": 335, + "ChainGloves": 336, + "LightGauntlets": 337, + "Gauntlets": 338, + "Boots": 339, + "HeavyBoots": 340, + "ChainBoots": 341, + "LightPlatedBoots": 342, + "Greaves": 343, + "Sash": 344, + "LightBelt": 345, + "Belt": 346, + "HeavyBelt": 347, + "PlatedBelt": 348, + "BoneHelm": 349, + "BoneShield": 350, + "SpikedShield": 351, + "WarHat": 352, + "Sallet": 353, + "Casque": 354, + "Basinet": 355, + "WingedHelm": 356, + "GrandCrown": 357, + "DeathMask": 358, + "GhostArmor": 359, + "SerpentskinArmor": 360, + "DemonhideArmor": 361, + "TrellisedArmor": 362, + "LinkedMail": 363, + "TigulatedMail": 364, + "MeshArmor": 365, + "Cuirass": 366, + "RussetArmor": 367, + "TemplarCoat": 368, + "SharktoothArmor": 369, + "EmbossedPlate": 370, + "ChaosArmor": 371, + "OrnatePlate": 372, + "MagePlate": 373, + "Defender": 374, + "RoundShield": 375, + "Scutum": 376, + "DragonShield": 377, + "Pavise": 378, + "AncientShield": 379, + "DemonhideGloves": 380, + "SharkskinGloves": 381, + "HeavyBracers": 382, + "BattleGauntlets": 383, + "WarGauntlets": 384, + "DemonhideBoots": 385, + "SharkskinBoots": 386, + "MeshBoots": 387, + "BattleBoots": 388, + "WarBoots": 389, + "DemonhideSash": 390, + "SharkskinBelt": 391, + "MeshBelt": 392, + "BattleBelt": 393, + "WarBelt": 394, + "GrimHelm": 395, + "GrimShield": 396, + "BarbedShield": 397, + "WolfHead": 398, + "HawkHelm": 399, + "Antlers": 400, + "FalconMask": 401, + "SpiritMask": 402, + "JawboneCap": 403, + "FangedHelm": 404, + "HornedHelm": 405, + "AssaultHelmet": 406, + "AvengerGuard": 407, + "Targe": 408, + "Rondache": 409, + "HeraldicShield": 410, + "AerinShield": 411, + "CrownShield": 412, + "PreservedHead": 413, + "ZombieHead": 414, + "UnravellerHead": 415, + "GargoyleHead": 416, + "DemonHead": 417, + "Circlet": 418, + "Coronet": 419, + "Tiara": 420, + "Diadem": 421, + "Shako": 422, + "Hydraskull": 423, + "Armet": 424, + "GiantConch": 425, + "SpiredHelm": 426, + "Corona": 427, + "Demonhead": 428, + "DuskShroud": 429, + "Wyrmhide": 430, + "ScarabHusk": 431, + "WireFleece": 432, + "DiamondMail": 433, + "LoricatedMail": 434, + "Boneweave": 435, + "GreatHauberk": 436, + "BalrogSkin": 437, + "HellforgePlate": 438, + "KrakenShell": 439, + "LacqueredPlate": 440, + "ShadowPlate": 441, + "SacredArmor": 442, + "ArchonPlate": 443, + "Heater": 444, + "Luna": 445, + "Hyperion": 446, + "Monarch": 447, + "Aegis": 448, + "Ward": 449, + "BrambleMitts": 450, + "VampireboneGloves": 451, + "Vambraces": 452, + "CrusaderGauntlets": 453, + "OgreGauntlets": 454, + "WyrmhideBoots": 455, + "ScarabshellBoots": 456, + "BoneweaveBoots": 457, + "MirroredBoots": 458, + "MyrmidonGreaves": 459, + "SpiderwebSash": 460, + "VampirefangBelt": 461, + "MithrilCoil": 462, + "TrollBelt": 463, + "ColossusGirdle": 464, + "BoneVisage": 465, + "TrollNest": 466, + "BladeBarrier": 467, + "AlphaHelm": 468, + "GriffonHeaddress": 469, + "HuntersGuise": 470, + "SacredFeathers": 471, + "TotemicMask": 472, + "JawboneVisor": 473, + "LionHelm": 474, + "RageMask": 475, + "SavageHelmet": 476, + "SlayerGuard": 477, + "AkaranTarge": 478, + "AkaranRondache": 479, + "ProtectorShield": 480, + "GildedShield": 481, + "RoyalShield": 482, + "MummifiedTrophy": 483, + "FetishTrophy": 484, + "SextonTrophy": 485, + "CantorTrophy": 486, + "HierophantTrophy": 487, + "BloodSpirit": 488, + "SunSpirit": 489, + "EarthSpirit": 490, + "SkySpirit": 491, + "DreamSpirit": 492, + "CarnageHelm": 493, + "FuryVisor": 494, + "DestroyerHelm": 495, + "ConquerorCrown": 496, + "GuardianCrown": 497, + "SacredTarge": 498, + "SacredRondache": 499, + "KurastShield": 500, + "ZakarumShield": 501, + "VortexShield": 502, + "MinionSkull": 503, + "HellspawnSkull": 504, + "OverseerSkull": 505, + "SuccubusSkull": 506, + "BloodlordSkull": 507, + "Amulet": 520, + "Ring": 522, + "Arrows": 526, + "Bolts": 528, + "Jewel": 643, - mercs: { - mode: { - Death: 0, - Standing: 1, - Walking: 2, - GettingHit: 3, - Attacking1: 4, - Attacking2: 5, - Blocking: 6, - CastingSkill: 7, - UsingSkill1: 8, - UsingSkill2: 9, - UsingSkill3: 10, - UsingSkill4: 11, - Dead: 12, - KnockedBack: 13, - Spawning: 14, - Running: 15 - }, + // Misc? + "Elixir": 508, + "Torch": 527, + "Heart": 531, + "Brain": 532, + "Jawbone": 533, + "Eye": 534, + "Horn": 535, + "Tail": 536, + "Flag": 537, + "Fang": 538, + "Quill": 539, + "Soul": 540, + "Scalp": 541, + "Spleen": 542, + "Key": 543, + "Ear": 556, + "Herb": 602, + "anevilforce": 609, + // Potions, tomes/scrolls, gold + "TomeofTownPortal": 518, + "TomeofIdentify": 519, + "ScrollofTownPortal": 529, + "ScrollofIdentify": 530, + "RancidGasPotion": 80, + "OilPotion": 81, + "ChokingGasPotion": 82, + "ExplodingPotion": 83, + "StranglingGasPotion": 84, + "FulminatingPotion": 85, + "StaminaPotion": 513, + "AntidotePotion": 514, + "RejuvenationPotion": 515, + "FullRejuvenationPotion": 516, + "ThawingPotion": 517, + "MinorHealingPotion": 587, + "LightHealingPotion": 588, + "HealingPotion": 589, + "GreaterHealingPotion": 590, + "SuperHealingPotion": 591, + "MinorManaPotion": 592, + "LightManaPotion": 593, + "ManaPotion": 594, + "GreaterManaPotion": 595, + "SuperManaPotion": 596, + "Gold": 523, + // Charms + "SmallCharm": 603, + "LargeCharm": 604, + "GrandCharm": 605, - Rogue: 271, - Guard: 338, - IronWolf: 359, - A5Barb: 561, - }, + quest: { + // Act 1 + WirtsLeg: 88, + HoradricMalus: 89, + ScrollofInifuss: 524, + KeytotheCairnStones: 525, + // Act 2 + FinishedStaff: 91, + "HoradricStaff": 91, + IncompleteStaff: 92, + "ShaftoftheHoradricStaff": 92, + ViperAmulet: 521, + "TopoftheHoradricStaff": 521, + Cube: 549, + BookofSkill: 552, + // Act 3 + "DecoyGidbinn": 86, + "TheGidbinn": 87, + KhalimsFlail: 173, + KhalimsWill: 174, + PotofLife: 545, + "AJadeFigurine": 546, + JadeFigurine: 546, + TheGoldenBird: 547, + LamEsensTome: 548, + KhalimsEye: 553, + KhalimsHeart: 554, + KhalimsBrain: 555, + // Act 4 + HellForgeHammer: 90, + Soulstone: 551, + "MephistosSoulstone": 551, + // Act 5 + MalahsPotion: 644, + "ScrollofKnowledge": 645, + ScrollofResistance: 646, + // Pandemonium Event + KeyofTerror: 647, + KeyofHate: 648, + KeyofDestruction: 649, + DiablosHorn: 650, + BaalsEye: 651, + MephistosBrain: 652, + StandardofHeroes: 658, + // Essences/Token + TokenofAbsolution: 653, + TwistedEssenceofSuffering: 654, + ChargedEssenceofHatred: 655, + BurningEssenceofTerror: 656, + FesteringEssenceofDestruction: 657, + // Misc + "TheBlackTowerKey": 544, + }, + runes: { + El: 610, + Eld: 611, + Tir: 612, + Nef: 613, + Eth: 614, + Ith: 615, + Tal: 616, + Ral: 617, + Ort: 618, + Thul: 619, + Amn: 620, + Sol: 621, + Shael: 622, + Dol: 623, + Hel: 624, + Io: 625, + Lum: 626, + Ko: 627, + Fal: 628, + Lem: 629, + Pul: 630, + Um: 631, + Mal: 632, + Ist: 633, + Gul: 634, + Vex: 635, + Ohm: 636, + Lo: 637, + Sur: 638, + Ber: 639, + Jah: 640, + Cham: 641, + Zod: 642, + }, + gems: { + Perfect: { + Amethyst: 561, + Topaz: 566, + Sapphire: 571, + Emerald: 576, + Ruby: 581, + Diamond: 586, + Skull: 601, + }, + Flawless: { + Amethyst: 560, + Topaz: 565, + Sapphire: 570, + Emerald: 575, + Ruby: 580, + Diamond: 585, + Skull: 600, + }, + Normal: { + Amethyst: 559, + Topaz: 564, + Sapphire: 569, + Emerald: 574, + Ruby: 579, + Diamond: 584, + Skull: 599, + }, + Flawed: { + Amethyst: 558, + Topaz: 563, + Sapphire: 568, + Emerald: 573, + Ruby: 578, + Diamond: 583, + Skull: 598, + }, + Chipped: { + Amethyst: 557, + Topaz: 562, + Sapphire: 567, + Emerald: 572, + Ruby: 577, + Diamond: 582, + Skull: 597, + }, + }, + }, - storage: { - Equipped: 1, - Belt: 2, - Inventory: 3, - TradeWindow: 5, - Cube: 6, - Stash: 7, - }, + // locale strings + locale: { + monsters: { + // bosses + Andariel: 3021, + Duriel: 3054, + Mephisto: 3062, + Diablo: 3060, + Baal: 3061, + // Mini bosses + BloodRaven: 3111, + TreeheadWoodFist: 2873, + TheCountess: 2875, + TheSmith: 2889, + Radament: 2879, + TheSummoner: 3099, + HephastoTheArmorer: 1067, + Izual: 1014, + ShenktheOverseer: 22435, + // Uniques + Corpsefire: 3319, + TheCowKing: 2850, + GrandVizierofChaos: 2851, + LordDeSeis: 2852, + InfectorofSouls: 2853, + RiftwraiththeCannibal: 2854, + Taintbreeder: 2855, + TheTormentor: 2856, + Darkwing: 2857, + MafferDragonhand: 2858, + WyandVoidbringer: 2859, + ToorcIcefist: 2860, + BremmSparkfist: 2861, + GelebFlamefinger: 2862, + IsmailVilehand: 2863, + IcehawkRiftwing: 2864, + BattlemaidSarina: 2865, + Stormtree: 2866, + WitchDoctorEndugu: 2867, + SszarkTheBurning: 2868, + Bishibosh: 2869, + Bonebreaker: 2870, + Coldcrow: 2871, + Rakanishu: 2872, + Griswold: 2874, + PitspawnFouldog: 2876, + FlamespiketheCrawler: 2877, + BoneAsh: 2878, + BloodwitchtheWild: 2880, + Fangskin: 2881, + Beetleburst: 2882, + CreepingFeature: 2883, + ColdwormtheBurrower: 2884, + FireEye: 2885, + DarkElder: 2886, + AncientKaatheSoulless: 2888, + SharpToothSayer: 22493, + SnapchipShatter: 22496, + Pindleskin: 22497, + ThreshSocket: 22498, + EyebacktheUnleashed: 22499, + EldritchtheRectifier: 22500, + DacFarren: 22501, + BonesawBreaker: 22502, + Frozenstein: 22504, + Rogue: 2897, + StygianDoll: 2898, + SoulKiller: 2899, + Flayer: 2900, + Fetish: 2901, + RatMan: 2902, + UndeadStygianDoll: 2903, + UndeadSoulKiller: 2904, + UndeadFlayer: 2905, + UndeadFetish: 2906, + UndeadRatMan: 2907, + DarkFamiliar: 2908, + BloodDiver: 2909, + Gloombat: 2910, + DesertWing: 2911, + TheBanished: 2912, + BloodLord: 2913, + DarkLord: 2914, + NightLord: 2915, + GhoulLord: 2916, + Spikefist: 2917, + Thrasher: 2918, + BrambleHulk: 2919, + ThornedHulk: 2920, + SpiderMagus: 2921, + FlameSpider: 2922, + PoisonSpinner: 2923, + SandFisher: 2924, + Arach: 2925, + BloodWing: 2926, + BloodHook: 2927, + Feeder: 2928, + Sucker: 2929, + WingedNightmare: 2930, + HellBuzzard: 2931, + UndeadScavenger: 2932, + CarrionBird: 2933, + Unraveler: 2934, + Guardian: 2935, + HollowOne: 2936, + HoradrimAncient: 2937, + BoneScarab: 2938, + SteelScarab: 2939, + Scarab: 2940, + DeathBeetle: 2941, + DungSoldier: 2942, + HellSwarm: 2943, + PlagueBugs: 2944, + BlackLocusts: 2945, + Itchies: 2946, + HellCat: 2947, + NightTiger: 2948, + SaberCat: 2949, + Huntress: 2950, + CliffLurker: 2951, + TreeLurker: 2952, + CaveLeaper: 2953, + TombCreeper: 2954, + SandLeaper: 2955, + TombViper: 2956, + PitViper: 2957, + Salamander: 2958, + ClawViper: 2959, + SerpentMagus: 2960, + BloodMaggot: 2961, + GiantLamprey: 2962, + Devourer: 2963, + RockWorm: 2964, + SandMaggot: 2965, + BushBarb: 2966, + RazorSpine: 2967, + ThornBeast: 2968, + SpikeFiend: 2969, + QuillRat: 2970, + HellClan: 2971, + MoonClan: 2972, + NightClan: 2973, + DeathClan: 2974, + BloodClan: 2975, + TempleGuard: 2976, + DoomApe: 2977, + JungleHunter: 2978, + RockDweller: 2979, + DuneBeast: 2980, + FleshHunter: 2981, + BlackRogue: 2982, + DarkStalker: 2983, + VileHunter: 2984, + DarkHunter: 2985, + DarkShape: 2986, + Apparition: 2987, + Specter: 2988, + Wraith: 2989, + Ghost: 2990, + Assailant: 2991, + Infidel: 2992, + Invader: 2993, + Marauder: 2994, + SandRaider: 2995, + GargantuanBeast: 2996, + WailingBeast: 2997, + Yeti: 2998, + Crusher: 2999, + Brute: 3000, + CloudStalker: 3001, + BlackVulture: 3002, + BlackRaptor: 3003, + BloodHawk: 3004, + FoulCrow: 3005, + PlagueBearer: 3006, + Ghoul: 3007, + DrownedCarcass: 3008, + HungryDead: 3009, + Zombie: 3010, + Horror: 3012, + Returned: 3013, + BurningDead: 3014, + BoneWarrior: 3015, + Damned: 3016, + Disfigured: 3017, + Misshapen: 3018, + Tainted: 3019, + Afflicted: 3020, + Camel: 3033, + Cadaver: 3034, + PreservedDead: 3035, + Embalmed: 3036, + DriedCorpse: 3037, + Decayed: 3038, + Urdar: 3039, + Mauler: 3040, + Gorebelly: 3041, + Blunderbore: 3042, + BloodMaggotYoung: 3043, + GiantLampreyYoung: 3044, + DevourerYoung: 3045, + RockWormYoung: 3046, + SandMaggotYoung: 3047, + BloodMaggotEgg: 3048, + GiantLampreyEgg: 3049, + DevourerEgg: 3050, + RockWormEgg: 3051, + SandMaggotEgg: 3052, + Maggot: 3053, + BloodHawkNest: 3055, + FlyingScimitar: 3056, + CloudStalkerNest: 3057, + BlackRaptorNest: 3058, + FoulCrowNest: 3059, + Cantor: 3063, + Heirophant: 3064, + Sexton: 3065, + Zealot: 3066, + Faithful: 3067, + Zakarumite: 3068, + BlackSoul: 3069, + BurningSoul: 3070, + SwampGhost: 3071, + Gloam: 3072, + WarpedShaman: 3073, + DarkShaman: 3074, + DevilkinShaman: 3075, + CarverShaman: 3076, + FallenShaman: 3077, + WarpedOne: 3078, + DarkOne: 3079, + Devilkin: 3080, + Carver: 3081, + Fallen: 3082, + ReturnedArcher: 3083, + HorrorArcher: 3084, + BurningDeadArcher: 3085, + BoneArcher: 3086, + CorpseArcher: 3087, + SkeletonArcher: 3088, + FleshLancer: 3089, + BlackLancer: 3090, + DarkLancer: 3091, + VileLancer: 3092, + DarkSpearwoman: 3093, + FleshArcher: 3094, + BlackArcher: 3095, + DarkRanger: 3096, + VileArcher: 3097, + DarkArcher: 3098, + StygianDollShaman: 3100, + SoulKillerShaman: 3101, + FlayerShaman: 3102, + FetishShaman: 3103, + RatManShaman: 3104, + HorrorMage: 3105, + BurningDeadMage: 3106, + BoneMage: 3107, + CorpseMage: 3108, + ReturnedMage: 3109, + GargoyleTrap: 3110, + NightMarauder: 3121, + FireGolem: 3122, + IronGolem: 3123, + BloodGolem: 3124, + ClayGolem: 3125, + BloodMaggotQueen: 3126, + GiantLampreyQueen: 3127, + DevourerQueen: 3128, + RockWormQueen: 3129, + SandMaggotQueen: 3130, + SlimePrince: 3131, + BogCreature: 3132, + SwampDweller: 3133, + BarbedGiant: 3134, + RazorBeast: 3135, + ThornBrute: 3136, + SpikeGiant: 3137, + QuillBear: 3138, + CouncilMember: 3139, + DarkWanderer: 3141, + HellSlinger: 3142, + NightSlinger: 3143, + SpearCat: 3144, + Slinger: 3145, + FireTower: 3146, + LightningSpire: 3147, + PitLord: 3148, + Balrog: 3149, + VenomLord: 3150, + IronWolf: 3151, + InvisoSpawner: 3152, + OblivionKnight: 3153, + Mage: 3154, + AbyssKnight: 3155, + FighterMage: 3156, + DoomKnight: 3157, + Fighter: 3158, + MawFiend: 3159, + CorpseSpitter: 3160, + Corpulent: 3161, + StormCaster: 3162, + Strangler: 3163, + DoomCaster: 3164, + GrotesqueWyrm: 3165, + StygianDog: 3166, + FleshBeast: 3167, + Grotesque: 3168, + StygianHag: 3169, + FleshSpawner: 3170, + RogueScout: 3171, + BloodWingNest: 3172, + BloodHookNest: 3173, + FeederNest: 3174, + SuckerNest: 3175, + Hydra: 3325, + }, + npcs: { + Asheara: 1008, + Hratli: 1009, + Alkor: 1010, + Ormus: 1011, + Natalya: 1012, + Tyrael: 1013, + Izual1: 1014, + Izual2: 1015, + Jamella: 1016, + Halbu: 1017, + Hadriel: 1018, + Hazade: 1019, + Alhizeer: 1020, + Azrael: 1021, + Ahsab: 1022, + Chalan: 1023, + Haseen: 1024, + Razan: 1025, + Emilio: 1026, + Pratham: 1027, + Fazel: 1028, + Jemali: 1029, + Kasim: 1030, + Gulzar: 1031, + Mizan: 1032, + Leharas: 1033, + Durga: 1034, + Neeraj: 1035, + Ilzan: 1036, + Zanarhi: 1037, + Waheed: 1038, + Vikhyat: 1039, + Jelani: 1040, + Barani: 1041, + Jabari: 1042, + Devak: 1043, + Raldin: 1044, + Telash: 1045, + Ajheed: 1046, + Narphet: 1047, + Khaleel: 1048, + Phaet: 1049, + Geshef: 1050, + Vanji: 1051, + Haphet: 1052, + Thadar: 1053, + Yatiraj: 1054, + Rhadge: 1055, + Yashied: 1056, + Lharhad: 1057, + Flux: 1058, + Scorch: 1059, + //Natalya: 3022, both 1012 and 3022 return Natalya? + DeckardCain: 2890, + Gheed: 2891, + Akara: 2892, + Kashya: 2893, + Charsi: 2894, + Warriv: 2895, + Drognan: 3023, + Atma: 3024, + Fara: 3025, + Lysander: 3026, + Jerhyn: 3028, + Geglash: 3029, + Elzix: 3030, + Greiz: 3031, + Flavie: 3112, + Kaelan: 3113, + Meshif: 3114, + Larzuk: 22476, + Anya: 22477, + Malah: 22478, + Nihlathak1: 22479, + QualKehk: 22480, + Guard: 22481, + Combatant: 22482, + Nihlathak2: 22483, + }, + items: { + KhalimsFlail: 1060, + KhalimsWill1: 1061, + KhalimsFlail2: 1062, + KhalimsWill2: 1063, + KhalimsEye: 1064, + KhalimsBrain: 1065, + KhalimsHeart: 1066, + ScrollofInifuss: 2216, + KeytotheCairnStones: 2217, + AJadeFigurine: 2227, + TheGoldenBird: 2228, + LamEsensTome1: 2229, + LamEsensTome2: 2230, + HoradricCube: 2231, + HoradricScroll: 2232, + MephistosSoulstone: 2233, + Ear: 2235, + AmuletoftheViper: 2697, + StaffofKings: 2698, + HoradricStaff: 2699, - node: { - NotOnPlayer: 0, - Storage: 1, - Belt: 2, - Equipped: 3, - Cursor: 4, - }, + // Sets + // Angelic Rainment + AngelicsSword: 10172, + AngelicsArmor: 10173, + AngelicsRing: 10174, + AngelicsAmulet: 10175, + // Arcannas Tricks + ArcannasAmulet: 10180, + ArcannasStaff: 10181, + ArcannasHelmet: 10182, + ArcannasArmor: 10183, + // Artic Gear + ArticsBow: 10176, + ArticsArmor: 10177, + ArticsBelt: 10178, + ArticsGloves: 10179, + // Berserkers Gear + BerserkersHelmet: 10166, + BerserkersAxe: 10167, + BerserkersArmor: 10168, + // Cathans Traps + CathansRing: 10147, + CathansAmulet: 10148, + CathansHelmet: 10149, + CathansArmor: 10150, + CathansStaff: 10151, + // Civerbs Gear + CiverbsShield: 10122, + CiverbsAmulet: 10123, + CiverbsScepter: 10124, + // Clegaws Brace + ClegawsSword: 10128, + ClegawsShield: 10129, + ClegawsGloves: 10130, + // Deaths Disguise + DeathsGloves: 10169, + DeathsBelt: 10170, + DeathsSword: 10171, + // Hsarus Defense + HsarusBoots: 10125, + HsarusShield: 10126, + HsarusBelt: 10127, + // Infernal Tools + InfernalsHelmet: 10163, + InfernalsWand: 10164, + InfernalsBelt: 10165, + // Irathas Finery + IrathasBelt: 10131, + IrathasHelmet: 10132, + IrathasGloves: 10133, + IrathasAmulet: 10134, + // Isenharts Armory + IsenhartsHelmet: 10135, + IsenhartsArmor: 10136, + IsenhartsShield: 10137, + IsenhartsSword: 10138, + // Milabrega Regalia + MilabregasArmor: 10143, + MilabregasHelmet: 10144, + MilabregasScepter: 10145, + MilabregasShield: 10146, + // Sigons + SigonsHelmet: 10157, + SigonsArmor: 10158, + SigonsGloves: 10159, + SigonsBoots: 10160, + SigonsBelt: 10161, + SigonsShield: 10162, + // Tancreds + TancredsPick: 10152, + TancredsArmor: 10153, + TancredsBoots: 10154, + TancredsAmulet: 10155, + TancredsHelmet: 10156, + // Vidalas + VidalasAmulet: 10139, + VidalasArmor: 10140, + VidalasBoots: 10141, + VidalasBow: 10142, - // Same apply's for merc with less things available - body: { - None: 0, - Head: 1, - Neck: 2, - Torso: 3, - Armor: 3, - RightArm: 4, - LeftArm: 5, - RingRight: 6, - RingLeft: 7, - Belt: 8, - Feet: 9, - Gloves: 10, - RightArmSecondary: 11, - LeftArmSecondary: 12 - }, + // LoD Sets + // Aldurs's Legacy + AldursHelmet: 21697, + AldursArmor: 21698, + AldursBoots: 21700, + AldursMace: 21847, + // Bul-Kathos's Children + BulKathosBlade: 21688, + BulKathoSword: 21689, + // Cow Kings's Leathers + CowKingsHelmet: 21723, + CowKingsArmor: 21724, + CowKingsBoots: 21725, + // Disciples + DisciplesAmulet: 21717, + DisciplesGloves: 21718, + DisciplesBoots: 21719, + DisciplesArmor: 21720, + DisciplesBelt: 21721, + // Griswolds's Legacy + GriswoldsScepter: 21673, + GriswoldsShield: 21674, + GriswoldsArmor: 21675, + GriswoldsHelmet: 21676, + // Heaven's Brethren + HeavensMace: 21823, + HeavensHelmet: 21824, + HeavensShield: 21825, + HeavensArmor: 21826, + // Hwanin's + HwaninsHelmet: 21712, + HwaninsPolearm: 21713, + HwaninsArmor: 21714, + HwaninsBelt: 21821, + // IK + ImmortalKingsMaul: 21840, + ImmortalKingsBoots: 21841, + ImmortalKingsGloves: 21842, + ImmortalKingsBelt: 21843, + ImmortalKingsArmor: 21844, + ImmortalKingsHelmet: 21845, + // M'avina's + MavinasHelmet: 21702, + MavinasArmor: 21703, + MavinasGloves: 21704, + MavinasBelt: 21705, + MavinasBow: 21706, + // Natalya's + NatalyasHelmet: 21668, + NatalyasClaw: 21669, + NatalyasArmor: 21670, + NatalyasBoots: 21671, + // Naj's + NajsStaff: 21640, + NajsArmor: 21831, + NajsHelmet: 21832, + // Orphan's + OrphansHelmet: 21731, + OrphansBelt: 21732, + OrphansGloves: 21733, + OrphansShield: 21734, + // Sanders's + SandersGloves: 21876, + SandersBoots: 21877, + SandersHelmet: 21878, + SandersWand: 21879, + // Sazabi's + SazabisSword: 21708, + SazabisArmor: 21709, + SazabisHelmet: 21710, + // Tal + TalRashasBelt: 21816, + TalRashasAmulet: 21817, + TalRashasArmor: 21818, + TalRashasOrb: 21819, + TalRashasHelmet: 21820, + // Trang-Ouls + TrangOulsHelmet: 21661, + TrangOulsShield: 21662, + TrangOulsArmor: 21664, + TrangOulsGloves: 21665, + TrangOulsBelt: 21666, - items: { - cost: { - ToBuy: 0, - ToSell: 1, - ToRepair: 2, - }, - flags: { - Equipped: 0x00000001, - InSocket: 0x00000008, - Identified: 0x00000010, - OnActiveWeaponSlot: 0x00000040, - OnSwapWeaponSlot: 0x00000080, - Broken: 0x00000100, - FullRejuv: 0x00000400, - Socketed: 0x00000800, - InTradeGamble: 0x00002000, - NotInSocket: 0x00004000, - Ear: 0x00010000, - StartingItem: 0x00020000, - RuneQuestPotion: 0x00200000, - Ethereal: 0x00400000, - IsAnItem: 0x00800000, - Personalized: 0x01000000, - Runeword: 0x04000000, - }, - mode: { - inStorage: 0, //Item inven stash cube store = Item inven stash cube store - Equipped: 1, // Item equipped self or merc - inBelt: 2, // Item in belt - onGround: 3, // Item on ground - onCursor: 4, // Item on cursor - Dropping: 5, // Item being dropped - Socketed: 6 // Item socketed in item - }, - quality: { - LowQuality: 1, - Normal: 2, - Superior: 3, - Magic: 4, - Set: 5, - Rare: 6, - Unique: 7, - Crafted: 8, - }, - class: { - Normal: 0, - Exceptional: 1, - Elite: 2, - }, - type: { - Shield: 2, - Armor: 3, - Gold: 4, - BowQuiver: 5, - CrossbowQuiver: 6, - PlayerBodyPart: 7, - Herb: 8, - Potion: 9, - Ring: 10, - Elixir: 11, - Amulet: 12, - Charm: 13, - notused0: 14, - Boots: 15, - Gloves: 16, - notused1: 17, - Book: 18, - Belt: 19, - Gem: 20, - Torch: 21, - Scroll: 22, - notused2: 23, - Scepter: 24, - Wand: 25, - Staff: 26, - Bow: 27, - Axe: 28, - Club: 29, - Sword: 30, - Hammer: 31, - Knife: 32, - Spear: 33, - Polearm: 34, - Crossbow: 35, - Mace: 36, - Helm: 37, - MissilePotion: 38, - Quest: 39, - Bodypart: 40, - Key: 41, - ThrowingKnife: 42, - ThrowingAxe: 43, - Javelin: 44, - Weapon: 45, - MeleeWeapon: 46, - MissileWeapon: 47, - ThrownWeapon: 48, - ComboWeapon: 49, - AnyArmor: 50, - AnyShield: 51, - Miscellaneous: 52, - SocketFiller: 53, - Secondhand: 54, - StavesandRods: 55, - Missile: 56, - Blunt: 57, - Jewel: 58, - ClassSpecific: 59, - AmazonItem: 60, - BarbarianItem: 61, - NecromancerItem: 62, - PaladinItem: 63, - SorceressItem: 64, - AssassinItem: 65, - DruidItem: 66, - HandtoHand: 67, - Orb: 68, - VoodooHeads: 69, - AuricShields: 70, - PrimalHelm: 71, - Pelt: 72, - Cloak: 73, - Rune: 74, - Circlet: 75, - HealingPotion: 76, - ManaPotion: 77, - RejuvPotion: 78, - StaminaPotion: 79, - AntidotePotion: 80, - ThawingPotion: 81, - SmallCharm: 82, - LargeCharm: 83, - GrandCharm: 84, - AmazonBow: 85, - AmazonSpear: 86, - AmazonJavelin: 87, - AssassinClaw: 88, - MagicBowQuiv: 89, - MagicxBowQuiv: 90, - ChippedGem: 91, - FlawedGem: 92, - StandardGem: 93, - FlawlessGem: 94, - PerfectgGem: 95, - Amethyst: 96, - Diamond: 97, - Emerald: 98, - Ruby: 99, - Sapphire: 100, - Topaz: 101, - Skull: 102, - }, - - // Weapons - "HandAxe": 0, - "Axe": 1, - "DoubleAxe": 2, - "MilitaryPick": 3, - "WarAxe": 4, - "LargeAxe": 5, - "BroadAxe": 6, - "BattleAxe": 7, - "GreatAxe": 8, - "GiantAxe": 9, - "Wand": 10, - "YewWand": 11, - "BoneWand": 12, - "GrimWand": 13, - "Club": 14, - "Scepter": 15, - "GrandScepter": 16, - "WarScepter": 17, - "SpikedClub": 18, - "Mace": 19, - "MorningStar": 20, - "Flail": 21, - "WarHammer": 22, - "Maul": 23, - "GreatMaul": 24, - "ShortSword": 25, - "Scimitar": 26, - "Sabre": 27, - "Falchion": 28, - "CrystalSword": 29, - "BroadSword": 30, - "LongSword": 31, - "WarSword": 32, - "Two_HandedSword": 33, - "Claymore": 34, - "GiantSword": 35, - "BastardSword": 36, - "Flamberge": 37, - "GreatSword": 38, - "Dagger": 39, - "Dirk": 40, - "Kris": 41, - "Blade": 42, - "ThrowingKnife": 43, - "ThrowingAxe": 44, - "BalancedKnife": 45, - "BalancedAxe": 46, - "Javelin": 47, - "Pilum": 48, - "ShortSpear": 49, - "Glaive": 50, - "ThrowingSpear": 51, - "Spear": 52, - "Trident": 53, - "Brandistock": 54, - "Spetum": 55, - "Pike": 56, - "Bardiche": 57, - "Voulge": 58, - "Scythe": 59, - "Poleaxe": 60, - "Halberd": 61, - "WarScythe": 62, - "ShortStaff": 63, - "LongStaff": 64, - "GnarledStaff": 65, - "BattleStaff": 66, - "WarStaff": 67, - "ShortBow": 68, - "HuntersBow": 69, - "LongBow": 70, - "CompositeBow": 71, - "ShortBattleBow": 72, - "LongBattleBow": 73, - "ShortWarBow": 74, - "LongWarBow": 75, - "LightCrossbow": 76, - "Crossbow": 77, - "HeavyCrossbow": 78, - "RepeatingCrossbow": 79, - "Hatchet": 93, - "Cleaver": 94, - "TwinAxe": 95, - "Crowbill": 96, - "Naga": 97, - "MilitaryAxe": 98, - "BeardedAxe": 99, - "Tabar": 100, - "GothicAxe": 101, - "AncientAxe": 102, - "BurntWand": 103, - "PetrifiedWand": 104, - "TombWand": 105, - "GraveWand": 106, - "Cudgel": 107, - "RuneScepter": 108, - "HolyWaterSprinkler": 109, - "DivineScepter": 110, - "BarbedClub": 111, - "FlangedMace": 112, - "JaggedStar": 113, - "Knout": 114, - "BattleHammer": 115, - "WarClub": 116, - "MarteldeFer": 117, - "Gladius": 118, - "Cutlass": 119, - "Shamshir": 120, - "Tulwar": 121, - "DimensionalBlade": 122, - "BattleSword": 123, - "RuneSword": 124, - "AncientSword": 125, - "Espandon": 126, - "DacianFalx": 127, - "TuskSword": 128, - "GothicSword": 129, - "Zweihander": 130, - "ExecutionerSword": 131, - "Poignard": 132, - "Rondel": 133, - "Cinquedeas": 134, - "Stiletto": 135, - "BattleDart": 136, - "Francisca": 137, - "WarDart": 138, - "Hurlbat": 139, - "WarJavelin": 140, - "GreatPilum": 141, - "Simbilan": 142, - "Spiculum": 143, - "Harpoon": 144, - "WarSpear": 145, - "Fuscina": 146, - "WarFork": 147, - "Yari": 148, - "Lance": 149, - "LochaberAxe": 150, - "Bill": 151, - "BattleScythe": 152, - "Partizan": 153, - "Bec_de_Corbin": 154, - "GrimScythe": 155, - "JoStaff": 156, - "Quarterstaff": 157, - "CedarStaff": 158, - "GothicStaff": 159, - "RuneStaff": 160, - "EdgeBow": 161, - "RazorBow": 162, - "CedarBow": 163, - "DoubleBow": 164, - "ShortSiegeBow": 165, - "LargeSiegeBow": 166, - "RuneBow": 167, - "GothicBow": 168, - "Arbalest": 169, - "SiegeCrossbow": 170, - "Ballista": 171, - "Chu_Ko_Nu": 172, - "Katar": 175, - "WristBlade": 176, - "HatchetHands": 177, - "Cestus": 178, - "Claws": 179, - "BladeTalons": 180, - "ScissorsKatar": 181, - "Quhab": 182, - "WristSpike": 183, - "Fascia": 184, - "HandScythe": 185, - "GreaterClaws": 186, - "GreaterTalons": 187, - "ScissorsQuhab": 188, - "Suwayyah": 189, - "WristSword": 190, - "WarFist": 191, - "BattleCestus": 192, - "FeralClaws": 193, - "RunicTalons": 194, - "ScissorsSuwayyah": 195, - "Tomahawk": 196, - "SmallCrescent": 197, - "EttinAxe": 198, - "WarSpike": 199, - "BerserkerAxe": 200, - "FeralAxe": 201, - "Silver_edgedAxe": 202, - "Decapitator": 203, - "ChampionAxe": 204, - "GloriousAxe": 205, - "PolishedWand": 206, - "GhostWand": 207, - "LichWand": 208, - "UnearthedWand": 209, - "Truncheon": 210, - "MightyScepter": 211, - "SeraphRod": 212, - "Caduceus": 213, - "TyrantClub": 214, - "ReinforcedMace": 215, - "DevilStar": 216, - "Scourge": 217, - "LegendaryMallet": 218, - "OgreMaul": 219, - "ThunderMaul": 220, - "Falcata": 221, - "Ataghan": 222, - "ElegantBlade": 223, - "HydraEdge": 224, - "PhaseBlade": 225, - "ConquestSword": 226, - "CrypticSword": 227, - "MythicalSword": 228, - "LegendSword": 229, - "HighlandBlade": 230, - "BalrogBlade": 231, - "ChampionSword": 232, - "ColossusSword": 233, - "ColossusBlade": 234, - "BoneKnife": 235, - "MithrilPoint": 236, - "FangedKnife": 237, - "LegendSpike": 238, - "FlyingKnife": 239, - "FlyingAxe": 240, - "WingedKnife": 241, - "WingedAxe": 242, - "HyperionJavelin": 243, - "StygianPilum": 244, - "BalrogSpear": 245, - "GhostGlaive": 246, - "WingedHarpoon": 247, - "HyperionSpear": 248, - "StygianPike": 249, - "Mancatcher": 250, - "GhostSpear": 251, - "WarPike": 252, - "OgreAxe": 253, - "ColossusVoulge": 254, - "Thresher": 255, - "CrypticAxe": 256, - "GreatPoleaxe": 257, - "GiantThresher": 258, - "WalkingStick": 259, - "Stalagmite": 260, - "ElderStaff": 261, - "Shillelagh": 262, - "ArchonStaff": 263, - "SpiderBow": 264, - "BladeBow": 265, - "ShadowBow": 266, - "GreatBow": 267, - "DiamondBow": 268, - "CrusaderBow": 269, - "WardBow": 270, - "HydraBow": 271, - "PelletBow": 272, - "GorgonCrossbow": 273, - "ColossusCrossbow": 274, - "DemonCrossbow": 275, - "EagleOrb": 276, - "SacredGlobe": 277, - "SmokedSphere": 278, - "ClaspedOrb": 279, - "JaredsStone": 280, - "StagBow": 281, - "ReflexBow": 282, - "MaidenSpear": 283, - "MaidenPike": 284, - "MaidenJavelin": 285, - "GlowingOrb": 286, - "CrystallineGlobe": 287, - "CloudySphere": 288, - "SparklingBall": 289, - "SwirlingCrystal": 290, - "AshwoodBow": 291, - "CeremonialBow": 292, - "CeremonialSpear": 293, - "CeremonialPike": 294, - "CeremonialJavelin": 295, - "HeavenlyStone": 296, - "EldritchOrb": 297, - "DemonHeart": 298, - "VortexOrb": 299, - "DimensionalShard": 300, - "MatriarchalBow": 301, - "GrandMatronBow": 302, - "MatriarchalSpear": 303, - "MatriarchalPike": 304, - "MatriarchalJavelin": 305, - "Cap": 306, - "SkullCap": 307, - "Helm": 308, - "FullHelm": 309, - "GreatHelm": 310, - "Crown": 311, - "Mask": 312, - "QuiltedArmor": 313, - "LeatherArmor": 314, - "HardLeatherArmor": 315, - "StuddedLeather": 316, - "RingMail": 317, - "ScaleMail": 318, - "ChainMail": 319, - "BreastPlate": 320, - "SplintMail": 321, - "PlateMail": 322, - "FieldPlate": 323, - "GothicPlate": 324, - "FullPlateMail": 325, - "AncientArmor": 326, - "LightPlate": 327, - "Buckler": 328, - "SmallShield": 329, - "LargeShield": 330, - "KiteShield": 331, - "TowerShield": 332, - "GothicShield": 333, - "LeatherGloves": 334, - "HeavyGloves": 335, - "ChainGloves": 336, - "LightGauntlets": 337, - "Gauntlets": 338, - "Boots": 339, - "HeavyBoots": 340, - "ChainBoots": 341, - "LightPlatedBoots": 342, - "Greaves": 343, - "Sash": 344, - "LightBelt": 345, - "Belt": 346, - "HeavyBelt": 347, - "PlatedBelt": 348, - "BoneHelm": 349, - "BoneShield": 350, - "SpikedShield": 351, - "WarHat": 352, - "Sallet": 353, - "Casque": 354, - "Basinet": 355, - "WingedHelm": 356, - "GrandCrown": 357, - "DeathMask": 358, - "GhostArmor": 359, - "SerpentskinArmor": 360, - "DemonhideArmor": 361, - "TrellisedArmor": 362, - "LinkedMail": 363, - "TigulatedMail": 364, - "MeshArmor": 365, - "Cuirass": 366, - "RussetArmor": 367, - "TemplarCoat": 368, - "SharktoothArmor": 369, - "EmbossedPlate": 370, - "ChaosArmor": 371, - "OrnatePlate": 372, - "MagePlate": 373, - "Defender": 374, - "RoundShield": 375, - "Scutum": 376, - "DragonShield": 377, - "Pavise": 378, - "AncientShield": 379, - "DemonhideGloves": 380, - "SharkskinGloves": 381, - "HeavyBracers": 382, - "BattleGauntlets": 383, - "WarGauntlets": 384, - "DemonhideBoots": 385, - "SharkskinBoots": 386, - "MeshBoots": 387, - "BattleBoots": 388, - "WarBoots": 389, - "DemonhideSash": 390, - "SharkskinBelt": 391, - "MeshBelt": 392, - "BattleBelt": 393, - "WarBelt": 394, - "GrimHelm": 395, - "GrimShield": 396, - "BarbedShield": 397, - "WolfHead": 398, - "HawkHelm": 399, - "Antlers": 400, - "FalconMask": 401, - "SpiritMask": 402, - "JawboneCap": 403, - "FangedHelm": 404, - "HornedHelm": 405, - "AssaultHelmet": 406, - "AvengerGuard": 407, - "Targe": 408, - "Rondache": 409, - "HeraldicShield": 410, - "AerinShield": 411, - "CrownShield": 412, - "PreservedHead": 413, - "ZombieHead": 414, - "UnravellerHead": 415, - "GargoyleHead": 416, - "DemonHead": 417, - "Circlet": 418, - "Coronet": 419, - "Tiara": 420, - "Diadem": 421, - "Shako": 422, - "Hydraskull": 423, - "Armet": 424, - "GiantConch": 425, - "SpiredHelm": 426, - "Corona": 427, - "Demonhead": 428, - "DuskShroud": 429, - "Wyrmhide": 430, - "ScarabHusk": 431, - "WireFleece": 432, - "DiamondMail": 433, - "LoricatedMail": 434, - "Boneweave": 435, - "GreatHauberk": 436, - "BalrogSkin": 437, - "HellforgePlate": 438, - "KrakenShell": 439, - "LacqueredPlate": 440, - "ShadowPlate": 441, - "SacredArmor": 442, - "ArchonPlate": 443, - "Heater": 444, - "Luna": 445, - "Hyperion": 446, - "Monarch": 447, - "Aegis": 448, - "Ward": 449, - "BrambleMitts": 450, - "VampireboneGloves": 451, - "Vambraces": 452, - "CrusaderGauntlets": 453, - "OgreGauntlets": 454, - "WyrmhideBoots": 455, - "ScarabshellBoots": 456, - "BoneweaveBoots": 457, - "MirroredBoots": 458, - "MyrmidonGreaves": 459, - "SpiderwebSash": 460, - "VampirefangBelt": 461, - "MithrilCoil": 462, - "TrollBelt": 463, - "ColossusGirdle": 464, - "BoneVisage": 465, - "TrollNest": 466, - "BladeBarrier": 467, - "AlphaHelm": 468, - "GriffonHeaddress": 469, - "HuntersGuise": 470, - "SacredFeathers": 471, - "TotemicMask": 472, - "JawboneVisor": 473, - "LionHelm": 474, - "RageMask": 475, - "SavageHelmet": 476, - "SlayerGuard": 477, - "AkaranTarge": 478, - "AkaranRondache": 479, - "ProtectorShield": 480, - "GildedShield": 481, - "RoyalShield": 482, - "MummifiedTrophy": 483, - "FetishTrophy": 484, - "SextonTrophy": 485, - "CantorTrophy": 486, - "HierophantTrophy": 487, - "BloodSpirit": 488, - "SunSpirit": 489, - "EarthSpirit": 490, - "SkySpirit": 491, - "DreamSpirit": 492, - "CarnageHelm": 493, - "FuryVisor": 494, - "DestroyerHelm": 495, - "ConquerorCrown": 496, - "GuardianCrown": 497, - "SacredTarge": 498, - "SacredRondache": 499, - "KurastShield": 500, - "ZakarumShield": 501, - "VortexShield": 502, - "MinionSkull": 503, - "HellspawnSkull": 504, - "OverseerSkull": 505, - "SuccubusSkull": 506, - "BloodlordSkull": 507, - "Amulet": 520, - "Ring": 522, - "Arrows": 526, - "Bolts": 528, - "Jewel": 643, + // Uniques + // Quest/Misc + KeyofTerror: 11146, + KeyofHate: 11147, + KeyofDestruction: 11148, + DiablosHorn: 11149, + BaalsEye: 11150, + MephistosBrain: 11151, + StandardofHeroes: 11152, + HellfireTorch: 11153, + Annihilus: 21743, - // Misc? - "Elixir": 508, - "Torch": 527, - "Heart": 531, - "Brain": 532, - "Jawbone": 533, - "Eye": 534, - "Horn": 535, - "Tail": 536, - "Flag": 537, - "Fang": 538, - "Quill": 539, - "Soul": 540, - "Scalp": 541, - "Spleen": 542, - "Key": 543, - "Ear": 556, - "Herb": 602, - "anevilforce": 609, - // Potions, tomes/scrolls, gold - "TomeofTownPortal": 518, - "TomeofIdentify": 519, - "ScrollofTownPortal": 529, - "ScrollofIdentify": 530, - "RancidGasPotion": 80, - "OilPotion": 81, - "ChokingGasPotion": 82, - "ExplodingPotion": 83, - "StranglingGasPotion": 84, - "FulminatingPotion": 85, - "StaminaPotion": 513, - "AntidotePotion": 514, - "RejuvenationPotion": 515, - "FullRejuvenationPotion": 516, - "ThawingPotion": 517, - "MinorHealingPotion": 587, - "LightHealingPotion": 588, - "HealingPotion": 589, - "GreaterHealingPotion": 590, - "SuperHealingPotion": 591, - "MinorManaPotion": 592, - "LightManaPotion": 593, - "ManaPotion": 594, - "GreaterManaPotion": 595, - "SuperManaPotion": 596, - "Gold": 523, - // Charms - "SmallCharm": 603, - "LargeCharm": 604, - "GrandCharm": 605, + // Unique Items + WitchwildString: 10911, + TitansRevenge: 21735, + LycandersAim: 21737, + ArreatsFace: 21744, + Homunculus: 21755, + JalalsMane: 21750, + HeraldofZakarum: 21758, + BloodRavensCharge: 21508, + Windforce: 21635, + Gimmershred: 21637, + MedusasGaze: 21516, + Rockstopper: 21519, + CrownofThieves: 21522, + BlackhornsFace: 21523, + TheSpiritShroud: 21524, + SkinoftheFlayedOne: 21525, + IronPelt: 21526, + SpiritForge: 21527, + CrowCaw: 21528, + DurielsShell: 21529, + SkulldersIre: 21530, + Toothrow: 21531, + AtmasWail: 21532, + BlackHades: 21533, + Corpsemourn: 21534, + QueHegans: 21535, + QueHegansWisdom: 21535, + Mosers: 21536, + MosersBlessedCircle: 21536, + Stormchaser: 21537, + TiamatsRubuke: 21538, + GerkesSanctuary: 21539, + RadamentsSphere: 21540, + Gravepalm: 21541, + Ghoulhide: 21542, + Hellmouth: 21543, + Infernostride: 21544, + Waterwalk: 21545, + Silkweave: 21546, + WarTraveler: 21547, + Razortail: 21548, + GloomsTrap: 21549, + Snowclash: 21550, + ThundergodsVigor: 21551, + LidlessWall: 21552, + LanceGuard: 21553, + Boneflame: 21555, + SteelPillar: 21556, + NightwingsVeil: 21557, + CrownofAges: 21559, + AndarielsVisage: 21560, + Dragonscale: 21562, + SteelCarapace: 21563, + RainbowFacet: 21565, + Ravenlore: 21566, + Boneshade: 21567, + Flamebellow: 21570, + DeathsFathom: 21571, + Wolfhowl: 21572, + SpiritWard: 21573, + KirasGuardian: 21574, + OrmusRobe: 21575, + GheedsFortune: 21576, + HalberdsReign: 21579, + DraculsGrasp: 21583, + Frostwind: 21584, + TemplarsMight: 21585, + EschutasTemper: 21586, // also 21620? + FirelizardsTalons: 21587, + SandstormTrek: 21588, + Marrowwalk: 21589, + HeavensLight: 21590, + ArachnidMesh: 21592, + NosferatusCoil: 21593, + Verdungos: 21595, + VerdungosHeartyCord: 21595, + CarrionWind: 21597, + GiantSkull: 21598, + AstreonsIronWard: 21599, + SaracensChance: 21608, + HighlordsWrath: 21609, + Ravenfrost: 21610, + Dwarfstar: 21611, + AtmasScarab: 21612, + Maras: 21613, + MarasKaleidoscope: 21613, + CrescentMoonAmulet: 21614, + TheRisingSun: 21615, + TheCatsEye: 21616, + BulKathosWeddingBand: 21617, + Metalgrid: 21619, + Stormshield: 21621, + BlackoakShield: 21622, + ArkainesValor: 21624, + TheGladiatorsBane: 21625, + HarlequinsCrest: 21627, + GuardianAngel: 21632, + TheGrandfather: 21643, + Doombringer: 21644, + TyraelsMight: 21645, + Lightsabre: 21646, + TheCraniumBasher: 21647, + DeathsWeb: 21650, + TheAtlantean: 21654, + CarinShard: 21658, + Coldkill: 21286, + ButchersCleaver: 21287, + Islestrike: 21289, + GuardianNaga: 21291, + SpellSteel: 21293, + SuicideBranch: 21297, + ArmofKingLeoric: 21299, + BlackhandKey: 21300, + DarkClanCrusher: 21301, + TheFetidSprinkler: 21304, + HandofBlessedLight: 21305, + Fleshrender: 21306, + SureshrillFrost: 21307, + Moonfall: 21308, + BaezilsVortex: 21309, + Earthshaker: 21310, + TheGavelofPain: 21312, + Bloodletter: 21313, + ColdstealEye: 21314, + Hexfire: 21315, + BladeofAliBaba: 21316, + Riftslash: 21317, + Headstriker: 21318, + PlagueBearer: 21319, + //TheAtlantean: 21320, + CrainteVomir: 21321, + BingSzWang: 21322, + TheVileHusk: 21323, + Cloudcrack: 21324, + TodesfaelleFlamme: 21325, + Swordguard: 21326, + Spineripper: 21327, + HeartCarver: 21328, + BlackbogsSharp: 21329, + Stormspike: 21330, + TheImpaler: 21331, + HoneSudan: 21334, + SpireofHonor: 21335, + TheMeatScraper: 21336, + BlackleachBlade: 21337, + AthenasWrath: 21338, + PierreTombaleCouant: 21339, + GrimsBurningDead: 21341, + Ribcracker: 21342, + ChromaticIre: 21343, + Warspear: 21344, + SkullCollector: 21345, + Skystrike: 21346, + //WitchwildString: 21349, + GoldstrikeArch: 21350, + PusSpitter: 21352, + VampireGaze: 21354, + StringofEars: 21355, + GoreRider: 21356, + LavaGout: 21357, + VenomGrip: 21358, + Visceratuant: 21359, + //GuardianAngel: 21360, + Shaftstop: 21361, + SkinofVipermagi: 21362, + Blackhorn: 21363, + ValkyrieWing: 21364, + PeasantCrown: 21365, + DemonMachine: 21366, + Riphook: 21369, + Razorswitch: 21370, + OndalsWisdom: 21375, + Deathbit: 21379, + Warshrike: 21380, + DemonLimb: 21387, + SteelShade: 21388, + TombReaver: 21389, + //DeathsWeb: 21390, + AngelsSong: 21393, + TheRedeemer: 21394, + Bonehew: 21398, + Steelrend: 21399, + AriocsNeedle: 21402, + SoulDrainer: 21407, + RuneMaster: 21408, + DeathCleaver: 21409, + ExecutionersJustice: 21410, + Leviathan: 21412, + WispProjector: 21417, + Lacerator: 21419, + MangSongsLesson: 21420, + Viperfork: 21421, + TheReapersToll: 21427, + SpiritKeeper: 21428, + Hellrack: 21429, + AlmaNegra: 21430, + DarkforceSpawn: 21431, + Ghostflame: 21438, + ShadowKiller: 21439, + GriffonsEye: 21442, + Thunderstroke: 21445, + DemonsArch: 21447, + DjinnSlayer: 21450, + GinthersRift: 21829, - quest: { - // Act 1 - WirtsLeg: 88, - HoradricMalus: 89, - ScrollofInifuss: 524, - KeytotheCairnStones: 525, - // Act 2 - FinishedStaff: 91, - "HoradricStaff": 91, - IncompleteStaff: 92, - "ShaftoftheHoradricStaff": 92, - ViperAmulet: 521, - "TopoftheHoradricStaff": 521, - Cube: 549, - BookofSkill: 552, - // Act 3 - "DecoyGidbinn": 86, - "TheGidbinn": 87, - KhalimsFlail: 173, - KhalimsWill: 174, - PotofLife: 545, - "AJadeFigurine": 546, - JadeFigurine: 546, - TheGoldenBird: 547, - LamEsensTome: 548, - KhalimsEye: 553, - KhalimsHeart: 554, - KhalimsBrain: 555, - // Act 4 - HellForgeHammer: 90, - Soulstone: 551, - "MephistosSoulstone": 551, - // Act 5 - MalahsPotion: 644, - "ScrollofKnowledge": 645, - ScrollofResistance: 646, - // Pandemonium Event - KeyofTerror: 647, - KeyofHate: 648, - KeyofDestruction: 649, - DiablosHorn: 650, - BaalsEye: 651, - MephistosBrain: 652, - StandardofHeroes: 658, - // Essences/Token - TokenofAbsolution: 653, - TwistedEssenceofSuffering: 654, - ChargedEssenceofHatred: 655, - BurningEssenceofTerror: 656, - FesteringEssenceofDestruction: 657, - // Misc - "TheBlackTowerKey": 544, - }, - runes: { - El: 610, - Eld: 611, - Tir: 612, - Nef: 613, - Eth: 614, - Ith: 615, - Tal: 616, - Ral: 617, - Ort: 618, - Thul: 619, - Amn: 620, - Sol: 621, - Shael: 622, - Dol: 623, - Hel: 624, - Io: 625, - Lum: 626, - Ko: 627, - Fal: 628, - Lem: 629, - Pul: 630, - Um: 631, - Mal: 632, - Ist: 633, - Gul: 634, - Vex: 635, - Ohm: 636, - Lo: 637, - Sur: 638, - Ber: 639, - Jah: 640, - Cham: 641, - Zod: 642, - }, - gems: { - Perfect: { - Amethyst: 561, - Topaz: 566, - Sapphire: 571, - Emerald: 576, - Ruby: 581, - Diamond: 586, - Skull: 601, - }, - Flawless: { - Amethyst: 560, - Topaz: 565, - Sapphire: 570, - Emerald: 575, - Ruby: 580, - Diamond: 585, - Skull: 600, - }, - Normal: { - Amethyst: 559, - Topaz: 564, - Sapphire: 569, - Emerald: 574, - Ruby: 579, - Diamond: 584, - Skull: 599, - }, - Flawed: { - Amethyst: 558, - Topaz: 563, - Sapphire: 568, - Emerald: 573, - Ruby: 578, - Diamond: 583, - Skull: 598, - }, - Chipped: { - Amethyst: 557, - Topaz: 562, - Sapphire: 567, - Emerald: 572, - Ruby: 577, - Diamond: 582, - Skull: 597, - }, - }, - }, + // Runewords + AncientsPledge: 20507, + Armageddon: 20508, + Authority: 20509, + Beast: 20510, + Beauty: 20511, + Black: 20512, + Blood: 20513, + Bone: 20514, + Bramble: 20515, + Brand: 20516, + BreathoftheDying: 20517, + BrokenPromise: 20518, + CalltoArms: 20519, + ChainsofHonor: 20520, + Chance: 20521, + Chaos: 20522, + CrescentMoon: 20523, + Darkness: 20524, + Daylight: 20525, + Death: 20526, + Deception: 20527, + Delerium: 20528, + Desire: 20529, + Despair: 20530, + Destruction: 20531, + Doom: 20532, + Dragon: 20533, + Dread: 20534, + Dream: 20535, + Duress: 20536, + Edge: 20537, + Elation: 20538, + Enigma: 20539, + Enlightenment: 20540, + Envy: 20541, + Eternity: 20542, + Exile: 20543, + Faith: 20544, + Famine: 20545, + Flame: 20546, + Fortitude: 20547, + Fortune: 20548, + Friendship: 20549, + Fury: 20550, + Gloom: 20551, + Grief: 20553, + HandofJustice: 20554, + Harmony: 20555, + HeartoftheOak: 20557, + HolyThunder: 20560, + Honor: 20561, + Revenge: 20562, + Humility: 20563, + Hunger: 20564, + Ice: 20565, + Infinity: 20566, + Innocence: 20567, + Insight: 20568, + Jealousy: 20569, + Judgement: 20570, + KingsGrace: 20571, + Kingslayer: 20572, + KnightsVigil: 20573, + Knowledge: 20574, + LastWish: 20575, + Law: 20576, + Lawbringer: 20577, + Leaf: 20578, + Lightning: 20579, + Lionheart: 20580, + Lore: 20581, + Love: 20582, + Loyalty: 20583, + Lust: 20584, + Madness: 20585, + Malice: 20586, + Melody: 20587, + Memory: 20588, + Mist: 20589, + Morning: 20590, + Mystery: 20591, + Myth: 20592, + Nadir: 20593, + NaturesKingdom: 20594, + Night: 20595, + Oath: 20596, + Obedience: 20597, + Oblivion: 20598, + Obsession: 20599, + Passion: 20600, + Patience: 20601, + Patter: 20602, + Peace: 20603, + VoiceofReason: 20604, + Penitence: 20605, + Peril: 20606, + Pestilence: 20607, + Phoenix: 20608, + Piety: 20609, + PillarofFaith: 20610, + Plague: 20611, + Praise: 20612, + Prayer: 20613, + Pride: 20614, + Principle: 20615, + ProwessinBattle: 20616, + Prudence: 20617, + Punishment: 20618, + Purity: 20619, + Question: 20620, + Radiance: 20621, + Rain: 20622, + Reason: 20623, + Red: 20624, + Rhyme: 20625, + Rift: 20626, + Sanctuary: 20627, + Serendipity: 20628, + Shadow: 20629, + ShadowofDoubt: 20630, + Silence: 20631, + SirensSong: 20632, + Smoke: 20633, + Sorrow: 20634, + Spirit: 20635, + Splendor: 20636, + Starlight: 20637, + Stealth: 20638, + Steel: 20639, + StillWater: 20640, + Sting: 20641, + Stone: 20642, + Storm: 20643, + Strength: 20644, + Tempest: 20645, + Temptation: 20646, + Terror: 20647, + Thirst: 20648, + Thought: 20649, + Thunder: 20650, + Time: 20651, + Tradition: 20652, + Treachery: 20653, + Trust: 20654, + Truth: 20655, + UnbendingWill: 20656, + Valor: 20657, + Vengeance: 20658, + Venom: 20659, + Victory: 20660, + Voice: 20661, + Void: 20662, + War: 20663, + Water: 20664, + Wealth: 20665, + Whisper: 20666, + White: 20667, + Wind: 20668, + WingsofHope: 20669, + Wisdom: 20670, + Woe: 20671, + Wonder: 20672, + Wrath: 20673, + Youth: 20674, + Zephyr: 20675, + }, + dialog: { + youDoNotHaveEnoughGoldForThat: 3362 + }, - // locale strings - locale: { - monsters: { - // bosses - Andariel: 3021, - Duriel: 3054, - Mephisto: 3062, - Diablo: 3060, - Baal: 3061, - // Mini bosses - BloodRaven: 3111, - TreeheadWoodFist: 2873, - TheCountess: 2875, - TheSmith: 2889, - Radament: 2879, - TheSummoner: 3099, - HephastoTheArmorer: 1067, - Izual: 1014, - ShenktheOverseer: 22435, - // Uniques - Corpsefire: 3319, - TheCowKing: 2850, - GrandVizierofChaos: 2851, - LordDeSeis: 2852, - InfectorofSouls: 2853, - RiftwraiththeCannibal: 2854, - Taintbreeder: 2855, - TheTormentor: 2856, - Darkwing: 2857, - MafferDragonhand: 2858, - WyandVoidbringer: 2859, - ToorcIcefist: 2860, - BremmSparkfist: 2861, - GelebFlamefinger: 2862, - IsmailVilehand: 2863, - IcehawkRiftwing: 2864, - BattlemaidSarina: 2865, - Stormtree: 2866, - WitchDoctorEndugu: 2867, - SszarkTheBurning: 2868, - Bishibosh: 2869, - Bonebreaker: 2870, - Coldcrow: 2871, - Rakanishu: 2872, - Griswold: 2874, - PitspawnFouldog: 2876, - FlamespiketheCrawler: 2877, - BoneAsh: 2878, - BloodwitchtheWild: 2880, - Fangskin: 2881, - Beetleburst: 2882, - CreepingFeature: 2883, - ColdwormtheBurrower: 2884, - FireEye: 2885, - DarkElder: 2886, - AncientKaatheSoulless: 2888, - SharpToothSayer: 22493, - SnapchipShatter: 22496, - Pindleskin: 22497, - ThreshSocket: 22498, - EyebacktheUnleashed: 22499, - EldritchtheRectifier: 22500, - DacFarren: 22501, - BonesawBreaker: 22502, - Frozenstein: 22504, - Rogue: 2897, - StygianDoll: 2898, - SoulKiller: 2899, - Flayer: 2900, - Fetish: 2901, - RatMan: 2902, - UndeadStygianDoll: 2903, - UndeadSoulKiller: 2904, - UndeadFlayer: 2905, - UndeadFetish: 2906, - UndeadRatMan: 2907, - DarkFamiliar: 2908, - BloodDiver: 2909, - Gloombat: 2910, - DesertWing: 2911, - TheBanished: 2912, - BloodLord: 2913, - DarkLord: 2914, - NightLord: 2915, - GhoulLord: 2916, - Spikefist: 2917, - Thrasher: 2918, - BrambleHulk: 2919, - ThornedHulk: 2920, - SpiderMagus: 2921, - FlameSpider: 2922, - PoisonSpinner: 2923, - SandFisher: 2924, - Arach: 2925, - BloodWing: 2926, - BloodHook: 2927, - Feeder: 2928, - Sucker: 2929, - WingedNightmare: 2930, - HellBuzzard: 2931, - UndeadScavenger: 2932, - CarrionBird: 2933, - Unraveler: 2934, - Guardian: 2935, - HollowOne: 2936, - HoradrimAncient: 2937, - BoneScarab: 2938, - SteelScarab: 2939, - Scarab: 2940, - DeathBeetle: 2941, - DungSoldier: 2942, - HellSwarm: 2943, - PlagueBugs: 2944, - BlackLocusts: 2945, - Itchies: 2946, - HellCat: 2947, - NightTiger: 2948, - SaberCat: 2949, - Huntress: 2950, - CliffLurker: 2951, - TreeLurker: 2952, - CaveLeaper: 2953, - TombCreeper: 2954, - SandLeaper: 2955, - TombViper: 2956, - PitViper: 2957, - Salamander: 2958, - ClawViper: 2959, - SerpentMagus: 2960, - BloodMaggot: 2961, - GiantLamprey: 2962, - Devourer: 2963, - RockWorm: 2964, - SandMaggot: 2965, - BushBarb: 2966, - RazorSpine: 2967, - ThornBeast: 2968, - SpikeFiend: 2969, - QuillRat: 2970, - HellClan: 2971, - MoonClan: 2972, - NightClan: 2973, - DeathClan: 2974, - BloodClan: 2975, - TempleGuard: 2976, - DoomApe: 2977, - JungleHunter: 2978, - RockDweller: 2979, - DuneBeast: 2980, - FleshHunter: 2981, - BlackRogue: 2982, - DarkStalker: 2983, - VileHunter: 2984, - DarkHunter: 2985, - DarkShape: 2986, - Apparition: 2987, - Specter: 2988, - Wraith: 2989, - Ghost: 2990, - Assailant: 2991, - Infidel: 2992, - Invader: 2993, - Marauder: 2994, - SandRaider: 2995, - GargantuanBeast: 2996, - WailingBeast: 2997, - Yeti: 2998, - Crusher: 2999, - Brute: 3000, - CloudStalker: 3001, - BlackVulture: 3002, - BlackRaptor: 3003, - BloodHawk: 3004, - FoulCrow: 3005, - PlagueBearer: 3006, - Ghoul: 3007, - DrownedCarcass: 3008, - HungryDead: 3009, - Zombie: 3010, - Horror: 3012, - Returned: 3013, - BurningDead: 3014, - BoneWarrior: 3015, - Damned: 3016, - Disfigured: 3017, - Misshapen: 3018, - Tainted: 3019, - Afflicted: 3020, - Camel: 3033, - Cadaver: 3034, - PreservedDead: 3035, - Embalmed: 3036, - DriedCorpse: 3037, - Decayed: 3038, - Urdar: 3039, - Mauler: 3040, - Gorebelly: 3041, - Blunderbore: 3042, - BloodMaggotYoung: 3043, - GiantLampreyYoung: 3044, - DevourerYoung: 3045, - RockWormYoung: 3046, - SandMaggotYoung: 3047, - BloodMaggotEgg: 3048, - GiantLampreyEgg: 3049, - DevourerEgg: 3050, - RockWormEgg: 3051, - SandMaggotEgg: 3052, - Maggot: 3053, - BloodHawkNest: 3055, - FlyingScimitar: 3056, - CloudStalkerNest: 3057, - BlackRaptorNest: 3058, - FoulCrowNest: 3059, - Cantor: 3063, - Heirophant: 3064, - Sexton: 3065, - Zealot: 3066, - Faithful: 3067, - Zakarumite: 3068, - BlackSoul: 3069, - BurningSoul: 3070, - SwampGhost: 3071, - Gloam: 3072, - WarpedShaman: 3073, - DarkShaman: 3074, - DevilkinShaman: 3075, - CarverShaman: 3076, - FallenShaman: 3077, - WarpedOne: 3078, - DarkOne: 3079, - Devilkin: 3080, - Carver: 3081, - Fallen: 3082, - ReturnedArcher: 3083, - HorrorArcher: 3084, - BurningDeadArcher: 3085, - BoneArcher: 3086, - CorpseArcher: 3087, - SkeletonArcher: 3088, - FleshLancer: 3089, - BlackLancer: 3090, - DarkLancer: 3091, - VileLancer: 3092, - DarkSpearwoman: 3093, - FleshArcher: 3094, - BlackArcher: 3095, - DarkRanger: 3096, - VileArcher: 3097, - DarkArcher: 3098, - StygianDollShaman: 3100, - SoulKillerShaman: 3101, - FlayerShaman: 3102, - FetishShaman: 3103, - RatManShaman: 3104, - HorrorMage: 3105, - BurningDeadMage: 3106, - BoneMage: 3107, - CorpseMage: 3108, - ReturnedMage: 3109, - GargoyleTrap: 3110, - NightMarauder: 3121, - FireGolem: 3122, - IronGolem: 3123, - BloodGolem: 3124, - ClayGolem: 3125, - BloodMaggotQueen: 3126, - GiantLampreyQueen: 3127, - DevourerQueen: 3128, - RockWormQueen: 3129, - SandMaggotQueen: 3130, - SlimePrince: 3131, - BogCreature: 3132, - SwampDweller: 3133, - BarbedGiant: 3134, - RazorBeast: 3135, - ThornBrute: 3136, - SpikeGiant: 3137, - QuillBear: 3138, - CouncilMember: 3139, - DarkWanderer: 3141, - HellSlinger: 3142, - NightSlinger: 3143, - SpearCat: 3144, - Slinger: 3145, - FireTower: 3146, - LightningSpire: 3147, - PitLord: 3148, - Balrog: 3149, - VenomLord: 3150, - IronWolf: 3151, - InvisoSpawner: 3152, - OblivionKnight: 3153, - Mage: 3154, - AbyssKnight: 3155, - FighterMage: 3156, - DoomKnight: 3157, - Fighter: 3158, - MawFiend: 3159, - CorpseSpitter: 3160, - Corpulent: 3161, - StormCaster: 3162, - Strangler: 3163, - DoomCaster: 3164, - GrotesqueWyrm: 3165, - StygianDog: 3166, - FleshBeast: 3167, - Grotesque: 3168, - StygianHag: 3169, - FleshSpawner: 3170, - RogueScout: 3171, - BloodWingNest: 3172, - BloodHookNest: 3173, - FeederNest: 3174, - SuckerNest: 3175, - Hydra: 3325, - }, - npcs: { - Asheara: 1008, - Hratli: 1009, - Alkor: 1010, - Ormus: 1011, - Natalya: 1012, - Tyrael: 1013, - Izual1: 1014, - Izual2: 1015, - Jamella: 1016, - Halbu: 1017, - Hadriel: 1018, - Hazade: 1019, - Alhizeer: 1020, - Azrael: 1021, - Ahsab: 1022, - Chalan: 1023, - Haseen: 1024, - Razan: 1025, - Emilio: 1026, - Pratham: 1027, - Fazel: 1028, - Jemali: 1029, - Kasim: 1030, - Gulzar: 1031, - Mizan: 1032, - Leharas: 1033, - Durga: 1034, - Neeraj: 1035, - Ilzan: 1036, - Zanarhi: 1037, - Waheed: 1038, - Vikhyat: 1039, - Jelani: 1040, - Barani: 1041, - Jabari: 1042, - Devak: 1043, - Raldin: 1044, - Telash: 1045, - Ajheed: 1046, - Narphet: 1047, - Khaleel: 1048, - Phaet: 1049, - Geshef: 1050, - Vanji: 1051, - Haphet: 1052, - Thadar: 1053, - Yatiraj: 1054, - Rhadge: 1055, - Yashied: 1056, - Lharhad: 1057, - Flux: 1058, - Scorch: 1059, - //Natalya: 3022, both 1012 and 3022 return Natalya? - DeckardCain: 2890, - Gheed: 2891, - Akara: 2892, - Kashya: 2893, - Charsi: 2894, - Warriv: 2895, - Drognan: 3023, - Atma: 3024, - Fara: 3025, - Lysander: 3026, - Jerhyn: 3028, - Geglash: 3029, - Elzix: 3030, - Greiz: 3031, - Flavie: 3112, - Kaelan: 3113, - Meshif: 3114, - Larzuk: 22476, - Anya: 22477, - Malah: 22478, - Nihlathak1: 22479, - QualKehk: 22480, - Guard: 22481, - Combatant: 22482, - Nihlathak2: 22483, - }, - items: { - KhalimsFlail: 1060, - KhalimsWill1: 1061, - KhalimsFlail2: 1062, - KhalimsWill2: 1063, - KhalimsEye: 1064, - KhalimsBrain: 1065, - KhalimsHeart: 1066, - ScrollofInifuss: 2216, - KeytotheCairnStones: 2217, - AJadeFigurine: 2227, - TheGoldenBird: 2228, - LamEsensTome1: 2229, - LamEsensTome2: 2230, - HoradricCube: 2231, - HoradricScroll: 2232, - MephistosSoulstone: 2233, - Ear: 2235, - AmuletoftheViper: 2697, - StaffofKings: 2698, - HoradricStaff: 2699, + text: { + RepairCost: 3330, + SellValue: 3331, + IdentifyCost: 3332, + ItemCannotBeTradedHere: 3333, + TradeRepair: 3334, + Buy: 3335, + Sell: 3336, + Heal: 3337, + Repair: 3338, + NextPage: 3339, + PreviousPage: 3340, + Transmute: 3341, + YourGold: 3342, + WhichItemShouldBeImbued: 3343, + Yes: 3344, + No: 3345, + Gold2: 3346, + Sell2: 3347, + Buy2: 3358, + Hire: 3349, + ToStrength: 3473, + ToDexterity: 3474, + Defense: 3481, + Identify: 3350, + Repair2: 3351, + EnhancedDefense: 3520, + RealmGoingDownInXMinutes: 3651, + Strength: 4060, + Dexterity: 4062, + Vitality: 4066, + Energy: 4069, + DoNotMeetLevelReqForThisGame: 5162, + CdKeyDisabled: 5199, + CdKeyInUseBy: 5200, + OnlyOneInstanceAtATime: 5201, + CdKeyIntendedForAnotherProduct: 5202, + InvalidPassword: 5207, + AccountDoesNotExist: 5208, + AccountIsCorrupted: 5209, + AccountMustBeAtLeast: 5217, + AccountCantBeMoreThan: 5218, + PasswordMustBeAtLeast: 5219, + PasswordCantBeMoreThan: 5220, + LoginError: 5224, + UsernameMustBeAtLeast: 5231, + UsernameIncludedIllegalChars: 5232, + UsernameIncludedDisallowedwords: 5233, + AccountNameAlreadyExist: 5239, + UnableToIndentifyVersion: 5245, + UnableToCreateAccount: 5249, + CannotCreateGamesDeadHCChar: 5304, + Disconnected: 5347, + BattlenetNotResponding: 5353, + BattlenetNotResponding2: 5354, + HcCannotPlayWithSc: 5361, + ScCannotPlayWithHc: 5362, + CannotPlayInHellClassic: 5363, + CannotPlayInNightmareClassic: 5364, + EnhancedDamage: 10038, + ClassicCannotPlayWithXpac: 10101, + XpacCannotPlayWithClassic: 10102, + LoDKeyDisabled: 10913, + LodKeyInUseBy: 10914, + LoDKeyIntendedForAnotherProduct: 10915, + NonLadderCannotPlayWithLadder: 10929, + LadderCannotPlayWithNonLadder: 10930, + YourPositionInLineIs: 11026, + Gateway: 11049, + Ghostly: 11084, + Fanatic: 11085, + Possessed: 11086, + Berserker: 11087, + ExpiresIn: 11133, + CdKeyDisabledFromRealm: 11161, + CannotPlayInHellXpac: 21793, + CannotPlayInNightmareXpac: 21794, + }, - // Sets - // Angelic Rainment - AngelicsSword: 10172, - AngelicsArmor: 10173, - AngelicsRing: 10174, - AngelicsAmulet: 10175, - // Arcannas Tricks - ArcannasAmulet: 10180, - ArcannasStaff: 10181, - ArcannasHelmet: 10182, - ArcannasArmor: 10183, - // Artic Gear - ArticsBow: 10176, - ArticsArmor: 10177, - ArticsBelt: 10178, - ArticsGloves: 10179, - // Berserkers Gear - BerserkersHelmet: 10166, - BerserkersAxe: 10167, - BerserkersArmor: 10168, - // Cathans Traps - CathansRing: 10147, - CathansAmulet: 10148, - CathansHelmet: 10149, - CathansArmor: 10150, - CathansStaff: 10151, - // Civerbs Gear - CiverbsShield: 10122, - CiverbsAmulet: 10123, - CiverbsScepter: 10124, - // Clegaws Brace - ClegawsSword: 10128, - ClegawsShield: 10129, - ClegawsGloves: 10130, - // Deaths Disguise - DeathsGloves: 10169, - DeathsBelt: 10170, - DeathsSword: 10171, - // Hsarus Defense - HsarusBoots: 10125, - HsarusShield: 10126, - HsarusBelt: 10127, - // Infernal Tools - InfernalsHelmet: 10163, - InfernalsWand: 10164, - InfernalsBelt: 10165, - // Irathas Finery - IrathasBelt: 10131, - IrathasHelmet: 10132, - IrathasGloves: 10133, - IrathasAmulet: 10134, - // Isenharts Armory - IsenhartsHelmet: 10135, - IsenhartsArmor: 10136, - IsenhartsShield: 10137, - IsenhartsSword: 10138, - // Milabrega Regalia - MilabregasArmor: 10143, - MilabregasHelmet: 10144, - MilabregasScepter: 10145, - MilabregasShield: 10146, - // Sigons - SigonsHelmet: 10157, - SigonsArmor: 10158, - SigonsGloves: 10159, - SigonsBoots: 10160, - SigonsBelt: 10161, - SigonsShield: 10162, - // Tancreds - TancredsPick: 10152, - TancredsArmor: 10153, - TancredsBoots: 10154, - TancredsAmulet: 10155, - TancredsHelmet: 10156, - // Vidalas - VidalasAmulet: 10139, - VidalasArmor: 10140, - VidalasBoots: 10141, - VidalasBow: 10142, + areas: { + // Act 1 + RogueEncampment: 5055, + BloodMoor: 5054, + ColdPlains: 5053, + StonyField: 5052, + DarkWood: 5051, + BlackMarsh: 5050, + TamoeHighland: 5049, + DenofEvil: 5048, + CaveLvl1: 5047, + UndergroundPassageLvl1: 5046, + HoleLvl1: 5045, + PitLvl1: 5044, + CaveLvl2: 5043, + UndergroundPassageLvl2: 5042, + HoleLvl2: 5041, + PitLvl2: 5040, + BurialGrounds: 5039, + Crypt: 5038, + Mausoleum: 5037, + ForgottenTower: 5036, + TowerCellarLvl1: 5035, + TowerCellarLvl2: 5034, + TowerCellarLvl3: 5033, + TowerCellarLvl4: 5032, + TowerCellarLvl5: 5031, + MonasteryGate: 5030, + OuterCloister: 5029, + Barracks: 5038, + JailLvl1: 5027, + JailLvl2: 5026, + JailLvl3: 5025, + InnerCloister: 5024, + Cathedral: 5023, + CatacombsLvl1: 5022, + CatacombsLvl2: 5021, + CatacombsLvl3: 5020, + CatacombsLvl4: 5019, + Tristram: 5018, + MooMooFarm: 788, - // LoD Sets - // Aldurs's Legacy - AldursHelmet: 21697, - AldursArmor: 21698, - AldursBoots: 21700, - AldursMace: 21847, - // Bul-Kathos's Children - BulKathosBlade: 21688, - BulKathoSword: 21689, - // Cow Kings's Leathers - CowKingsHelmet: 21723, - CowKingsArmor: 21724, - CowKingsBoots: 21725, - // Disciples - DisciplesAmulet: 21717, - DisciplesGloves: 21718, - DisciplesBoots: 21719, - DisciplesArmor: 21720, - DisciplesBelt: 21721, - // Griswolds's Legacy - GriswoldsScepter: 21673, - GriswoldsShield: 21674, - GriswoldsArmor: 21675, - GriswoldsHelmet: 21676, - // Heaven's Brethren - HeavensMace: 21823, - HeavensHelmet: 21824, - HeavensShield: 21825, - HeavensArmor: 21826, - // Hwanin's - HwaninsHelmet: 21712, - HwaninsPolearm: 21713, - HwaninsArmor: 21714, - HwaninsBelt: 21821, - // IK - ImmortalKingsMaul: 21840, - ImmortalKingsBoots: 21841, - ImmortalKingsGloves: 21842, - ImmortalKingsBelt: 21843, - ImmortalKingsArmor: 21844, - ImmortalKingsHelmet: 21845, - // M'avina's - MavinasHelmet: 21702, - MavinasArmor: 21703, - MavinasGloves: 21704, - MavinasBelt: 21705, - MavinasBow: 21706, - // Natalya's - NatalyasHelmet: 21668, - NatalyasClaw: 21669, - NatalyasArmor: 21670, - NatalyasBoots: 21671, - // Naj's - NajsStaff: 21640, - NajsArmor: 21831, - NajsHelmet: 21832, - // Orphan's - OrphansHelmet: 21731, - OrphansBelt: 21732, - OrphansGloves: 21733, - OrphansShield: 21734, - // Sanders's - SandersGloves: 21876, - SandersBoots: 21877, - SandersHelmet: 21878, - SandersWand: 21879, - // Sazabi's - SazabisSword: 21708, - SazabisArmor: 21709, - SazabisHelmet: 21710, - // Tal - TalRashasBelt: 21816, - TalRashasAmulet: 21817, - TalRashasArmor: 21818, - TalRashasOrb: 21819, - TalRashasHelmet: 21820, - // Trang-Ouls - TrangOulsHelmet: 21661, - TrangOulsShield: 21662, - TrangOulsArmor: 21664, - TrangOulsGloves: 21665, - TrangOulsBelt: 21666, + // Act 2 + LutGholein: 852, + RockyWaste: 851, + DryHills: 850, + FarOasis: 849, + LostCity: 848, + ValleyofSnakes: 847, + CanyonofMagic: 846, + A2SewersLvl1: 845, + A2SewersLvl2: 844, + A2SewersLvl3: 843, + HaremLvl1: 842, + HaremLvl2: 841, + PalaceCellarLvl1: 840, + PalaceCellarLvl2: 839, + PalaceCellarLvl3: 838, + StonyTombLvl1: 837, + HallsoftheDeadLvl1: 836, + HallsoftheDeadLvl2: 835, + ClawViperTempleLvl1: 834, + StonyTombLvl2: 833, + HallsoftheDeadLvl3: 832, + ClawViperTempleLvl2: 831, + MaggotLairLvl1: 830, + MaggotLairLvl2: 829, + MaggotLairLvl3: 828, + AncientTunnels: 827, + TalRashasTomb1: 826, + TalRashasTomb2: 826, + TalRashasTomb3: 826, + TalRashasTomb4: 826, + TalRashasTomb5: 826, + TalRashasTomb6: 826, + TalRashasTomb7: 826, + DurielsLair: 825, + ArcaneSanctuary: 824, - // Uniques - // Quest/Misc - KeyofTerror: 11146, - KeyofHate: 11147, - KeyofDestruction: 11148, - DiablosHorn: 11149, - BaalsEye: 11150, - MephistosBrain: 11151, - StandardofHeroes: 11152, - HellfireTorch: 11153, - Annihilus: 21743, + // Act 3 + KurastDocktown: 820, + SpiderForest: 819, + GreatMarsh: 818, + FlayerJungle: 817, + LowerKurast: 816, + KurastBazaar: 815, + UpperKurast: 814, + KurastCauseway: 813, + Travincal: 812, + SpiderCave: 810, + SpiderCavern: 811, + SwampyPitLvl1: 809, + SwampyPitLvl2: 808, + FlayerDungeonLvl1: 806, + FlayerDungeonLvl2: 805, + SwampyPitLvl3: 807, + FlayerDungeonLvl3: 804, + A3SewersLvl1: 845, + A3SewersLvl2: 844, + RuinedTemple: 803, + DisusedFane: 802, + ForgottenReliquary: 801, + ForgottenTemple: 800, + RuinedFane: 799, + DisusedReliquary: 798, + DuranceofHateLvl1: 797, + DuranceofHateLvl2: 796, + DuranceofHateLvl3: 795, - // Unique Items - WitchwildString: 10911, - TitansRevenge: 21735, - LycandersAim: 21737, - ArreatsFace: 21744, - Homunculus: 21755, - JalalsMane: 21750, - HeraldofZakarum: 21758, - BloodRavensCharge: 21508, - Gimmershred: 21637, - MedusasGaze: 21516, - Rockstopper: 21519, - CrownofThieves: 21522, - BlackhornsFace: 21523, - TheSpiritShroud: 21524, - SkinoftheFlayedOne: 21525, - IronPelt: 21526, - SpiritForge: 21527, - CrowCaw: 21528, - DurielsShell: 21529, - SkulldersIre: 21530, - Toothrow: 21531, - AtmasWail: 21532, - BlackHades: 21533, - Corpsemourn: 21534, - QueHegans: 21535, - QueHegansWisdom: 21535, - Mosers: 21536, - MosersBlessedCircle: 21536, - Stormchaser: 21537, - TiamatsRubuke: 21538, - GerkesSanctuary: 21539, - RadamentsSphere: 21540, - Gravepalm: 21541, - Ghoulhide: 21542, - Hellmouth: 21543, - Infernostride: 21544, - Waterwalk: 21545, - Silkweave: 21546, - WarTraveler: 21547, - Razortail: 21548, - GloomsTrap: 21549, - Snowclash: 21550, - ThundergodsVigor: 21551, - LidlessWall: 21552, - LanceGuard: 21553, - Boneflame: 21555, - SteelPillar: 21556, - NightwingsVeil: 21557, - CrownofAges: 21559, - AndarielsVisage: 21560, - Dragonscale: 21562, - SteelCarapace: 21563, - RainbowFacet: 21565, - Ravenlore: 21566, - Boneshade: 21567, - Flamebellow: 21570, - DeathsFathom: 21571, - Wolfhowl: 21572, - SpiritWard: 21573, - KirasGuardian: 21574, - OrmusRobe: 21575, - GheedsFortune: 21576, - HalberdsReign: 21579, - DraculsGrasp: 21583, - Frostwind: 21584, - TemplarsMight: 21585, - EschutasTemper: 21586, // also 21620? - FirelizardsTalons: 21587, - SandstormTrek: 21588, - Marrowwalk: 21589, - HeavensLight: 21590, - ArachnidMesh: 21592, - NosferatusCoil: 21593, - Verdungos: 21595, - VerdungosHeartyCord: 21595, - CarrionWind: 21597, - GiantSkull: 21598, - AstreonsIronWard: 21599, - SaracensChance: 21608, - HighlordsWrath: 21609, - Ravenfrost: 21610, - Dwarfstar: 21611, - AtmasScarab: 21612, - Maras: 21613, - MarasKaleidoscope: 21613, - CrescentMoonAmulet: 21614, - TheRisingSun: 21615, - TheCatsEye: 21616, - BulKathosWeddingBand: 21617, - Metalgrid: 21619, - Stormshield: 21621, - BlackoakShield: 21622, - ArkainesValor: 21624, - TheGladiatorsBane: 21625, - HarlequinsCrest: 21627, - GuardianAngel: 21632, - TheGrandfather: 21643, - Doombringer: 21644, - TyraelsMight: 21645, - Lightsabre: 21646, - TheCraniumBasher: 21647, - DeathsWeb: 21650, - TheAtlantean: 21654, - CarinShard: 21658, - Coldkill: 21286, - ButchersCleaver: 21287, - Islestrike: 21289, - GuardianNaga: 21291, - SpellSteel: 21293, - SuicideBranch: 21297, - ArmofKingLeoric: 21299, - BlackhandKey: 21300, - DarkClanCrusher: 21301, - TheFetidSprinkler: 21304, - HandofBlessedLight: 21305, - Fleshrender: 21306, - SureshrillFrost: 21307, - Moonfall: 21308, - BaezilsVortex: 21309, - Earthshaker: 21310, - TheGavelofPain: 21312, - Bloodletter: 21313, - ColdstealEye: 21314, - Hexfire: 21315, - BladeofAliBaba: 21316, - Riftslash: 21317, - Headstriker: 21318, - PlagueBearer: 21319, - //TheAtlantean: 21320, - CrainteVomir: 21321, - BingSzWang: 21322, - TheVileHusk: 21323, - Cloudcrack: 21324, - TodesfaelleFlamme: 21325, - Swordguard: 21326, - Spineripper: 21327, - HeartCarver: 21328, - BlackbogsSharp: 21329, - Stormspike: 21330, - TheImpaler: 21331, - HoneSudan: 21334, - SpireofHonor: 21335, - TheMeatScraper: 21336, - BlackleachBlade: 21337, - AthenasWrath: 21338, - PierreTombaleCouant: 21339, - GrimsBurningDead: 21341, - Ribcracker: 21342, - ChromaticIre: 21343, - Warspear: 21344, - SkullCollector: 21345, - Skystrike: 21346, - //WitchwildString: 21349, - GoldstrikeArch: 21350, - PusSpitter: 21352, - VampireGaze: 21354, - StringofEars: 21355, - GoreRider: 21356, - LavaGout: 21357, - VenomGrip: 21358, - Visceratuant: 21359, - //GuardianAngel: 21360, - Shaftstop: 21361, - SkinofVipermagi: 21362, - Blackhorn: 21363, - ValkyrieWing: 21364, - PeasantCrown: 21365, - DemonMachine: 21366, - Riphook: 21369, - Razorswitch: 21370, - OndalsWisdom: 21375, - Deathbit: 21379, - Warshrike: 21380, - DemonLimb: 21387, - SteelShade: 21388, - TombReaver: 21389, - //DeathsWeb: 21390, - AngelsSong: 21393, - TheRedeemer: 21394, - Bonehew: 21398, - Steelrend: 21399, - AriocsNeedle: 21402, - SoulDrainer: 21407, - RuneMaster: 21408, - DeathCleaver: 21409, - ExecutionersJustice: 21410, - Leviathan: 21412, - WispProjector: 21417, - Lacerator: 21419, - MangSongsLesson: 21420, - Viperfork: 21421, - TheReapersToll: 21427, - SpiritKeeper: 21428, - Hellrack: 21429, - AlmaNegra: 21430, - DarkforceSpawn: 21431, - Ghostflame: 21438, - ShadowKiller: 21439, - GriffonsEye: 21442, - Thunderstroke: 21445, - DemonsArch: 21447, - DjinnSlayer: 21450, + // Act 4 + PandemoniumFortress: 790, + OuterSteppes: 792, + PlainsofDespair: 793, + CityoftheDamned: 794, + RiverofFlame: 791, + ChaosSanctuary: 789, - // Runewords - AncientsPledge: 20507, - Armageddon: 20508, - Authority: 20509, - Beast: 20510, - Beauty: 20511, - Black: 20512, - Blood: 20513, - Bone: 20514, - Bramble: 20515, - Brand: 20516, - BreathoftheDying: 20517, - BrokenPromise: 20518, - CalltoArms: 20519, - ChainsofHonor: 20520, - Chance: 20521, - Chaos: 20522, - CrescentMoon: 20523, - Darkness: 20524, - Daylight: 20525, - Death: 20526, - Deception: 20527, - Delerium: 20528, - Desire: 20529, - Despair: 20530, - Destruction: 20531, - Doom: 20532, - Dragon: 20533, - Dread: 20534, - Dream: 20535, - Duress: 20536, - Edge: 20537, - Elation: 20538, - Enigma: 20539, - Enlightenment: 20540, - Envy: 20541, - Eternity: 20542, - Exile: 20543, - Faith: 20544, - Famine: 20545, - Flame: 20546, - Fortitude: 20547, - Fortune: 20548, - Friendship: 20549, - Fury: 20550, - Gloom: 20551, - Grief: 20553, - HandofJustice: 20554, - Harmony: 20555, - HeartoftheOak: 20557, - HolyThunder: 20560, - Honor: 20561, - Revenge: 20562, - Humility: 20563, - Hunger: 20564, - Ice: 20565, - Infinity: 20566, - Innocence: 20567, - Insight: 20568, - Jealousy: 20569, - Judgement: 20570, - KingsGrace: 20571, - Kingslayer: 20572, - KnightsVigil: 20573, - Knowledge: 20574, - LastWish: 20575, - Law: 20576, - Lawbringer: 20577, - Leaf: 20578, - Lightning: 20579, - Lionheart: 20580, - Lore: 20581, - Love: 20582, - Loyalty: 20583, - Lust: 20584, - Madness: 20585, - Malice: 20586, - Melody: 20587, - Memory: 20588, - Mist: 20589, - Morning: 20590, - Mystery: 20591, - Myth: 20592, - Nadir: 20593, - NaturesKingdom: 20594, - Night: 20595, - Oath: 20596, - Obedience: 20597, - Oblivion: 20598, - Obsession: 20599, - Passion: 20600, - Patience: 20601, - Patter: 20602, - Peace: 20603, - VoiceofReason: 20604, - Penitence: 20605, - Peril: 20606, - Pestilence: 20607, - Phoenix: 20608, - Piety: 20609, - PillarofFaith: 20610, - Plague: 20611, - Praise: 20612, - Prayer: 20613, - Pride: 20614, - Principle: 20615, - ProwessinBattle: 20616, - Prudence: 20617, - Punishment: 20618, - Purity: 20619, - Question: 20620, - Radiance: 20621, - Rain: 20622, - Reason: 20623, - Red: 20624, - Rhyme: 20625, - Rift: 20626, - Sanctuary: 20627, - Serendipity: 20628, - Shadow: 20629, - ShadowofDoubt: 20630, - Silence: 20631, - SirensSong: 20632, - Smoke: 20633, - Sorrow: 20634, - Spirit: 20635, - Splendor: 20636, - Starlight: 20637, - Stealth: 20638, - Steel: 20639, - StillWater: 20640, - Sting: 20641, - Stone: 20642, - Storm: 20643, - Strength: 20644, - Tempest: 20645, - Temptation: 20646, - Terror: 20647, - Thirst: 20648, - Thought: 20649, - Thunder: 20650, - Time: 20651, - Tradition: 20652, - Treachery: 20653, - Trust: 20654, - Truth: 20655, - UnbendingWill: 20656, - Valor: 20657, - Vengeance: 20658, - Venom: 20659, - Victory: 20660, - Voice: 20661, - Void: 20662, - War: 20663, - Water: 20664, - Wealth: 20665, - Whisper: 20666, - White: 20667, - Wind: 20668, - WingsofHope: 20669, - Wisdom: 20670, - Woe: 20671, - Wonder: 20672, - Wrath: 20673, - Youth: 20674, - Zephyr: 20675, - }, - dialog: { - youDoNotHaveEnoughGoldForThat: 3362 - }, + // Act 5 + Harrogath: 22646, + BloodyFoothills: 22647, + FrigidHighlands: 22648, + ArreatPlateau: 22649, + CrystalizedPassage: 22650, + FrozenRiver: 22651, + GlacialTrail: 22652, + DrifterCavern: 22653, + FrozenTundra: 22654, + AncientsWay: 22655, + IcyCellar: 22656, + ArreatSummit: 22657, + NihlathaksTemple: 22658, + HallsofAnguish: 22659, + HallsofPain: 22660, + HallsofVaught: 22662, + Abaddon: 21865, + PitofAcheron: 21866, + InfernalPit: 21867, + WorldstoneLvl1: 22663, + WorldstoneLvl2: 22664, + WorldstoneLvl3: 22665, + ThroneofDestruction: 22667, + WorldstoneChamber: 22666, - text: { - RepairCost: 3330, - SellValue: 3331, - IdentifyCost: 3332, - ItemCannotBeTradedHere: 3333, - TradeRepair: 3334, - Buy: 3335, - Sell: 3336, - Heal: 3337, - Repair: 3338, - NextPage: 3339, - PreviousPage: 3340, - Transmute: 3341, - YourGold: 3342, - WhichItemShouldBeImbued: 3343, - Yes: 3344, - No: 3345, - Gold2: 3346, - Sell2: 3347, - Buy2: 3358, - Hire: 3349, - ToStrength: 3473, - ToDexterity: 3474, - Defense: 3481, - Identify: 3350, - Repair2: 3351, - EnhancedDefense: 3520, - Strength: 4060, - Dexterity: 4062, - Vitality: 4066, - Energy: 4069, - DoNotMeetLevelReqForThisGame: 5162, - CdKeyDisabled: 5199, - OnlyOneInstanceAtATime: 5201, - CdKeyIntendedForAnotherProduct: 5202, - InvalidPassword: 5207, - AccountDoesNotExist: 5208, - AccountIsCorrupted: 5209, - AccountMustBeAtLeast: 5217, - AccountCantBeMoreThan: 5218, - PasswordMustBeAtLeast: 5219, - PasswordCantBeMoreThan: 5220, - LoginError: 5224, - UsernameMustBeAtLeast: 5231, - UsernameIncludedIllegalChars: 5232, - UsernameIncludedDisallowedwords: 5233, - AccountNameAlreadyExist: 5239, - UnableToCreateAccount: 5249, - Disconnected: 5347, - UnableToIndentifyVersion: 5245, - EnhancedDamage: 10038, - LoDKeyDisabled: 10913, - CdKeyInUseBy: 10914, - LoDKeyIntendedForAnotherProduct: 10915, - YourPositionInLineIs: 11026, - Gateway: 11049, - Ghostly: 11084, - Fanatic: 11085, - Possessed: 11086, - Berserker: 11087, - ExpiresIn: 11133, - CdKeyDisabledFromRealm: 11161, - }, - }, + // Ubers + MatronsDen: 5389, + ForgottenSands: 5389, + FurnaceofPain: 5389, + UberTristram: 5018, + }, + }, - game: { - profiletype: { - SinglePlayer: 1, - Battlenet: 2, - OpenBattlenet: 3, - TcpIpHost: 4, - TcpIpJoin: 5 - }, + game: { + profiletype: { + SinglePlayer: 1, + Battlenet: 2, + OpenBattlenet: 3, + TcpIpHost: 4, + TcpIpJoin: 5 + }, - controls: { - Disabled: 4, - }, + controls: { + Disabled: 4, + }, - gametype: { - Classic: 0, - Expansion: 1, - }, + gametype: { + Classic: 0, + Expansion: 1, + }, - // out of game locations - locations: { - PreSplash: 0, - Lobby: 1, - WaitingInLine: 2, - LobbyChat: 3, - CreateGame: 4, - JoinGame: 5, - Ladder: 6, - ChannelList: 7, - MainMenu: 8, - Login: 9, - LoginError: 10, - LoginUnableToConnect: 11, - CharSelect: 12, - RealmDown: 13, - Disconnected: 14, - NewCharSelected: 15, - CharSelectPleaseWait: 16, - LobbyLostConnection: 17, - SplashScreen: 18, - CdKeyInUse: 19, - SelectDifficultySP: 20, - MainMenuConnecting: 21, - InvalidCdKey: 22, - CharSelectConnecting: 23, - ServerDown: 24, - LobbyPleaseWait: 25, - GameNameExists: 26, - GatewaySelect: 27, - GameDoesNotExist: 28, - CharacterCreate: 29, - OkCenteredErrorPopUp: 30, - TermsOfUse: 31, - CreateNewAccount: 32, - PleaseRead: 33, - RegisterEmail: 34, - Credits: 35, - Cinematics: 36, - CharChangeRealm: 37, - GameIsFull: 38, - OtherMultiplayer: 39, - TcpIp: 40, - TcpIpEnterIp: 41, - CharSelectNoChars: 42, - CharSelectChangeRealm: 43, - TcpIpUnableToConnect: 44, - }, - }, + // out of game locations + locations: { + PreSplash: 0, + Lobby: 1, + WaitingInLine: 2, + LobbyChat: 3, + CreateGame: 4, + JoinGame: 5, + Ladder: 6, + ChannelList: 7, + MainMenu: 8, + Login: 9, + LoginError: 10, + LoginUnableToConnect: 11, + CharSelect: 12, + RealmDown: 13, + Disconnected: 14, + NewCharSelected: 15, + CharSelectPleaseWait: 16, + LobbyLostConnection: 17, + SplashScreen: 18, + CdKeyInUse: 19, + SelectDifficultySP: 20, + MainMenuConnecting: 21, + InvalidCdKey: 22, + CharSelectConnecting: 23, + ServerDown: 24, + LobbyPleaseWait: 25, + GameNameExists: 26, + GatewaySelect: 27, + GameDoesNotExist: 28, + CharacterCreate: 29, + OkCenteredErrorPopUp: 30, + TermsOfUse: 31, + CreateNewAccount: 32, + PleaseRead: 33, + RegisterEmail: 34, + Credits: 35, + Cinematics: 36, + CharChangeRealm: 37, + GameIsFull: 38, + OtherMultiplayer: 39, + TcpIp: 40, + TcpIpEnterIp: 41, + CharSelectNoChars: 42, + CharSelectChangeRealm: 43, + TcpIpUnableToConnect: 44, + }, + }, - colors: { - White: "ÿc0", - Red: "ÿc1", - NeonGreen: "ÿc2", - Blue: "ÿc3", - DarkGold: "ÿc4", - Gray: "ÿc5", - Black: "ÿc6", - LightGold: "ÿc7", - Orange: "ÿc8", - Yellow: "ÿc9", - DarkGreen: "ÿc:", - Purple: "ÿc;", - Green: "ÿc<", - D2Bot: { - Black: 0, - Blue: 4, - Green: 5, - Gold: 6, - DarkGold: 7, - Orange: 8, - Red: 9, - Gray: 10 - } - }, + colors: { + White: "ÿc0", + Red: "ÿc1", + NeonGreen: "ÿc2", + Blue: "ÿc3", + DarkGold: "ÿc4", + Gray: "ÿc5", + Black: "ÿc6", + LightGold: "ÿc7", + Orange: "ÿc8", + Yellow: "ÿc9", + DarkGreen: "ÿc:", + Purple: "ÿc;", + Green: "ÿc<", + D2Bot: { + Black: 0, + Blue: 4, + Green: 5, + Gold: 6, + DarkGold: 7, + Orange: 8, + Red: 9, + Gray: 10 + } + }, - keys: { - Backspace: 8, - Tab: 9, - Enter: 13, - Shift: 16, - Ctrl: 17, - Alt: 18, - PauseBreak: 19, - CapsLock: 20, - Escape: 27, - Spacebar: 32, - PageUp: 33, - PageDown: 34, - End: 35, - Home: 36, - LeftArrow: 37, - UpArrow: 38, - RightArrow: 39, - DownArrow: 40, - Insert: 45, - Delete: 46, - Zero: 48, - One: 49, - Two: 50, - Three: 51, - Four: 52, - Five: 53, - Six: 54, - Seven: 55, - Eight: 56, - Nine: 57, - LeftWindowKey: 91, - RightWindowKey: 92, - SelectKey: 93, - Numpad0: 96, - Numpad1: 97, - Numpad2: 98, - Numpad3: 99, - Numpad4: 100, - Numpad5: 101, - Numpad6: 102, - Numpad7: 103, - Numpad8: 104, - Numpad9: 105, - NumpadStar: 106, - NumpadPlus: 107, - NumpadDash: 109, - NumpadDecimal: 110, - NumpadSlash: 111, - F1: 112, - F2: 113, - F3: 114, - F4: 115, - F5: 116, - F6: 117, - F7: 118, - F8: 119, - F9: 120, - F10: 121, - F11: 122, - F12: 123, - NumLock: 144, - ScrollLock: 145, - SemiColon: 186, - EqualSign: 187, - Comma: 188, - Dash: 189, - Period: 190, - ForwardSlash: 191, - GraveAccent: 192, - OpenBracket: 219, - BackSlash: 220, - CloseBracket: 221, - SingleQuote: 222, - code: { - Backspace: 0x08, - Tab: 0x09, - Clear: 0x0C, - Enter: 0x0D, - Shift: 0x10, - Ctrl: 0x11, - Alt: 0x12, - PauseBreak: 0x13, - CapsLock: 0x14, - Esc: 0x1B, - Space: 0x20, - PageUp: 0x21, - PageDown: 0x22, - End: 0x23, - Home: 0x24, - LeftArrow: 0x25, - UpArrow: 0x26, - RightArrow: 0x27, - DownArrow: 0x28, - Select: 0x29, - Print: 0x2A, - PrintScreen: 0x2C, - Insert: 0x2D, - Delete: 0x2E, - } - }, + keys: { + Backspace: 8, + Tab: 9, + Enter: 13, + Shift: 16, + Ctrl: 17, + Alt: 18, + PauseBreak: 19, + CapsLock: 20, + Escape: 27, + Spacebar: 32, + PageUp: 33, + PageDown: 34, + End: 35, + Home: 36, + LeftArrow: 37, + UpArrow: 38, + RightArrow: 39, + DownArrow: 40, + Insert: 45, + Delete: 46, + Zero: 48, + One: 49, + Two: 50, + Three: 51, + Four: 52, + Five: 53, + Six: 54, + Seven: 55, + Eight: 56, + Nine: 57, + LeftWindowKey: 91, + RightWindowKey: 92, + SelectKey: 93, + Numpad0: 96, + Numpad1: 97, + Numpad2: 98, + Numpad3: 99, + Numpad4: 100, + Numpad5: 101, + Numpad6: 102, + Numpad7: 103, + Numpad8: 104, + Numpad9: 105, + NumpadStar: 106, + NumpadPlus: 107, + NumpadDash: 109, + NumpadDecimal: 110, + NumpadSlash: 111, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + NumLock: 144, + ScrollLock: 145, + SemiColon: 186, + EqualSign: 187, + Comma: 188, + Dash: 189, + Period: 190, + ForwardSlash: 191, + GraveAccent: 192, + OpenBracket: 219, + BackSlash: 220, + CloseBracket: 221, + SingleQuote: 222, + code: { + Backspace: 0x08, + Tab: 0x09, + Clear: 0x0C, + Enter: 0x0D, + Shift: 0x10, + Ctrl: 0x11, + Alt: 0x12, + PauseBreak: 0x13, + CapsLock: 0x14, + Esc: 0x1B, + Space: 0x20, + PageUp: 0x21, + PageDown: 0x22, + End: 0x23, + Home: 0x24, + LeftArrow: 0x25, + UpArrow: 0x26, + RightArrow: 0x27, + DownArrow: 0x28, + Select: 0x29, + Print: 0x2A, + PrintScreen: 0x2C, + Insert: 0x2D, + Delete: 0x2E, + } + }, - controls: { - TextBox: 1, - Image1: 2, - Image2: 3, - LabelBox: 4, - ScrollBar: 5, - Button: 6, - List: 7, - Timer: 8, - Smack: 9, - ProgressBar: 10, - Popup: 11, - AccountList: 12 - }, + controls: { + TextBox: 1, + Image1: 2, + Image2: 3, + LabelBox: 4, + ScrollBar: 5, + Button: 6, + List: 7, + Timer: 8, + Smack: 9, + ProgressBar: 10, + Popup: 11, + AccountList: 12 + }, - packets: { - send: { - WalkToLocation: 0x01, - WalkToEntity: 0x02, - RunToLocation: 0x03, - RunToEntity: 0x04, - LeftSkillOnLocation: 0x05, - LeftSkillOnEntity: 0x06, - LeftSkillOnEntityEx: 0x07, - LeftSkillOnLocationEx: 0x08, - LeftSkillOnEntityEx2: 0x09, - LeftSkillOnEntityEx3: 0x0A, - RightSkillOnLocation: 0x0C, - RightSkillOnEntity: 0x0D, - RightSkillOnEntityEx: 0x0E, - RightSkillOnLocationEx: 0x0F, - RightSkillOnEntityEx2: 0x10, - RightSkillOnEntityEx3: 0x11, - SetInfernoState: 0x12, - InteractWithEntity: 0x13, - OverheadMessage: 0x14, - Chat: 0x15, - PickupItem: 0x16, - DropItem: 0x17, - ItemToBuffer: 0x18, - PickupBufferItem: 0x19, - ItemToBody: 0x1A, - Swap2HandedItem: 0x1B, - PickupBodyItem: 0x1C, - SwitchBodyItem: 0x1D, - Switch1HandWith2Hand: 0x1E, - SwitchInventoryItem: 0x1F, - UseItem: 0x20, - StackItem: 0x21, - RemoveStackItem: 0x22, - ItemToBelt: 0x23, - RemoveBeltItem: 0x24, - SwitchBeltItem: 0x25, - UseBeltItem: 0x26, - IndentifyItem: 0x27, - InsertSocketItem: 0x28, - ScrollToMe: 0x29, - ItemToCube: 0x2A, - NPCInit: 0x2F, - NPCCancel: 0x30, - QuestMessage: 0x31, - NPCBuy: 0x32, - NPCSell: 0x33, - NPCIndentifyItems: 0x34, - Repair: 0x35, - HireMerc: 0x36, - IndentifyGamble: 0x37, - EntityAction: 0x38, - AddStat: 0x3A, - AddSkill: 0x3B, - SelectSkill: 0x3C, - ActivateItem: 0x3E, - CharacterPhrase: 0x3F, - UpdateQuests: 0x40, - Resurrect: 0x41, - StaffInOrifice: 0x44, - MercInteract: 0x46, - MercMove: 0x47, - BusyStateOff: 0x48, - Waypoint: 0x49, - RequestEntityUpdate: 0x4B, - Transmorgify: 0x4C, - PlayNPCMessage: 0x4D, - ClickButton: 0x4F, - DropGold: 0x50, - BindHotkey: 0x51, - StaminaOn: 0x53, - StaminaOff: 0x54, - QuestCompleted: 0x58, - MakeEntityMove: 0x59, - SquelchHostile: 0x5D, - Party: 0x5E, - UpdatePlayerPos: 0x5F, - SwapWeapon: 0x60, - MercItem: 0x61, - MercRessurect: 0x62, - LeaveGame: 0x69, - }, - recv: { - GameExit: 0x06, - MapReveal: 0x07, - MapHide: 0x08, - ReassignPlayer: 0x15, - SetSkill: 0x23, - Chat: 0x26, - WeaponSwitch: 0x97, - } - } - }; + packets: { + send: { + WalkToLocation: 0x01, + WalkToEntity: 0x02, + RunToLocation: 0x03, + RunToEntity: 0x04, + LeftSkillOnLocation: 0x05, + LeftSkillOnEntity: 0x06, + LeftSkillOnEntityEx: 0x07, + LeftSkillOnLocationEx: 0x08, + LeftSkillOnEntityEx2: 0x09, + LeftSkillOnEntityEx3: 0x0A, + RightSkillOnLocation: 0x0C, + RightSkillOnEntity: 0x0D, + RightSkillOnEntityEx: 0x0E, + RightSkillOnLocationEx: 0x0F, + RightSkillOnEntityEx2: 0x10, + RightSkillOnEntityEx3: 0x11, + SetInfernoState: 0x12, + InteractWithEntity: 0x13, + OverheadMessage: 0x14, + Chat: 0x15, + PickupItem: 0x16, + DropItem: 0x17, + ItemToBuffer: 0x18, + PickupBufferItem: 0x19, + ItemToBody: 0x1A, + Swap2HandedItem: 0x1B, + PickupBodyItem: 0x1C, + SwitchBodyItem: 0x1D, + Switch1HandWith2Hand: 0x1E, + SwitchInventoryItem: 0x1F, + UseItem: 0x20, + StackItem: 0x21, + RemoveStackItem: 0x22, + ItemToBelt: 0x23, + RemoveBeltItem: 0x24, + SwitchBeltItem: 0x25, + UseBeltItem: 0x26, + IndentifyItem: 0x27, + InsertSocketItem: 0x28, + ScrollToMe: 0x29, + ItemToCube: 0x2A, + NPCInit: 0x2F, + NPCCancel: 0x30, + QuestMessage: 0x31, + NPCBuy: 0x32, + NPCSell: 0x33, + NPCIndentifyItems: 0x34, + Repair: 0x35, + HireMerc: 0x36, + IndentifyGamble: 0x37, + EntityAction: 0x38, + AddStat: 0x3A, + AddSkill: 0x3B, + SelectSkill: 0x3C, + ActivateItem: 0x3E, + CharacterPhrase: 0x3F, + UpdateQuests: 0x40, + Resurrect: 0x41, + StaffInOrifice: 0x44, + MercInteract: 0x46, + MercMove: 0x47, + BusyStateOff: 0x48, + Waypoint: 0x49, + RequestEntityUpdate: 0x4B, + Transmorgify: 0x4C, + PlayNPCMessage: 0x4D, + ClickButton: 0x4F, + DropGold: 0x50, + BindHotkey: 0x51, + StaminaOn: 0x53, + StaminaOff: 0x54, + QuestCompleted: 0x58, + MakeEntityMove: 0x59, + SquelchHostile: 0x5D, + Party: 0x5E, + UpdatePlayerPos: 0x5F, + SwapWeapon: 0x60, + MercItem: 0x61, + MercRessurect: 0x62, + LeaveGame: 0x69, + }, + recv: { + GameExit: 0x06, + MapReveal: 0x07, + MapHide: 0x08, + ReassignPlayer: 0x15, + SetSkill: 0x23, + Chat: 0x26, + UniqueEvents: 0x89, + WeaponSwitch: 0x97, + } + } + }; - // Need to be set after its loaded - sdk.skillTabs = { - amazon: { - bowandcrossbow: { - id: 0, - skills: [sdk.skills.MagicArrow, sdk.skills.FireArrow, sdk.skills.MultipleShot, sdk.skills.ExplodingArrow, sdk.skills.IceArrow, sdk.skills.GuidedArrow, sdk.skills.ImmolationArrow, sdk.skills.Strafe], - }, - passiveandmagic: { - id: 1, - skills: [sdk.skills.InnerSight, sdk.skills.CriticalStrike, sdk.skills.Dodge, sdk.skills.SlowMissiles, sdk.skills.Avoid, sdk.skills.Penetrate, sdk.skills.Dopplezon, sdk.skills.Evade, sdk.skills.Valkyrie, sdk.skills.Pierce], - }, - javelinandspear: { - id: 2, - skills: [sdk.skills.Jab, sdk.skills.PowerStrike, sdk.skills.PoisonJavelin, sdk.skills.Impale, sdk.skills.LightningBolt, sdk.skills.ChargedStrike, sdk.skills.PlagueJavelin, sdk.skills.Fend, sdk.skills.LightningStrike, sdk.skills.LightningFury], - }, - }, - sorc: { - fire: { - id: 8, - skills: [sdk.skills.FireBolt, sdk.skills.Warmth, sdk.skills.Inferno, sdk.skills.Blaze, sdk.skills.FireBall, sdk.skills.FireWall, sdk.skills.Enchant, sdk.skills.Meteor, sdk.skills.FireMastery, sdk.skills.Hydra], - }, - lightning: { - id: 9, - skills: [sdk.skills.ChargedBolt, sdk.skills.StaticField, sdk.skills.Telekinesis, sdk.skills.Nova, sdk.skills.Lightning, sdk.skills.ChainLightning, sdk.skills.Teleport, sdk.skills.ThunderStorm, sdk.skills.EnergyShield, sdk.skills.LightningMastery], - }, - cold: { - id: 10, - skills: [sdk.skills.IceBolt, sdk.skills.FrozenArmor, sdk.skills.FrostNova, sdk.skills.IceBlast, sdk.skills.ShiverArmor, sdk.skills.GlacialSpike, sdk.skills.Blizzard, sdk.skills.ChillingArmor, sdk.skills.FrozenOrb, sdk.skills.ColdMastery] - } - }, - necro: { - curse: { - id: 16, - skills: [sdk.skills.AmplifyDamage, sdk.skills.DimVision, sdk.skills.Weaken, sdk.skills.IronMaiden, sdk.skills.Terror, sdk.skills.Confuse, sdk.skills.LifeTap, sdk.skills.Attract, sdk.skills.Decrepify, sdk.skills.LowerResist], - }, - poisonandbone: { - id: 17, - skills: [sdk.skills.Teeth, sdk.skills.BoneArmor, sdk.skills.PoisonDagger, sdk.skills.CorpseExplosion, sdk.skills.BoneWall, sdk.skills.PoisonExplosion, sdk.skills.BoneSpear, sdk.skills.BonePrison, sdk.skills.PoisonNova, sdk.skills.BoneSpirit], - }, - summoning: { - id: 18, - skills: [sdk.skills.SkeletonMastery, sdk.skills.RaiseSkeleton, sdk.skills.ClayGolem, sdk.skills.GolemMastery, sdk.skills.RaiseSkeletalMage, sdk.skills.BloodGolem, sdk.skills.SummonResist, sdk.skills.IronGolem, sdk.skills.FireGolem, sdk.skills.Revive] - } - }, - paladin: { - combat: { - id: 24, - skills: [sdk.skills.Sacrifice, sdk.skills.Smite, sdk.skills.HolyBolt, sdk.skills.Zeal, sdk.skills.Charge, sdk.skills.Vengeance, sdk.skills.BlessedHammer, sdk.skills.Conversion, sdk.skills.HolyShield, sdk.skills.FistoftheHeavens], - }, - offensiveaura: { - id: 25, - skills: [sdk.skills.Might, sdk.skills.HolyFire, sdk.skills.Thorns, sdk.skills.BlessedAim, sdk.skills.Concentration, sdk.skills.HolyFreeze, sdk.skills.HolyShock, sdk.skills.Sanctuary, sdk.skills.Fanaticism, sdk.skills.Conviction], - }, - defensiveaura: { - id: 26, - skills: [sdk.skills.Prayer, sdk.skills.ResistFire, sdk.skills.Defiance, sdk.skills.ResistCold, sdk.skills.Cleansing, sdk.skills.ResistLightning, sdk.skills.Vigor, sdk.skills.Meditation, sdk.skills.Redemption, sdk.skills.Salvation], - } - }, - barb: { - combat: { - id: 32, - skills: [sdk.skills.Bash, sdk.skills.Leap, sdk.skills.DoubleSwing, sdk.skills.Stun, sdk.skills.DoubleThrow, sdk.skills.LeapAttack, sdk.skills.Concentrate, sdk.skills.Frenzy, sdk.skills.Whirlwind, sdk.skills.Berserk], - }, - masteries: { - id: 33, - skills: [sdk.skills.SwordMastery, sdk.skills.AxeMastery, sdk.skills.MaceMastery, sdk.skills.PoleArmMastery, sdk.skills.ThrowingMastery, sdk.skills.SpearMastery, sdk.skills.IncreasedStamina, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.NaturalResistance], - }, - warcries: { - id: 34, - skills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand, sdk.skills.WarCry, sdk.skills.BattleCry, sdk.skills.GrimWard, sdk.skills.FindItem, sdk.skills.Shout, sdk.skills.Taunt, sdk.skills.Howl, sdk.skills.FindPotion], - } - }, - druid: { - summoning: { - id: 40, - skills: [sdk.skills.Raven, sdk.skills.PoisonCreeper, sdk.skills.OakSage, sdk.skills.SummonSpiritWolf, sdk.skills.CarrionVine, sdk.skills.HeartofWolverine, sdk.skills.SummonDireWolf, sdk.skills.SolarCreeper, sdk.skills.SpiritofBarbs, sdk.skills.SummonGrizzly], - }, - shapeshifting: { - id: 41, - skills: [sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.Werebear, sdk.skills.FeralRage, sdk.skills.Maul, sdk.skills.Rabies, sdk.skills.FireClaws, sdk.skills.Hunger, sdk.skills.ShockWave, sdk.skills.Fury], - }, - elemental: { - id: 42, - skills: [sdk.skills.Firestorm, sdk.skills.MoltenBoulder, sdk.skills.ArcticBlast, sdk.skills.Fissure, sdk.skills.CycloneArmor, sdk.skills.Twister, sdk.skills.Volcano, sdk.skills.Tornado, sdk.skills.Armageddon, sdk.skills.Hurricane] - } - }, - assassin: { - traps: { - id: 48, - skills: [sdk.skills.FireBlast, sdk.skills.ShockWeb, sdk.skills.BladeSentinel, sdk.skills.ChargedBoltSentry, sdk.skills.WakeofFire, sdk.skills.BladeFury, sdk.skills.LightningSentry, sdk.skills.WakeofInferno, sdk.skills.DeathSentry, sdk.skills.BladeShield], - }, - shadowdisciplines: { - id: 49, - skills: [sdk.skills.ClawMastery, sdk.skills.PsychicHammer, sdk.skills.BurstofSpeed, sdk.skills.WeaponBlock, sdk.skills.CloakofShadows, sdk.skills.Fade, sdk.skills.ShadowWarrior, sdk.skills.MindBlast, sdk.skills.Venom, sdk.skills.ShadowMaster], - }, - martialarts: { - id: 50, - skills: [sdk.skills.TigerStrike, sdk.skills.DragonTalon, sdk.skills.FistsofFire, sdk.skills.DragonClaw, sdk.skills.CobraStrike, sdk.skills.ClawsofThunder, sdk.skills.DragonTail, sdk.skills.BladesofIce, sdk.skills.DragonFlight, sdk.skills.PhoenixStrike], - } - }, - }; + // Need to be set after its loaded + sdk.skillTabs = { + amazon: { + bowandcrossbow: { + id: 0, + skills: [ + sdk.skills.MagicArrow, sdk.skills.FireArrow, sdk.skills.MultipleShot, + sdk.skills.ExplodingArrow, sdk.skills.IceArrow, sdk.skills.GuidedArrow, + sdk.skills.ImmolationArrow, sdk.skills.Strafe + ], + }, + passiveandmagic: { + id: 1, + skills: [ + sdk.skills.InnerSight, sdk.skills.CriticalStrike, sdk.skills.Dodge, + sdk.skills.SlowMissiles, sdk.skills.Avoid, sdk.skills.Penetrate, + sdk.skills.Dopplezon, sdk.skills.Evade, sdk.skills.Valkyrie, sdk.skills.Pierce + ], + }, + javelinandspear: { + id: 2, + skills: [ + sdk.skills.Jab, sdk.skills.PowerStrike, sdk.skills.PoisonJavelin, + sdk.skills.Impale, sdk.skills.LightningBolt, sdk.skills.ChargedStrike, + sdk.skills.PlagueJavelin, sdk.skills.Fend, sdk.skills.LightningStrike, sdk.skills.LightningFury + ], + }, + }, + sorc: { + fire: { + id: 8, + skills: [ + sdk.skills.FireBolt, sdk.skills.Warmth, sdk.skills.Inferno, + sdk.skills.Blaze, sdk.skills.FireBall, sdk.skills.FireWall, + sdk.skills.Enchant, sdk.skills.Meteor, sdk.skills.FireMastery, sdk.skills.Hydra + ], + }, + lightning: { + id: 9, + skills: [ + sdk.skills.ChargedBolt, sdk.skills.StaticField, sdk.skills.Telekinesis, + sdk.skills.Nova, sdk.skills.Lightning, sdk.skills.ChainLightning, + sdk.skills.Teleport, sdk.skills.ThunderStorm, sdk.skills.EnergyShield, + sdk.skills.LightningMastery + ], + }, + cold: { + id: 10, + skills: [ + sdk.skills.IceBolt, sdk.skills.FrozenArmor, sdk.skills.FrostNova, + sdk.skills.IceBlast, sdk.skills.ShiverArmor, sdk.skills.GlacialSpike, + sdk.skills.Blizzard, sdk.skills.ChillingArmor, + sdk.skills.FrozenOrb, sdk.skills.ColdMastery + ] + } + }, + necro: { + curse: { + id: 16, + skills: [ + sdk.skills.AmplifyDamage, sdk.skills.DimVision, sdk.skills.Weaken, + sdk.skills.IronMaiden, sdk.skills.Terror, sdk.skills.Confuse, + sdk.skills.LifeTap, sdk.skills.Attract, + sdk.skills.Decrepify, sdk.skills.LowerResist + ], + }, + poisonandbone: { + id: 17, + skills: [ + sdk.skills.Teeth, sdk.skills.BoneArmor, sdk.skills.PoisonDagger, + sdk.skills.CorpseExplosion, sdk.skills.BoneWall, sdk.skills.PoisonExplosion, + sdk.skills.BoneSpear, sdk.skills.BonePrison, + sdk.skills.PoisonNova, sdk.skills.BoneSpirit + ], + }, + summoning: { + id: 18, + skills: [ + sdk.skills.SkeletonMastery, sdk.skills.RaiseSkeleton, sdk.skills.ClayGolem, + sdk.skills.GolemMastery, sdk.skills.RaiseSkeletalMage, sdk.skills.BloodGolem, + sdk.skills.SummonResist, sdk.skills.IronGolem, + sdk.skills.FireGolem, sdk.skills.Revive + ] + } + }, + paladin: { + combat: { + id: 24, + skills: [ + sdk.skills.Sacrifice, sdk.skills.Smite, sdk.skills.HolyBolt, + sdk.skills.Zeal, sdk.skills.Charge, sdk.skills.Vengeance, + sdk.skills.BlessedHammer, sdk.skills.Conversion, + sdk.skills.HolyShield, sdk.skills.FistoftheHeavens + ], + }, + offensiveaura: { + id: 25, + skills: [ + sdk.skills.Might, sdk.skills.HolyFire, sdk.skills.Thorns, + sdk.skills.BlessedAim, sdk.skills.Concentration, sdk.skills.HolyFreeze, + sdk.skills.HolyShock, sdk.skills.Sanctuary, + sdk.skills.Fanaticism, sdk.skills.Conviction + ], + }, + defensiveaura: { + id: 26, + skills: [ + sdk.skills.Prayer, sdk.skills.ResistFire, sdk.skills.Defiance, + sdk.skills.ResistCold, sdk.skills.Cleansing, sdk.skills.ResistLightning, + sdk.skills.Vigor, sdk.skills.Meditation, + sdk.skills.Redemption, sdk.skills.Salvation + ], + } + }, + barb: { + combat: { + id: 32, + skills: [ + sdk.skills.Bash, sdk.skills.Leap, sdk.skills.DoubleSwing, + sdk.skills.Stun, sdk.skills.DoubleThrow, sdk.skills.LeapAttack, + sdk.skills.Concentrate, sdk.skills.Frenzy, + sdk.skills.Whirlwind, sdk.skills.Berserk + ], + }, + masteries: { + id: 33, + skills: [ + sdk.skills.SwordMastery, sdk.skills.AxeMastery, sdk.skills.MaceMastery, sdk.skills.PoleArmMastery, + sdk.skills.ThrowingMastery, sdk.skills.SpearMastery, sdk.skills.IncreasedStamina, + sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.NaturalResistance + ], + }, + warcries: { + id: 34, + skills: [ + sdk.skills.BattleOrders, sdk.skills.BattleCommand, sdk.skills.WarCry, sdk.skills.BattleCry, + sdk.skills.GrimWard, sdk.skills.FindItem, sdk.skills.Shout, + sdk.skills.Taunt, sdk.skills.Howl, sdk.skills.FindPotion + ], + } + }, + druid: { + summoning: { + id: 40, + skills: [ + sdk.skills.Raven, sdk.skills.PoisonCreeper, sdk.skills.OakSage, + sdk.skills.SummonSpiritWolf, sdk.skills.CarrionVine, sdk.skills.HeartofWolverine, + sdk.skills.SummonDireWolf, sdk.skills.SolarCreeper, sdk.skills.SpiritofBarbs, sdk.skills.SummonGrizzly + ], + }, + shapeshifting: { + id: 41, + skills: [ + sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.Werebear, sdk.skills.FeralRage, + sdk.skills.Maul, sdk.skills.Rabies, sdk.skills.FireClaws, + sdk.skills.Hunger, sdk.skills.ShockWave, sdk.skills.Fury + ], + }, + elemental: { + id: 42, + skills: [ + sdk.skills.Firestorm, sdk.skills.MoltenBoulder, sdk.skills.ArcticBlast, + sdk.skills.Fissure, sdk.skills.CycloneArmor, sdk.skills.Twister, + sdk.skills.Volcano, sdk.skills.Tornado, sdk.skills.Armageddon, sdk.skills.Hurricane + ] + } + }, + assassin: { + traps: { + id: 48, + skills: [ + sdk.skills.FireBlast, sdk.skills.ShockWeb, sdk.skills.BladeSentinel, + sdk.skills.ChargedBoltSentry, sdk.skills.WakeofFire, sdk.skills.BladeFury, + sdk.skills.LightningSentry, sdk.skills.WakeofInferno, sdk.skills.DeathSentry, sdk.skills.BladeShield + ], + }, + shadowdisciplines: { + id: 49, + skills: [ + sdk.skills.ClawMastery, sdk.skills.PsychicHammer, sdk.skills.BurstofSpeed, + sdk.skills.WeaponBlock, sdk.skills.CloakofShadows, sdk.skills.Fade, + sdk.skills.ShadowWarrior, sdk.skills.MindBlast, sdk.skills.Venom, sdk.skills.ShadowMaster + ], + }, + martialarts: { + id: 50, + skills: [ + sdk.skills.TigerStrike, sdk.skills.DragonTalon, sdk.skills.FistsofFire, + sdk.skills.DragonClaw, sdk.skills.CobraStrike, sdk.skills.ClawsofThunder, + sdk.skills.DragonTail, sdk.skills.BladesofIce, sdk.skills.DragonFlight, sdk.skills.PhoenixStrike + ], + } + }, + }; - module.exports = sdk; -})(module); + return sdk; +})); diff --git a/d2bs/kolbot/libs/modules/workers/Advertise.js b/d2bs/kolbot/libs/modules/workers/Advertise.js new file mode 100644 index 000000000..ef237688a --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/Advertise.js @@ -0,0 +1,40 @@ +/** +* @filename Advertise.js +* @author theBGuy +* @desc Worker script for advertising in chat +* +*/ + +(function (module, require, Worker) { + // Only load this in global scope + if (new RegExp(/[default.dbj|main.js]/gi).test(getScript(true).name)) { + // handle invalid interval input + if (!Array.isArray(Config.Advertise.Interval)) { + if (typeof Config.Advertise.Interval === "number") { + Config.Advertise.Interval = [Config.Advertise.Interval, Config.Advertise.Interval]; + } else { + Config.Advertise.Interval = [30, 60]; + } + } else if (Config.Advertise.Interval.length < 2) { + if (typeof Config.Advertise.Interval[0] === "number") { + Config.Advertise.Interval.push(Config.Advertise.Interval[0] + rand(0, 30)); + } else { + Config.Advertise.Interval = [30, 60]; + } + } + const [min, max] = Config.Advertise.Interval; + let waitTick = getTickCount() + Time.seconds(rand(min, max)); + + // Start + Worker.runInBackground.Advertise = function () { + if (getTickCount() - waitTick < 0) return true; + waitTick += Time.seconds(rand(min, max)); + + say("!" + Config.Advertise.Message, true); + + return true; + }; + + console.log("ÿc2Kolbotÿc0 :: Advertise running"); + } +})(module, require, typeof Worker === "object" && Worker || require("../Worker")); diff --git a/d2bs/kolbot/libs/modules/workers/Guard.js b/d2bs/kolbot/libs/modules/workers/Guard.js new file mode 100644 index 000000000..758cc91f6 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/Guard.js @@ -0,0 +1,99 @@ +(function (module, require, thread, globalThis) { + "use strict"; + const Messaging = require("../Messaging"); + const Worker = require("../Worker"); + const sdk = require("../sdk"); + + switch (thread) { + case "thread": { + Worker.runInBackground.stackTrace = (new function () { + const self = this; + let stack; + + let myStack = ""; + + // recv stack + Messaging.on( + "Guard", + (data => typeof data === "object" && data && data.hasOwnProperty("stack") && (myStack = data.stack)) + ); + + /** + * @constructor + * @param {function():string} callback + */ + function UpdateableText (callback) { + let element = new Text(callback(), self.x + 15, self.y + (7 * self.hooks.length), 0, 12, 0); + self.hooks.push(element); + this.update = () => { + element.text = callback(); + element.visible = me.gameReady && [sdk.uiflags.Inventory, + sdk.uiflags.SkillWindow, + sdk.uiflags.TradePrompt, + sdk.uiflags.Stash, + sdk.uiflags.Cube, + sdk.uiflags.QuickSkill].every(f => !getUIFlag(f)); + }; + } + + this.hooks = []; + this.x = 500; + this.y = 600 - (400 + (self.hooks.length * 15)); + // this.box = new Box(this.x-2, this.y-20, 250, (self.hooks.length * 15), 0, 0.2); + + + for (let i = 0; i < 22; i++) { + (i => this.hooks.push(new UpdateableText(() => stack && stack.length > i && stack[i] || "")))(i); + } + + this.update = () => { + stack = myStack.match(/[^\r\n]+/g); + stack = stack && stack.slice(6/*skip path to here*/).map(el => { + let line = el.substr(el.lastIndexOf(":") + 1); + let functionName = el.substr(0, el.indexOf("@")); + let filename = el.substr(el.lastIndexOf("\\") + 1); + + filename = filename.substr(0, filename.indexOf(".")); + + return filename + "ÿc::ÿc0" + line + "ÿc:@ÿc0" + functionName; + }).reverse(); + this.hooks.filter(hook => hook.hasOwnProperty("update") && typeof hook.update === "function" && hook.update()); + return true; + }; + }).update; + + let quiting = false; + addEventListener("scriptmsg", data => data === "quit" && (quiting = true)); + + // eslint-disable-next-line dot-notation + globalThis["main"] = function () { + while (!quiting) delay(3); + //@ts-ignore + getScript(true).stop(); + }; + break; + } + case "started": { + console.log("ÿc2Kolbotÿc0 :: Guard running"); + let sendStack = getTickCount(); + Worker.push(function highPrio () { + Worker.push(highPrio); + if ((getTickCount() - sendStack) < 200 || (sendStack = getTickCount()) && false) return true; + Messaging.send({ Guard: { stack: (new Error).stack } }); + return true; + }); + + break; + } + case "loaded": { + break; + } + } + +}).call( + null, + typeof module === "object" && module || {}, + typeof require === "undefined" && (include("require.js") && require) || require, + getScript.startAsThread(), + [].filter.constructor("return this")() +); diff --git a/d2bs/kolbot/libs/modules/workers/SimpleParty.js b/d2bs/kolbot/libs/modules/workers/SimpleParty.js new file mode 100644 index 000000000..113b0e182 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/SimpleParty.js @@ -0,0 +1,223 @@ +(function (module, require) { + // party thread specific + include("oog/ShitList.js"); + if ((Config.ShitList || Config.UnpartyShitlisted)) { + ShitList.read().forEach((name) => me.shitList.add(name)); + } + const Worker = require("../Worker"); + const NO_PARTY = 65535; + const PARTY_MEMBER = 1; + const ACCEPTABLE = 2; + const INVITED = 4; + const BUTTON_INVITE_ACCEPT_CANCEL = 2; + const BUTTON_LEAVE_PARTY = 3; + // const BUTTON_HOSTILE = 4; + + const SimpleParty = {}; + + SimpleParty.biggestPartyId = function () { + let uniqueParties = []; + try { + // Or add it and return the value + for (let party = getParty(); party.getNext();) { + ( + // Find this party + uniqueParties.find(u => u.partyid === party.partyid) + // Or create an instance of it + || ((uniqueParties.push({ + partyid: party.partyid, + used: 0 + }) && false) || uniqueParties[uniqueParties.length - 1]) + // Once we have the party object, increase field used + ).used++; + } + + // Filter out no party, if another party is found + if (uniqueParties.some(u => u.partyid !== NO_PARTY)) { + (uniqueParties = uniqueParties.filter(u => u.partyid !== NO_PARTY)); + } + return (uniqueParties + .sort(function (a, b) { + /* b-a = desc */ + return b.used - a.used; + }).first() || { partyid: -1 }).partyid; + } catch (e) { + console.error(e); + + return -1; + } + }; + + SimpleParty.acceptFirst = function () { + const toMd5Int = what => parseInt(md5(what).substr(0, 4), 16); //ToDo; do something with game number here + const names = []; + for (let party = getParty(); party.getNext();) { + if (party.partyid === NO_PARTY && party.partyflag === ACCEPTABLE) { + names.push(party.name); + } + } + return names + .filter(n => n !== me.name /*cant accept yourself ;)*/) + .sort((a, b) => toMd5Int(a) - toMd5Int(b)) + .first(); + }; + + SimpleParty.getFirstPartyMember = function () { + let myPartyId = ((() => (getParty() || { partyid: 0 }).partyid))(); + for (let party = getParty(); party.getNext();) { + if (party.partyid === myPartyId && party.name !== me.charname) { + return party; + } + } + return undefined; + }; + + SimpleParty.invite = function (name) { + + for (let party = getParty(); party.getNext();) { + // If party member is + if (party.name === name + && party.partyflag !== ACCEPTABLE + && party.partyflag !== PARTY_MEMBER + && party.partyid === NO_PARTY) { + clickParty(party, BUTTON_INVITE_ACCEPT_CANCEL); // Press the invite button + return true; + } + } + return false; + }; + + SimpleParty.timer = 0; + + if (new RegExp(/[default.dbj|main.js]/gi).test(getScript(true).name)) { + addEventListener("scriptmsg", function (msg) { + if (!isType(msg, "string")) return; + if (msg === "hostileEventEnded" && (Config.ShitList || Config.UnpartyShitlisted)) { + ShitList.read().forEach((name) => me.shitList.add(name)); + } + }); + + // For now, we gonna do this in game with a single party + (Worker.runInBackground.party = (function () { + console.log("ÿc2Kolbotÿc0 :: Simple party running"); + SimpleParty.timer = getTickCount(); + return function () { + // Set timer back on 3 seconds, or reset it and continue + if ((getTickCount() - SimpleParty.timer) < 3000 + || (SimpleParty.timer = getTickCount()) && false) { + return true; + } + if (!me.gameReady) { + SimpleParty.timer = getTickCount(); + return true; + } + + // Public mode 1/2/3 dont count. This is SimplyParty + if (Config.PublicMode !== true) { + return true; + } + + const myPartyId = ((() => (getParty() || { partyid: 0 }).partyid))(); + if (!myPartyId) { + return true; // party ain't up yet + } + + const biggestPartyId = SimpleParty.biggestPartyId(); + + for (let party = getParty(), acceptFirst; party && party.getNext();) { + if (!(party && typeof party === "object")) { + continue; + } + + if (!(party.hasOwnProperty("life"))) { + continue; + } // Somehow not a party member + + // Deal with inviting + if ( // If no party is formed, or im member of the biggest party + party.partyflag !== INVITED // Already invited + && party.partyflag !== ACCEPTABLE // Need to accept invite, so cant invite + && party.partyflag !== PARTY_MEMBER // cant party again with soemone + && party.partyid === NO_PARTY // Can only invite someone that isnt in a party + && ( // If im not in a party, only if there is no party + myPartyId === NO_PARTY && biggestPartyId === NO_PARTY + // OR, if im part of the biggest party + || biggestPartyId === myPartyId + ) + ) { + if (getPlayerFlag(me.gid, party.gid, sdk.player.flag.Hostile)) { + if (me.shitList.has(party.name)) { + say(party.name + " has been shitlisted."); + ShitList.add(party.name); + } + + if (party.partyflag === sdk.party.flag.Cancel) { + clickParty(party, BUTTON_INVITE_ACCEPT_CANCEL); // cancel invitation + } + + continue; + } else if (me.shitList.has(party.name)) { + continue; + } + + // if player isn't invited, invite + clickParty(party, BUTTON_INVITE_ACCEPT_CANCEL); + } + + // Deal with accepting + if ( + party.partyflag === ACCEPTABLE + && myPartyId === NO_PARTY // Can only accept if we are not in a party + && party.partyid === biggestPartyId // Only accept if it is an invite to the biggest party + ) { + // Try to make all bots accept the same char first, to avoid confusion with multiple parties + if (biggestPartyId === NO_PARTY) { + // if acceptFirst isnt set, create it (to cache it, yet generate on demand) + if (!acceptFirst) { + acceptFirst = SimpleParty.acceptFirst(); + } + + if (acceptFirst !== party.name) { + continue; // Ignore party acceptation + } + if (me.shitList.has(party.name)) { + continue; + } + } + + clickParty(party, BUTTON_INVITE_ACCEPT_CANCEL); + } + + // Deal with being in the wrong party. (we want to be in the biggest party) + if ( + party.partyflag === PARTY_MEMBER // We are in the same party + && biggestPartyId !== party.partyid // yet this party isnt the biggest party available + && biggestPartyId !== NO_PARTY // And the biggest party isnt no party + ) { + clickParty(party, BUTTON_LEAVE_PARTY); + } + + if (Config.UnpartyShitlisted) { + // Add new hostile players to temp shitlist, leader should have Config.ShitList set to true to update the permanent list. + if (getPlayerFlag(me.gid, party.gid, sdk.player.flag.Hostile) + && !me.shitList.has(party.name)) { + me.shitList.add(party.name); + } + + if (me.shitList.has(party.name) + && myPartyId !== NO_PARTY + && party.partyid === myPartyId) { + console.log("Unpartying shitlisted player: " + party.name); + clickParty(party, BUTTON_LEAVE_PARTY); + delay(100); + } + } + } + return true; + }; + })()); + } + + module.exports = SimpleParty; + +})(module, require); diff --git a/d2bs/kolbot/libs/modules/workers/TownChicken.js b/d2bs/kolbot/libs/modules/workers/TownChicken.js new file mode 100644 index 000000000..4f96ff056 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/TownChicken.js @@ -0,0 +1,381 @@ +/** +* @filename TownChicken.js +* @author theBGuy +* @desc TownChicken background worker thread +* +*/ + +(function (module, require, Worker) { + // Only load this in global scope + if (new RegExp(/[default.dbj|main.js]/gi).test(getScript(true).name)) { + /** + * @param {number} [targetArea] + * @param {string} [owner] + * @param {ObjectUnit} [unit] + * @param {Unit} [dummy] + * @returns {boolean} + */ + const usePortal = function (targetArea, owner, unit, dummy) { + if (targetArea && me.inArea(targetArea)) return true; + + me.cancelUIFlags(); + + const townAreaCheck = (area = 0) => sdk.areas.Towns.includes(area); + const preArea = me.area; + const leavingTown = townAreaCheck(preArea); + + for (let i = 0; i < 13; i += 1) { + if (me.dead) return false; + if (targetArea ? me.inArea(targetArea) : me.area !== preArea) return true; + + (i > 0 && owner && me.inTown) && Town.move("portalspot"); + + const portal = unit + ? copyUnit(unit) + : Pather.getPortal(targetArea, owner); + + if (portal && portal.area === me.area) { + const useTk = me.inTown && Skill.useTK(portal) && i < 3; + if (useTk) { + if (portal.distance > 21) { + me.inTown && me.act === 5 + ? Town.move("portalspot") + : Pather.moveNearUnit(portal, 20); + } + if (Packet.telekinesis(portal) + && Misc.poll(function () { + return targetArea ? me.inArea(targetArea) : me.area !== preArea; + })) { + Pather.lastPortalTick = getTickCount(); + delay(100); + + return true; + } + } else { + if (portal.distance > 5) { + i < 3 + ? Pather.moveNearUnit(portal, 4, false) + : Pather.moveToUnit(portal); + } + + if (getTickCount() - Pather.lastPortalTick > (leavingTown ? 2500 : 1000)) { + i < 2 + ? Packet.entityInteract(portal) + : Misc.click(0, 0, portal); + } else { + // only delay if we are in town and leaving town, don't delay if we are attempting to portal from out of town since this is the chicken thread + // and we are likely being attacked + leavingTown && delay(300); + + continue; + } + } + + let tick = getTickCount(); + + while (getTickCount() - tick < 500) { + if (me.area !== preArea) { + Pather.lastPortalTick = getTickCount(); + delay(100); + + return true; + } + + delay(10); + } + // try clicking dummy portal + !!dummy && portal.area === 1 && Misc.click(0, 0, portal); + + i > 1 && (i % 3) === 0 && Packet.flash(me.gid); + } else { + console.log("Didn't find portal, retry: " + i); + i > 3 && me.inTown && Town.move("portalspot", false); + if (i === 12) { + let p = Game.getObject("portal"); + console.debug(p); + if (!!p && Misc.click(0, 0, p) && Misc.poll(function () { + return me.area !== preArea, 1000, 100; + })) { + Pather.lastPortalTick = getTickCount(); + delay(100); + + return true; + } + } + Packet.flash(me.gid); + } + + delay(250); + } + + return (targetArea ? me.inArea(targetArea) : me.area !== preArea); + }; + + /** + * @param {boolean} use + * @returns {boolean} + */ + const makePortal = function (use = false) { + if (me.inTown) return true; + + let oldGid = -1; + + for (let i = 0; i < 5; i += 1) { + if (me.dead) return false; + + let tpTool = me.getTpTool(); + if (!tpTool) return false; + + let oldPortal = Game.getObject(sdk.objects.BluePortal); + if (oldPortal) { + do { + if (oldPortal.getParent() === me.name) { + oldGid = oldPortal.gid; + break; + } + } while (oldPortal.getNext()); + + // old portal is close to use, we should try to use it + if (oldPortal.getParent() === me.name && oldPortal.distance < 4) { + if (use) { + if (usePortal(null, null, copyUnit(oldPortal))) return true; + break; // don't spam usePortal + } else { + return copyUnit(oldPortal); + } + } + } + + let pingDelay = me.getPingDelay(); + + if (tpTool.use() || Game.getObject("portal")) { + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(500 + i * 100, pingDelay * 2 + 100)) { + const portal = getUnits(sdk.unittype.Object, "portal") + .filter(function (p) { + return p.getParent() === me.name && p.gid !== oldGid; + }).first(); + + if (portal) { + if (use) { + if (usePortal(null, null, copyUnit(portal))) return true; + break; // don't spam usePortal + } else { + return copyUnit(portal); + } + } else { + // check dummy + let dummy = getUnits(sdk.unittype.Object, "portal") + .filter(function (p) { + return p.name === "Dummy"; + }).first(); + if (dummy) { + console.debug(dummy); + if (use) return usePortal(null, null, dummy, true); + return copyUnit(dummy); + } + } + + delay(10); + } + } else { + console.log("Failed to use tp tool"); + Packet.flash(me.gid, pingDelay); + delay(200 + pingDelay); + } + + delay(40); + } + + return false; + }; + + /** + * @param {Act} act + * @param {boolean} wpmenu + * @returns {boolean} + */ + const goToTown = function (act = 0, wpmenu = false) { + if (!me.inTown) { + const townArea = sdk.areas.townOf(me.act); + try { + !makePortal(true) && console.warn("Town.goToTown: Failed to make TP"); + if (!me.inTown && !usePortal(townArea, me.name)) { + console.warn("Town.goToTown: Failed to take TP"); + if (!me.inTown && !usePortal(sdk.areas.townOf(me.area))) { + throw new Error("Town.goToTown: Failed to take TP"); + } + } + } catch (e) { + let tpTool = me.getTpTool(); + if (!tpTool && Misc.getPlayerCount() <= 1) { + Misc.errorReport(new Error("Town.goToTown: Failed to go to town and no tps available. Restart.")); + scriptBroadcast("quit"); + } else { + if (!Misc.poll(function () { + if (me.inTown) return true; + let p = Game.getObject("portal"); + console.debug(p); + !!p && Misc.click(0, 0, p) && delay(100); + Misc.poll(function () { + return me.idle; + }, 1000, 100); + console.debug("inTown? " + me.inTown); + return me.inTown; + }, 700, 100)) { + Misc.errorReport(new Error("Town.goToTown: Failed to go to town. Quiting.")); + scriptBroadcast("quit"); + } + } + } + } + + if (!act) return true; + if (act < 1 || act > 5) throw new Error("Town.goToTown: Invalid act"); + if (act > me.highestAct) return false; + + if (act !== me.act) { + try { + Pather.useWaypoint(sdk.areas.townOfAct(act), wpmenu); + } catch (WPError) { + throw new Error("Town.goToTown: Failed use WP"); + } + } + + return true; + }; + + const threads = ["threads/antihostile.js", "threads/rushthread.js", "threads/CloneKilla.js"]; + const togglePause = function () { + for (let thread of threads) { + let script = getScript(thread); + + if (script) { + script.running + ? script.pause() + : script.resume(); + } + } + + return true; + }; + + const visitTown = function () { + console.log("ÿc8Start ÿc0:: ÿc8visitTown"); + + const preArea = me.area; + const preAct = sdk.areas.actOf(preArea); + + if (!me.inTown && !me.getTpTool()) { + console.warn("Can't chicken to town. Quit"); + scriptBroadcast("quit"); + return false; + } + + let tick = getTickCount(); + + // not an essential function -> handle thrown errors + me.cancelUIFlags(); + try { + goToTown(); + while (!me.area) delay (3); + if (!me.inTown) return false; + } catch (e) { + return false; + } + + const { x, y } = me; + + Town.doChores(); + + console.debug("Current act: " + me.act + " Prev Act: " + preAct); + me.act !== preAct && goToTown(preAct); + Town.move("portalspot"); + Pather.moveTo(x, y); + + while (getTickCount() - tick < 4500) { + delay(10); + } + + if (!usePortal(preArea, me.name)) { + try { + usePortal(null, me.name); + } catch (e) { + throw new Error("Town.visitTown: Failed to go back from town"); + } + } + Config.PublicMode && Pather.makePortal(); + console.log("ÿc8End ÿc0:: ÿc8visitTown - currentArea: " + getAreaName(me.area)); + + return me.area === preArea; + }; + + let townCheck = false; + + Misc.townCheck = function () { + return false; + }; + + let waitTick = getTickCount(); + let potTick = getTickCount(); + let _recursion = false; + + // Start + Worker.runInBackground.TownChicken = function () { + if (getTickCount() - waitTick < 100) return true; + if (_recursion) return true; + waitTick = getTickCount(); + if (me.inTown) return true; + + let shouldChicken = ( + (townCheck || me.hpPercent < Config.TownHP || me.mpPercent < Config.TownMP) + ); + + if (shouldChicken && !me.canTpToTown()) { + // we should probably quit? + return true; + } + + if (!shouldChicken) { + if (getTickCount() - potTick < 300) return true; + potTick = getTickCount(); + // do we need potions? + if (!Config.TownCheck) return true; + // can we chicken? + if (!me.canTpToTown()) return true; + if (me.needBeltPots() || (Config.OpenChests.Enabled && me.needKeys())) { + shouldChicken = true; + } + } + + if (shouldChicken) { + let t4 = getTickCount(); + try { + _recursion = true; + togglePause(); + console.log("ÿc8(TownChicken) :: ÿc0Going to town"); + me.overhead("ÿc8(TownChicken) :: ÿc0Going to town"); + Attack.stopClear = true; + + visitTown(); + } catch (e) { + Misc.errorReport(e, "TownChicken.js"); + scriptBroadcast("quit"); + + return false; + } finally { + _recursion = false; + togglePause(); + Packet.flash(me.gid, 100); + console.log("ÿc8(TownChicken) :: ÿc0Took: " + Time.format(getTickCount() - t4) + " to visit town."); + [Attack.stopClear, townCheck] = [false, false]; + } + } + + return true; + }; + + console.log("ÿc2Kolbotÿc0 :: TownChicken running"); + } +})(module, require, typeof Worker === "object" && Worker || require("../Worker")); diff --git a/d2bs/kolbot/libs/modules/workers/WpWatcher.js b/d2bs/kolbot/libs/modules/workers/WpWatcher.js new file mode 100644 index 000000000..686bc9920 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/WpWatcher.js @@ -0,0 +1,40 @@ +/** +* @filename WpWatcher.js +* @author theBGuy +* @desc Worker script for cacheing and watching waypoints +* +*/ + +(function (module, require, Worker) { + // Only load this in global scope + if (new RegExp(/[default.dbj|main.js]/gi).test(getScript(true).name)) { + let waitTick = getTickCount(); + let done = false; + + // Start + Worker.runInBackground.WpWatcher = function () { + if (done) return true; + if (getTickCount() - waitTick < 100) return true; + waitTick = getTickCount(); + if (!me.gameReady) return true; + + // Waypoint is open, so lets cache it + if (!getUIFlag(sdk.uiflags.Waypoint)) { + return true; + } + + // Cache the waypoints + const waypoints = Pather.wpAreas.map(function (area, index) { + return getWaypoint(index, true); + }); + me.waypoints = waypoints; + Pather.initialized = true; + scriptBroadcast({ type: "cache-waypoints", data: waypoints }); + done = true; + + return true; + }; + + console.log("ÿc2Kolbotÿc0 :: Waypoint Watcher running"); + } +})(module, require, typeof Worker === "object" && Worker || require("../Worker")); diff --git a/d2bs/kolbot/libs/oog/D2Bot.js b/d2bs/kolbot/libs/oog/D2Bot.js new file mode 100644 index 000000000..7413be83c --- /dev/null +++ b/d2bs/kolbot/libs/oog/D2Bot.js @@ -0,0 +1,223 @@ +/** +* @filename D2Bot.js +* @author kolton, D3STROY3R, theBGuy +* @desc UMD module to handle interfacing with D2Bot# +* +*/ + +!isIncluded("Polyfill.js") && include("Polyfill.js"); +includeIfNotIncluded("oog/DataFile.js"); + +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + let v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define(["require", "../modules/CopyData"], factory); + } else { + root.D2Bot = factory(); + } +}([].filter.constructor("return this")(), function() { + const CopyData = require("../modules/CopyData"); + + const D2Bot = { + handle: 0, + _entry: "", + + init: function () { + let handle = DataFile.getStats().handle; + + if (handle) { + D2Bot.handle = handle; + + let tmp = getThreads().find((thread) => thread.name.endsWith(".dbj")); + if (tmp) { + // Remove .dbj + D2Bot._entry = tmp.name.split(".")[0]; + } + } + + return D2Bot.handle; + }, + + sendMessage: function (handle, mode, msg) { + sendCopyData(null, handle, mode, msg); + }, + + printToConsole: function (msg, color, tooltip, trigger) { + const printObj = { + msg: (("(" + D2Bot._entry + ") ") + (new Date().dateStamp() + " ") + msg), + color: color || 0, + tooltip: tooltip || "", + trigger: trigger || "" + }; + + new CopyData().data("printToConsole", [printObj]).send(); + }, + + printToItemLog: function (itemObj) { + new CopyData().data("printToItemLog", [itemObj]).send(); + }, + + uploadItem: function (itemObj) { + new CopyData().data("uploadItem", [itemObj]).send(); + }, + + writeToFile: function (filename, msg) { + new CopyData().data("writeToFile", [filename, msg]).send(); + }, + + postToIRC: function (ircProfile, recepient, msg) { + new CopyData().data("postToIRC", [ircProfile, recepient, msg]).send(); + }, + + ircEvent: function (mode) { + new CopyData().data("ircEvent", [mode ? "true" : "false"]).send(); + }, + + notify: function (msg) { + new CopyData().data("notify", [msg]).send(); + }, + + saveItem: function (itemObj) { + new CopyData().data("saveItem", [itemObj]).send(); + }, + + updateStatus: function (msg) { + new CopyData().data("updateStatus", [msg]).send(); + }, + + updateRuns: function () { + new CopyData().data("updateRuns", []).send(); + }, + + updateChickens: function () { + new CopyData().data("updateChickens", []).send(); + }, + + updateDeaths: function () { + new CopyData().data("updateDeaths", []).send(); + }, + + requestGameInfo: function () { + new CopyData().data("requestGameInfo", []).send(); + }, + + restart: function (keySwap) { + new CopyData().data( + "restartProfile", + arguments.length > 0 ? [me.profile, keySwap] : [me.profile] + ).send(); + }, + + CDKeyInUse: function () { + new CopyData().data("CDKeyInUse", []).send(); + }, + + CDKeyDisabled: function () { + new CopyData().data("CDKeyDisabled", []).send(); + }, + + CDKeyRD: function () { + new CopyData().data("CDKeyRD", []).send(); + }, + + stop: function (profile, release) { + !profile && (profile = me.profile); + + new CopyData().data("stop", [profile, release ? "True" : "False"]).send(); + }, + + start: function (profile) { + new CopyData().data("start", [profile]).send(); + }, + + startSchedule: function (profile) { + new CopyData().data("startSchedule", [profile]).send(); + }, + + stopSchedule: function (profile) { + new CopyData().data("stopSchedule", [profile]).send(); + }, + + updateCount: function () { + new CopyData().data("updateCount", ["1"]).send(); + }, + + shoutGlobal: function (msg, mode) { + new CopyData().data("shoutGlobal", [msg, mode]).send(); + }, + + heartBeat: function () { + new CopyData().mode(0xbbbb).data("heartBeat", []).send(); + }, + + sendWinMsg: function (wparam, lparam) { + new CopyData().data("winmsg", [wparam, lparam]).send(); + }, + + ingame: function () { + this.sendWinMsg(0x0086, 0x0000); + this.sendWinMsg(0x0006, 0x0002); + this.sendWinMsg(0x001c, 0x0000); + }, + + /** + * Profile to profile communication + * @param {string} profile + * @param {string} gameName + * @param {number} gameCount + * @param {string} gamePass + * @param {string} isUp + * @param {number} delay + */ + joinMe: function (profile, gameName, gameCount, gamePass, isUp, delay) { + let obj = { + gameName: (gameName + gameCount).toLowerCase(), + gamePass: (gamePass).toLowerCase(), + inGame: isUp === "yes", + delay: (delay || 0), + }; + + sendCopyData(null, profile, 1, JSON.stringify(obj)); + }, + + requestGame: function (profile) { + new CopyData().handle(profile).mode(3).send(); + }, + + getProfile: function () { + new CopyData().data("getProfile", []).send(); + }, + + setProfile: function (account, password, character, difficulty, realm, infoTag, gamePath) { + new CopyData().data( + "setProfile", + [account, password, character, difficulty, realm, infoTag, gamePath] + ).send(); + }, + + setTag: function (tag) { + new CopyData().data("setTag", [tag]).send(); + }, + + // Store info in d2bot# cache + store: function (info) { + this.remove(); + + new CopyData().data("store", [me.profile, info]).send(); + }, + + // Get info from d2bot# cache + retrieve: function () { + new CopyData().data("retrieve", [me.profile]).send(); + }, + + // Delete info from d2bot# cache + remove: function () { + new CopyData().data("delete", [me.profile]).send(); + } + }; + + return D2Bot; +})); diff --git a/d2bs/kolbot/libs/oog/DataFile.js b/d2bs/kolbot/libs/oog/DataFile.js new file mode 100644 index 000000000..9cc0dc041 --- /dev/null +++ b/d2bs/kolbot/libs/oog/DataFile.js @@ -0,0 +1,158 @@ +/** +* @filename DataFile.js +* @author kolton, D3STROY3R, theBGuy +* @desc Maintain profile datafiles +* +*/ + +!isIncluded("Polyfill.js") && include("Polyfill.js"); +includeIfNotIncluded("oog/FileAction.js"); + +(function (root, factory) { + if (typeof module === "object" && typeof module.exports === "object") { + let v = factory(); + if (v !== undefined) module.exports = v; + } else if (typeof define === "function" && define.amd) { + define([], factory); + } else { + root.DataFile = factory(); + } +}(this, function () { + const DataFile = { + _default: { + handle: 0, + name: "", + level: 0, + experience: 0, + gold: 0, + deaths: 0, + runs: 0, + lastArea: "", + ingameTick: 0, + gameName: "", + currentGame: "", + nextGame: "" + }, + + create: function () { + FileAction.write("data/" + me.profile + ".json", JSON.stringify(this._default, null, 2)); + + return this._default; + }, + + /** + * @param {string} profile + * @returns {DataFileObj | null} + */ + read: function (profile) { + if (!profile) return null; + if (!FileTools.exists("data/" + profile + ".json")) return null; + let string = FileAction.read("data/" + profile + ".json"); + + try { + let obj = JSON.parse(string); + return obj; + } catch (e) { + console.error(e); + + return null; + } + }, + + getObj: function () { + !FileTools.exists("data/" + me.profile + ".json") && DataFile.create(); + + let obj; + let string = FileAction.read("data/" + me.profile + ".json"); + + try { + obj = JSON.parse(string); + } catch (e) { + // If we failed, file might be corrupted, so create a new one + obj = this.create(); + obj.handle = D2Bot.handle; + } + + if (obj) { + return obj; + } + + console.warn("Error reading DataFile. Using null values."); + + return this._default; + }, + + getStats: function () { + let obj = this.getObj(); + return clone(obj); + }, + + updateStats: function (arg, value) { + while (me.ingame && !me.gameReady) { + delay(100); + } + + let statArr = []; + + if (Array.isArray(arg)) { + statArr = arg.slice(); + } else if (typeof arg === "string") { + statArr.push(arg); + } + + let obj = this.getObj(); + + for (let prop of statArr) { + switch (prop) { + case "experience": + obj.experience = me.getStat(sdk.stats.Experience); + obj.level = me.getStat(sdk.stats.Level); + + break; + case "lastArea": + if (obj.lastArea === getAreaName(me.area)) { + return; + } + + obj.lastArea = getAreaName(me.area); + + break; + case "gold": + if (!me.gameReady) { + break; + } + + obj.gold = me.getStat(sdk.stats.Gold) + me.getStat(sdk.stats.GoldBank); + + break; + case "name": + obj.name = me.charname; + + break; + case "currentGame": + obj.currentGame = me.ingame ? me.gamename : ""; + + break; + case "ingameTick": + obj.ingameTick = getTickCount(); + + break; + case "deaths": + obj.deaths = (obj.deaths || 0) + 1; + + break; + default: + obj[prop] = value; + + break; + } + } + + let string = JSON.stringify(obj, null, 2); + + FileAction.write("data/" + me.profile + ".json", string); + } + }; + + return DataFile; +})); diff --git a/d2bs/kolbot/libs/oog/FileAction.js b/d2bs/kolbot/libs/oog/FileAction.js new file mode 100644 index 000000000..14a537b62 --- /dev/null +++ b/d2bs/kolbot/libs/oog/FileAction.js @@ -0,0 +1,96 @@ +/** +* @filename FileAction.js +* @author theBGuy +* @desc Handle CRUD operations +* +*/ + +!isIncluded("Polyfill.js") && include("Polyfill.js"); + +/** + * Should this file be in the oog folder? Its technically actions performed out of game but many in game functions use it. + * Maybe common? I want core/ to essentially be an alias for core. Everything that is required for in game functionality + * All "extras" should be somewhere else + */ + +const FileAction = { + read: function (path = "") { + if (!path) throw new Error("No path provided"); + + let contents = ""; + + for (let i = 0; i < 30; i++) { + try { + contents = FileTools.readText(path); + + if (contents) return contents; + } catch (e) { + // console.error(e, path); + } + + // incremental delay + delay(100 + ((i % 5) * 100)); + } + + return contents; + }, + + write: function (path = "", msg = "") { + if (!path) throw new Error("No path provided"); + + // do we read the file to see if it has changed? + // for now keep the orginal behavior + for (let i = 0; i < 30; i++) { + try { + FileTools.writeText(path, msg); + + break; + } catch (e) { + // console.error(e, path); + } + + delay(100 + ((i % 5) * 100)); + } + + return true; + }, + + append: function (path = "", msg = "") { + if (!path) throw new Error("No path provided"); + + // do we read the file to see if it has changed? + // for now keep the orginal behavior + for (let i = 0; i < 30; i++) { + try { + FileTools.appendText(path, msg); + + break; + } catch (e) { + // console.error(e, path); + } + + delay(100 + ((i % 5) * 100)); + } + + return true; + }, + + parse: function (path = "") { + if (!path) throw new Error("No path provided"); + if (!FileTools.exists(path)) throw new Error("Can't parse file that doesn't exist"); + + let contents = ""; + + try { + contents = FileAction.read(path); + + if (contents) { + return JSON.parse(contents); + } + } catch (e) { + console.error(e, path); + } + + return contents; + }, +}; diff --git a/d2bs/kolbot/libs/oog/Locations.js b/d2bs/kolbot/libs/oog/Locations.js new file mode 100644 index 000000000..4ee5c974b --- /dev/null +++ b/d2bs/kolbot/libs/oog/Locations.js @@ -0,0 +1,437 @@ +/** +* @filename Locations.js +* @author theBGuy +* @desc Map of the out of game locations +* +*/ + +(function (module) { + const Controls = require("../modules/Control"); + /** + * @param {Control} control + * @returns {string} + */ + const parseControlText = function (control) { + if (!control) return ""; + let text = control.getText(); + if (!text || !text.length) return ""; + return text.join(" "); + }; + /** + * @param {number[]} locations + * @param {(location?: number) => any} action + */ + const addLocations = function (locations, action) { + locations.forEach(function (loc) { + _loc.set(loc, action); + }); + }; + const pType = Profile().type; + /** + * Default locations written as if bot is running d2botlead + */ + const _loc = new Map([ + [sdk.game.locations.GatewaySelect, + function () { + Controls.GatewayCancel.click(); + } + ], + [sdk.game.locations.OtherMultiplayer, + function () { + const pType = Profile().type; + if ([sdk.game.profiletype.TcpIpHost, sdk.game.profiletype.TcpIpJoin].includes(pType)) { + if (Controls.TcpIp.click()) { + pType === sdk.game.profiletype.TcpIpHost + ? Controls.TcpIpHost.click() + : Controls.TcpIpJoin.click(); + } + } else if (pType === sdk.game.profiletype.OpenBattlenet) { + Controls.OpenBattleNet.click(); + } else { + Controls.OtherMultiplayerCancel.click(); + } + } + ], + [sdk.game.locations.TcpIpEnterIp, + function () { + Controls.PopupNo.click(); + } + ], + [sdk.game.locations.MainMenu, + function () { + switch (pType) { + case sdk.game.profiletype.OpenBattlenet: + case sdk.game.profiletype.TcpIpHost: + case sdk.game.profiletype.TcpIpJoin: + Controls.OtherMultiplayer.click(); + + break; + case sdk.game.profiletype.Battlenet: + ControlAction.clickRealm(ControlAction.realms[Starter.profileInfo.realm]); + Controls.BattleNet.click(); + Starter.firstLogin && (Starter.firstLogin = false); + + break; + case sdk.game.profiletype.SinglePlayer: + default: + Controls.SinglePlayer.click(); + + break; + } + } + ], + [sdk.game.locations.MainMenuConnecting, + function (location) { + if (!Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, location)) { + Controls.LoginCancelWait.click(); + } + } + ], + [sdk.game.locations.OkCenteredErrorPopUp, + function () { + Controls.OkCentered.click(); + Controls.BottomLeftExit.click(); + } + ], + [sdk.game.locations.CharSelectNoChars, + function () { + Starter.LocationEvents.charSelectError(); + } + ], + [sdk.game.locations.CharSelectConnecting, + function () { + Starter.LocationEvents.charSelectError(); + } + ], + [sdk.game.locations.CharSelectPleaseWait, + function (location) { + if (!Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location)) { + Controls.OkCentered.click(); + } + } + ], + [sdk.game.locations.SelectDifficultySP, + function () { + Starter.LocationEvents.selectDifficultySP(); + } + ], + [sdk.game.locations.RealmDown, + function () { + Starter.LocationEvents.realmDown(); + } + ], + [sdk.game.locations.GameNameExists, + function () { + Controls.CreateGameWindow.click(); + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + } + ], + [sdk.game.locations.GameDoesNotExist, + function () { + Starter.LocationEvents.gameDoesNotExist(); + } + ], + [sdk.game.locations.CreateGame, + function (location) { + D2Bot.updateStatus("Creating Game"); + + if (typeof Starter.Config.CharacterDifference === "number") { + if (Controls.CharacterDifference.disabled === sdk.game.controls.Disabled) { + Controls.CharacterDifferenceButton.click(); + } + Controls.CharacterDifference.setText(Starter.Config.CharacterDifference.toString()); + } else if (!Starter.Config.CharacterDifference && Controls.CharacterDifference.disabled === 5) { + Controls.CharacterDifferenceButton.click(); + } + + if (typeof Starter.Config.MaxPlayerCount === "number") { + Controls.MaxPlayerCount.setText(Starter.Config.MaxPlayerCount.toString()); + } + + // Get game name if there is none + while (!Starter.gameInfo.gameName) { + D2Bot.requestGameInfo(); + delay(500); + } + + // FTJ handler + if (Starter.lastGameStatus === "pending") { + Starter.isUp = "no"; + D2Bot.printToConsole("Failed to create game"); + ControlAction.timeoutDelay("FTJ delay", Starter.Config.FTJDelay * 1e3); + D2Bot.updateRuns(); + } + + const gameName = (Starter.gameInfo.gameName === "?" + ? Starter.randomString(null, true) + : Starter.gameInfo.gameName + Starter.gameCount); + const gamePass = (Starter.gameInfo.gamePass === "?" + ? Starter.randomString(null, true) + : Starter.gameInfo.gamePass); + + ControlAction.createGame( + gameName, + gamePass, + Starter.gameInfo.difficulty, + Starter.Config.CreateGameDelay * 1000 + ); + + Starter.lastGameStatus = "pending"; + Starter.setNextGame(Starter.gameInfo); + Starter.locationTimeout(10000, location); + } + ], + [sdk.game.locations.WaitingInLine, + function () { + Starter.LocationEvents.waitingInLine(); + } + ], + [sdk.game.locations.TcpIp, + function () { + pType === sdk.game.profiletype.TcpIpHost + ? Controls.TcpIpHost.click() + : Controls.TcpIpCancel.click(); + } + ], + [sdk.game.locations.Lobby, + function () { + D2Bot.updateStatus("Lobby"); + + me.blockKeys = false; + Starter.loginRetry = 0; + !Starter.firstLogin && (Starter.firstLogin = true); + Starter.lastGameStatus === "pending" && (Starter.gameCount += 1); + + if (Starter.Config.PingQuitDelay && Starter.pingQuit) { + ControlAction.timeoutDelay("Ping Delay", Starter.Config.PingQuitDelay * 1e3); + Starter.pingQuit = false; + } + + if (Starter.Config.JoinChannel !== "" || Starter.Config.AnnounceGames) { + Controls.LobbyEnterChat.click(); + + return; + } + + if (Starter.inGame || Starter.gameInfo.error) { + !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick); + + Starter.isUp = "no"; + DataFile.updateStats("currentGame", ""); + if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { + ControlAction.timeoutDelay( + "Min game time wait", + Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount() + ); + } + } + + if (Starter.inGame) { + if (AutoMule.outOfGameCheck() + || TorchSystem.outOfGameCheck() + || Gambling.outOfGameCheck() + || CraftingSystem.outOfGameCheck()) { + return; + } + + console.log("updating runs"); + D2Bot.updateRuns(); + + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + + if (Starter.Config.ResetCount && Starter.gameCount > Starter.Config.ResetCount) { + Starter.gameCount = 1; + DataFile.updateStats("runs", Starter.gameCount); + } + } + + Starter.LocationEvents.openCreateGameWindow(); + } + ], + [sdk.game.locations.LobbyChat, + function () { + D2Bot.updateStatus("Lobby Chat"); + Starter.lastGameStatus === "pending" && (Starter.gameCount += 1); + + if (Starter.inGame || Starter.gameInfo.error) { + DataFile.updateStats("currentGame", ""); + !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick); + + if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3) { + ControlAction.timeoutDelay( + "Min game time wait", + Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount() + ); + } + } + + if (Starter.inGame) { + if (AutoMule.outOfGameCheck() + || TorchSystem.outOfGameCheck() + || Gambling.outOfGameCheck() + || CraftingSystem.outOfGameCheck()) { + return; + } + + console.log("updating runs"); + D2Bot.updateRuns(); + + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + Starter.inGame = false; + + if (Starter.Config.ResetCount && Starter.gameCount > Starter.Config.ResetCount) { + Starter.gameCount = 1; + DataFile.updateStats("runs", Starter.gameCount); + } + + Starter.chanInfo.afterMsg = Starter.Config.AfterGameMessage; + + // check that we are in the channel we are supposed to be in + if (Starter.chanInfo.joinChannel.length) { + let chanName = Controls.LobbyChannelName.getText(); + chanName && (chanName = chanName.toString()); + chanName && (chanName = chanName.slice(0, chanName.indexOf("(") - 1)); + Starter.chanInfo.joinChannel.indexOf(chanName) === -1 && (Starter.chatActionsDone = false); + } + + if (Starter.chanInfo.afterMsg) { + if (typeof Starter.chanInfo.afterMsg === "string") { + Starter.chanInfo.afterMsg = [Starter.chanInfo.afterMsg]; + } + + for (let msg of Starter.chanInfo.afterMsg) { + Starter.sayMsg(msg); + delay(500); + } + } + } + + if (!Starter.chatActionsDone) { + Starter.chatActionsDone = true; + Starter.chanInfo.joinChannel = Starter.Config.JoinChannel; + Starter.chanInfo.firstMsg = Starter.Config.FirstJoinMessage; + + if (Starter.chanInfo.joinChannel) { + if (typeof Starter.chanInfo.joinChannel === "string") { + Starter.chanInfo.joinChannel = [Starter.chanInfo.joinChannel]; + } + if (typeof Starter.chanInfo.firstMsg === "string") { + Starter.chanInfo.firstMsg = [Starter.chanInfo.firstMsg]; + } + + for (let i = 0; i < Starter.chanInfo.joinChannel.length; i += 1) { + ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3); + + if (ControlAction.joinChannel(Starter.chanInfo.joinChannel[i])) { + Starter.useChat = true; + } else { + console.warn("ÿc1Unable to join channel, disabling chat messages."); + Starter.useChat = false; + } + + if (Starter.chanInfo.firstMsg[i] !== "") { + Starter.sayMsg(Starter.chanInfo.firstMsg[i]); + delay(500); + } + } + } else if (Starter.Config.AnnounceGames) { + // announcing in public channel + Starter.useChat = true; + } + } + + // Announce game + Starter.chanInfo.announce = Starter.Config.AnnounceGames; + + Starter.LocationEvents.openCreateGameWindow(); + } + ], + ]); + addLocations([sdk.game.locations.PreSplash, sdk.game.locations.SplashScreen], + function (location) { + ControlAction.click(); + Starter.locationTimeout(5000, location); + getLocation() === sdk.game.locations.PreSplash && sendKey(0x0D); + } + ); + addLocations( + [ + sdk.game.locations.JoinGame, + sdk.game.locations.Ladder, + sdk.game.locations.ChannelList + ], + function () { + Starter.LocationEvents.openCreateGameWindow(); + } + ); + addLocations([sdk.game.locations.Login, sdk.game.locations.CharSelect], + function () { + const otherMulti = [ + sdk.game.profiletype.TcpIpHost, + sdk.game.profiletype.OpenBattlenet + ].includes(pType); + Starter.LocationEvents.login(otherMulti); + } + ); + addLocations([sdk.game.locations.CharSelectConnecting, sdk.game.locations.CharSelectNoChars], + function () { + Starter.LocationEvents.charSelectError(); + } + ); + addLocations([sdk.game.locations.LoginUnableToConnect, sdk.game.locations.TcpIpUnableToConnect], + function () { + Starter.LocationEvents.unableToConnect(); + } + ); + addLocations([sdk.game.locations.CharSelectPleaseWait, sdk.game.locations.LobbyPleaseWait], + function (location) { + if (!Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location)) { + Controls.OkCentered.click(); + } + } + ); + addLocations([sdk.game.locations.Disconnected, sdk.game.locations.LobbyLostConnection], + function () { + D2Bot.updateStatus("Disconnected/LostConnection"); + delay(1000); + Controls.OkCentered.click(); + } + ); + addLocations( + [ + sdk.game.locations.LoginError, + sdk.game.locations.InvalidCdKey, + sdk.game.locations.CdKeyInUse, + ], + function () { + Starter.LocationEvents.loginError(); + } + ); + addLocations( + [ + sdk.game.locations.CreateNewAccount, + sdk.game.locations.CharacterCreate, + sdk.game.locations.NewCharSelected, + ], + function () { + Controls.BottomLeftExit.click(); + } + ); + addLocations([sdk.game.locations.GameNameExists, sdk.game.locations.GameIsFull], + function () { + Controls.CreateGameWindow.click(); + Starter.gameCount += 1; + Starter.lastGameStatus = "ready"; + } + ); + + module.exports = { + locations: _loc, + addLocations: addLocations, + parseControlText: parseControlText, + }; +})(module); diff --git a/d2bs/kolbot/libs/oog/ShitList.js b/d2bs/kolbot/libs/oog/ShitList.js new file mode 100644 index 000000000..687bfd170 --- /dev/null +++ b/d2bs/kolbot/libs/oog/ShitList.js @@ -0,0 +1,105 @@ +/** +* @filename ShitList.js +* @author kolton, D3STROY3R, theBGuy +* @desc Maintain shitlist of griefers +* +*/ + +!isIncluded("Polyfill.js") && include("Polyfill.js"); +includeIfNotIncluded("oog/FileAction.js"); + +const ShitList = { + _default: { + /** @type {Array} */ + shitlist: [] + }, + _path: "logs/shitlist.json", + _list: new Set(), + + /** + * @private + * @returns {{ shitlist: Array }}} + */ + create: function () { + let string = JSON.stringify(this._default); + FileAction.write(this._path, string); + + return Object.assign({}, this._default); + }, + + reset: function () { + let string = JSON.stringify(this._default); + FileAction.write(this._path, string); + this._list.clear(); + + return Object.assign({}, this._default); + }, + + /** + * @private + * @returns {{ shitlist: Array }}} + */ + getObj: function () { + let obj; + let string = FileAction.read(this._path); + + try { + obj = JSON.parse(string); + } catch (e) { + obj = this.create(); + } + + if (obj) { + return obj; + } + + console.warn("Failed to read ShitList. Using null values"); + + return Object.assign({}, this._default); + }, + + /** @param {Array} name */ + read: function () { + if (!FileTools.exists(this._path)) { + return this.create().shitlist; + } + let obj = this.getObj(); + if (!this._list.size) { + obj.shitlist.forEach(name => this._list.add(name)); + } + return obj.shitlist; + }, + + /** @param {string} name */ + add: function (name) { + me.shitList.add(name); + let obj = this.getObj(); + if (obj.shitlist.includes(name)) return; + obj.shitlist.push(name); + this._list.add(name); + + let string = JSON.stringify(obj); + + FileAction.write(this._path, string); + }, + + /** @param {string} name */ + remove: function (name) { + me.shitList.delete(name); + let obj = this.getObj(); + let index = obj.shitlist.indexOf(name); + if (index === -1) return false; + obj.shitlist.splice(index, 1); + this._list.delete(name); + + let string = JSON.stringify(obj); + + FileAction.write(this._path, string); + return true; + }, + + /** @param {string} name */ + has: function (name) { + return this._list.has(name); + } +}; diff --git a/d2bs/kolbot/libs/require.js b/d2bs/kolbot/libs/require.js index 73eba1788..3fc8ba311 100644 --- a/d2bs/kolbot/libs/require.js +++ b/d2bs/kolbot/libs/require.js @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /* eslint-disable dot-notation */ /** * @filename require.js @@ -10,127 +11,135 @@ // noinspection ThisExpressionReferencesGlobalObjectJS <-- definition of global here typeof global === "undefined" && (this["global"] = this); -global["module"] = {exports: undefined}; +global["module"] = { exports: undefined }; global["exports"] = {}; function removeRelativePath(test) { - return test.replace(/\\/g, "/").split("/").reduce(function (acc, cur) { - if (!cur || cur === ".") return acc; - if (cur === "..") { - acc.pop(); - return acc; - } - acc.push(cur); - return acc; - - }, []).join("/"); + return test.replace(/\\/g, "/").split("/").reduce(function (acc, cur) { + if (!cur || cur === ".") return acc; + if (cur === "..") { + acc.pop(); + return acc; + } + acc.push(cur); + return acc; + + }, []).join("/"); } global.require = (function (include, isIncluded, print, notify) { - - - let depth = 0; - const modules = {}; - const obj = function require(field, path) { - const stack = new Error().stack.match(/[^\r\n]+/g); - let directory = stack[1].match(/.*?@.*?d2bs\\(kolbot\\?.*)\\.*(\.js|\.dbj):/)[1].replace("\\", "/") + "/"; - let filename = stack[1].match(/.*?@.*?d2bs\\kolbot\\?(.*)(\.js|\.dbj):/)[1]; - filename = filename.substr(filename.length - filename.split("").reverse().join("").indexOf("\\")); - // remove the name kolbot of the file - if (directory.startsWith("kolbot")) { - directory = directory.substr("kolbot".length); - } - - // remove the / from it - if (directory.startsWith("/")) { - directory = directory.substr(1); - } - - // strip off lib - if (directory.startsWith("lib")) { - directory = directory.substr(4); - } else { - directory = "../" + directory; // Add a extra recursive path, as we start out of the lib directory - } - - - path = path || directory; - - let fullpath = removeRelativePath((path + field).replace(/\\/, "/")).toLowerCase(); - // remove lib again, if required in e.g. kolbot\tools but wants modules\whatever - if (fullpath.startsWith("lib")) { - fullpath = fullpath.substr(4); - } - const packageName = fullpath; - - const asNew = this.__proto__.constructor === require && ((...args) => new (Function.prototype.bind.apply(modules[packageName].exports, args))); - - if (field.hasOwnProperty("endsWith") && field.endsWith(".json")) { // Simply reads a json file - return modules[packageName] = File.open("libs/" + path + field, 0).readAllLines(); - } - - const moduleNameShort = (fullpath + ".js").match(/.*?\/([^\/]*).js$/)[1]; - - if (!isIncluded(fullpath + ".js") && !modules.hasOwnProperty(moduleNameShort)) { - depth && notify && print("ÿc2Kolbotÿc0 :: - loading dependency of " + filename + ": " + moduleNameShort); - !depth && notify && print("ÿc2Kolbotÿc0 :: Loading module: " + moduleNameShort); - - let oldModule = Object.create(global["module"]); - let oldExports = Object.create(global["exports"]); - delete global["module"]; - delete global["exports"]; - global["module"] = {exports: null}; - global["exports"] = {}; - - // Include the file; - try { - depth++; - if (!include(fullpath + ".js")) { - const err = new Error("module " + fullpath + " not found"); - - // Rewrite the location of the error, to be more clear for the developer/user _where_ it crashes - const myStack = err.stack.match(/[^\r\n]+/g); - err.fileName = directory + myStack[1].match(/.*?@.*?d2bs\\kolbot\\?(.*)(\.js|\.dbj):/)[1]; - err.lineNumber = myStack[1].substr(stack[1].lastIndexOf(":") + 1); - myStack.unshift(); - err.stack = myStack.join("\r\n"); // rewrite stack - - throw err; - } - } finally { - depth--; - } - - if (!global["module"]["exports"] && Object.keys(global["exports"])) { // Incase its transpiled typescript - global["module"]["exports"] = global["exports"]; - } - - modules[packageName] = Object.create(global["module"]); - delete global["module"]; - delete global["exports"]; - global["module"] = oldModule; - global["exports"] = oldExports; - } - - if (!modules.hasOwnProperty(packageName)) throw Error("unexpected module error -- " + field); - - // If called as "new", fake an constructor - return asNew || modules[packageName].exports; - }; - obj.modules = modules; - return obj; + const debug = false; + + let depth = 0; + const modules = {}; + const obj = function require(field, path) { + const stack = new Error().stack.match(/[^\r\n]+/g); + let directory = stack[1].match(/.*?@.*?d2bs\\(kolbot\\?.*)\\.*(\.js|\.dbj):/)[1].replace("\\", "/") + "/"; + let filename = stack[1].match(/.*?@.*?d2bs\\kolbot\\?(.*)(\.js|\.dbj):/)[1]; + filename = filename.substr(filename.length - filename.split("").reverse().join("").indexOf("\\")); + // remove the name kolbot of the file + if (directory.startsWith("kolbot")) { + directory = directory.substr("kolbot".length); + } + + // remove the / from it + if (directory.startsWith("/")) { + directory = directory.substr(1); + } + + // strip off lib + if (directory.startsWith("lib")) { + directory = directory.substr(4); + } else { + directory = "../" + directory; // Add a extra recursive path, as we start out of the lib directory + } + + path = path || directory; + + let fullpath = removeRelativePath((path + field).replace(/\\/, "/")).toLowerCase(); + // remove lib again, if required in e.g. kolbot\tools but wants modules\whatever + if (fullpath.startsWith("lib")) { + fullpath = fullpath.substr(4); + } + const packageName = fullpath; + + const asNew = this.__proto__.constructor === require && ((...args) => new (Function.prototype.bind.apply(modules[packageName].exports, args))); + + if (field.hasOwnProperty("endsWith") && field.endsWith(".json")) { // Simply reads a json file + return modules[packageName] = File.open("libs/" + path + field, 0).readAllLines(); + } + + let nameShort; + try { + nameShort = (fullpath + ".js").match(/.*?\/([^/]*).js$/)[1]; + } catch (e) { + // file in libs folder same as us + nameShort = (fullpath + ".js").match(/.*?\/([^/]*).js$/)[0]; + } + const moduleNameShort = nameShort; + + if (!isIncluded(fullpath + ".js") && !modules.hasOwnProperty(moduleNameShort)) { + if (debug) { + depth && notify && print("ÿc2Kolbotÿc0 :: - loading dependency of " + filename + ": " + moduleNameShort); + !depth && notify && print("ÿc2Kolbotÿc0 :: Loading module: " + moduleNameShort); + } + + let oldModule = Object.create(global["module"]); + let oldExports = Object.create(global["exports"]); + delete global["module"]; + delete global["exports"]; + global["module"] = { exports: null }; + global["exports"] = {}; + + // Include the file; + try { + depth++; + if (!include(fullpath + ".js")) { + const err = new Error("module " + fullpath + " not found"); + + // Rewrite the location of the error, to be more clear for the developer/user _where_ it crashes + const myStack = err.stack.match(/[^\r\n]+/g); + err.fileName = directory + myStack[1].match(/.*?@.*?d2bs\\kolbot\\?(.*)(\.js|\.dbj):/)[1]; + err.lineNumber = myStack[1].substr(stack[1].lastIndexOf(":") + 1); + myStack.unshift(); + err.stack = myStack.join("\r\n"); // rewrite stack + + throw err; + } + } finally { + depth--; + } + + if (!global["module"]["exports"] && Object.keys(global["exports"])) { // Incase its transpiled typescript + global["module"]["exports"] = global["exports"]; + } + + modules[packageName] = Object.create(global["module"]); + delete global["module"]; + delete global["exports"]; + global["module"] = oldModule; + global["exports"] = oldExports; + } + + if (!modules.hasOwnProperty(packageName)) throw Error("unexpected module error -- " + field); + + // If called as "new", fake an constructor + return asNew || modules[packageName].exports; + }; + obj.modules = modules; + return obj; })(include, isIncluded, print, getScript(true).name.toLowerCase().split("").reverse().splice(0, ".dbj".length).reverse().join("") === ".dbj"); getScript.startAsThread = function () { - let stack = new Error().stack.match(/[^\r\n]+/g), - filename = stack[1].match(/.*?@.*?d2bs\\kolbot\\(.*):/)[1]; + let stack = new Error().stack.match(/[^\r\n]+/g), + filename = stack[1].match(/.*?@.*?d2bs\\kolbot\\(.*):/)[1]; - if (getScript(true).name.toLowerCase() === filename.toLowerCase()) { - return "thread"; - } + if (getScript(true).name.toLowerCase() === filename.toLowerCase()) { + return "thread"; + } - if (!getScript(filename)) { - load(filename); - return "started"; - } + if (!getScript(filename)) { + load(filename); + return "started"; + } - return "loaded"; + return "loaded"; }; diff --git a/d2bs/kolbot/libs/scripts/Abaddon.js b/d2bs/kolbot/libs/scripts/Abaddon.js new file mode 100644 index 000000000..6735b6870 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Abaddon.js @@ -0,0 +1,25 @@ +/** +* @filename Abaddon.js +* @author kolton +* @desc clear Abaddon +* +*/ + +const Abaddon = new Runnable( + function Abaddon () { + Pather.useWaypoint(sdk.areas.FrigidHighlands); + Precast.doPrecast(true); + + if (!Pather.moveToPresetObject(sdk.areas.FrigidHighlands, sdk.objects.RedPortal) + || !Pather.usePortal(sdk.areas.Abaddon)) { + throw new Error("Failed to move to Abaddon"); + } + + Attack.clearLevel(Config.ClearType); + + return true; + }, + { + startArea: sdk.areas.FrigidHighlands + } +); diff --git a/d2bs/kolbot/libs/scripts/AncientTunnels.js b/d2bs/kolbot/libs/scripts/AncientTunnels.js new file mode 100644 index 000000000..0e4d53ba6 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/AncientTunnels.js @@ -0,0 +1,39 @@ +/** +* @filename AncientTunnels.js +* @author kolton +* @desc clear Ancient Tunnels +* +*/ + +const AncientTunnels = new Runnable( + function AncientTunnels () { + Pather.useWaypoint(sdk.areas.LostCity); + Precast.doPrecast(true); + + try { + if (Config.AncientTunnels.OpenChest && Pather.moveToPresetObject(me.area, sdk.objects.SuperChest)) { + Misc.openChests(5) && Pickit.pickItems(); + } + } catch (e) { + console.error(e); + } + + try { + if (Config.AncientTunnels.KillDarkElder + && !Attack.haveKilled(getLocaleString(sdk.locale.monsters.DarkElder)) + && Pather.moveToPresetMonster(me.area, sdk.monsters.preset.DarkElder)) { + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.DarkElder)); + } + } catch (e) { + console.error(e); + } + + if (!Pather.moveToExit(sdk.areas.AncientTunnels, true)) throw new Error("Failed to move to Ancient Tunnels"); + Attack.clearLevel(Config.ClearType); + + return true; + }, + { + startArea: sdk.areas.LostCity + } +); diff --git a/d2bs/kolbot/libs/scripts/Andariel.js b/d2bs/kolbot/libs/scripts/Andariel.js new file mode 100644 index 000000000..bcc729d47 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Andariel.js @@ -0,0 +1,47 @@ +/** +* @filename Andariel.js +* @author kolton, theBGuy +* @desc kill Andariel +* +*/ + +const Andariel = new Runnable( + function Andariel () { + const killAndariel = function () { + let target = Game.getMonster(sdk.monsters.Andariel); + if (!target) throw new Error("Andariel not found."); + + Config.MFLeader && Pather.makePortal() && say("kill " + sdk.monsters.Andariel); + + for (let i = 0; i < 300 && target.attackable; i += 1) { + ClassAttack.doAttack(target); + target.distance <= 10 && Pather.moveTo(me.x > 22548 ? 22535 : 22560, 9520); + } + + return target.dead; + }; + + Pather.useWaypoint(sdk.areas.CatacombsLvl2); + Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true)) { + throw new Error("Failed to move to Catacombs Level 4"); + } + + Pather.move(new PathNode(22549, 9520), { callback: function () { + return Attack._killed.has(sdk.monsters.Andariel); + } }); + me.sorceress && me.classic + ? killAndariel() + : Attack.kill(sdk.monsters.Andariel); + + delay(2000); // Wait for minions to die. + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.CatacombsLvl2, + bossid: sdk.monsters.Andariel, + } +); diff --git a/d2bs/kolbot/libs/scripts/AutoBaal.js b/d2bs/kolbot/libs/scripts/AutoBaal.js new file mode 100644 index 000000000..13f6707de --- /dev/null +++ b/d2bs/kolbot/libs/scripts/AutoBaal.js @@ -0,0 +1,291 @@ +/** +* @filename AutoBaal.js +* @author kolton +* @desc Universal Baal leecher by Kolton with Autoleader by Ethic +* Pure leech script for throne and Baal +* Reenters throne/chamber upon death and picks the corpse back up +* Make sure you setup safeMsg and baalMsg accordingly +* +*/ + +/** +* @todo: +* - add silent follow support +* - needs to be in a way that doesn't interfere with normal following +* - should this listen for baal death packet? +*/ + +const AutoBaal = new Runnable( + function AutoBaal () { + // internal variables + let baalCheck, throneCheck, hotCheck, leader; // internal variables + let hotTick = 0; + const safeMsg = ["safe", "throne clear", "leechers can come", "tp is up", "1 clear"]; // safe message - casing doesn't matter + const baalMsg = ["baal"]; // baal message - casing doesn't matter + const hotMsg = ["hot", "warm", "dangerous", "lethal"]; // used for shrine hunt + + [safeMsg, baalMsg, hotMsg].forEach((function (arr) { + for (let i = 0; i < arr.length; i++) { + arr[i] = arr[i].toLowerCase(); + } + })); + + /** + * chat event handler function, listen to what leader says + * @param {string} nick + * @param {string} msg + */ + const chatEvent = function (nick, msg) { + // filter leader messages + if (!nick || !msg || nick !== leader) return; + msg = msg.toLowerCase(); + + // loop through all predefined messages to find a match + for (let str of hotMsg) { + // leader says a hot tp message + if (msg.includes(str)) { + hotCheck = true; // not safe to enter baal chamber + hotTick = getTickCount(); + + return; + } + } + + // loop through all predefined messages to find a match + for (let str of safeMsg) { + // leader says a safe tp message + if (msg.includes(str)) { + throneCheck = true; // safe to enter throne + + return; + } + } + + // loop through all predefined messages to find a match + for (let str of baalMsg) { + // leader says a baal message + if (msg.includes(str)) { + baalCheck = true; // safe to enter baal chamber + + return; + } + } + }; + + /** + * @todo maybe factor this out and make it useable for other leecher scripts? + */ + const longRangeSupport = function () { + switch (me.classid) { + case sdk.player.class.Necromancer: + ClassAttack.raiseArmy(50); + + if (Config.Curse[1] > 0) { + let monster = Game.getMonster(); + + if (monster) { + do { + if (monster.attackable && monster.distance < 50 && !checkCollision(me, monster, sdk.collision.Ranged) + && monster.curseable && !monster.isSpecial && ClassAttack.canCurse(monster, Config.Curse[1])) { + Skill.cast(Config.Curse[1], sdk.skills.hand.Right, monster); + } + } while (monster.getNext()); + } + } + + break; + case sdk.player.class.Assassin: + if (Config.UseTraps && ClassAttack.checkTraps({ x: 15095, y: 5037 })) { + ClassAttack.placeTraps({ x: 15095, y: 5037 }, 5); + } + + break; + default: + break; + } + + let skills = [ + sdk.skills.ChargedStrike, sdk.skills.Lightning, sdk.skills.FireWall, sdk.skills.Meteor, sdk.skills.Blizzard, + sdk.skills.BoneSpear, sdk.skills.BoneSpirit, sdk.skills.DoubleThrow, sdk.skills.Volcano + ]; + + if (!skills.some(skill => Config.AttackSkill[1] === skill || Config.AttackSkill[3] === skill)) { + return false; + } + + let monster = Game.getMonster(); + let monList = []; + + if (monster) { + do { + if (monster.attackable && monster.distance < 50 && !checkCollision(me, monster, sdk.collision.Ranged)) { + monList.push(copyUnit(monster)); + } + } while (monster.getNext()); + } + + if (me.inArea(sdk.areas.ThroneofDestruction)) { + [15116, 5026].distance > 10 && Pather.moveTo(15116, 5026); + } + + let oldVal = Skill.usePvpRange; + Skill.usePvpRange = true; + + try { + while (monList.length) { + monList.sort(Sort.units); + monster = copyUnit(monList[0]); + + if (monster && monster.attackable) { + let index = monster.isSpecial ? 1 : 3; + + if (Config.AttackSkill[index] > -1 + && Attack.checkResist(monster, Attack.getSkillElement(Config.AttackSkill[index]))) { + ClassAttack.doCast(monster, Config.AttackSkill[index], Config.AttackSkill[index + 1]); + } else { + monList.shift(); + } + } else { + monList.shift(); + } + + delay(5); + } + } finally { + Skill.usePvpRange = oldVal; + } + + return true; + }; + + // critical error - can't reach harrogath + if (!Town.goToTown(5)) throw new ScriptError("Town.goToTown failed."); + + if (Config.Leader) { + leader = Config.Leader; + if (!Misc.poll(() => Misc.inMyParty(leader), Time.seconds(30), Time.seconds(1))) { + throw new ScriptError("AutoBaal: Leader not partied"); + } + } + + try { + addEventListener("chatmsg", chatEvent); + Config.AutoBaal.FindShrine === 2 && (hotCheck = true); + + Town.doChores(); + Town.move("portalspot"); + + // find the first player in throne of destruction + if (leader || (leader = Misc.autoLeaderDetect({ + destination: sdk.areas.ThroneofDestruction, + quitIf: (area) => [sdk.areas.WorldstoneChamber].includes(area) + }))) { + const start = getTickCount(); + // do our stuff while partied + while (Misc.inMyParty(leader)) { + if (!throneCheck && !baalCheck && getTickCount() - start > Time.seconds(90)) { + // no signal? Lets set it ourselves and check things out + console.log("ÿc4AutoBaal: ÿc0No signal from leader, setting throne signal."); + throneCheck = true; + } + + if (!baalCheck && Misc.getPlayerArea(leader) === sdk.areas.WorldstoneChamber) { + console.log("ÿc4AutoBaal: ÿc0Leader is in Baal chamber, setting baal signal."); + baalCheck = true; + } + + if (hotCheck) { + if (Config.AutoBaal.FindShrine) { + let i; + Pather.useWaypoint(sdk.areas.StonyField); + Precast.doPrecast(true); + + for (i = sdk.areas.StonyField; i > 1; i--) { + if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { + break; + } + } + + if (i === 1) { + Town.goToTown(); + Pather.useWaypoint(sdk.areas.DarkWood); + + for (i = sdk.areas.DarkWood; i < sdk.areas.DenofEvil; i++) { + if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { + break; + } + } + } + Town.goToTown(5); + Town.move("portalspot"); + + hotCheck = false; + } else if (getTickCount() - hotTick > Time.seconds(30)) { + // maybe we missed the message, go ahead and enter throne + if (!throneCheck && !baalCheck) { + throneCheck = true; + hotCheck = false; + } + } + } + + // wait for throne signal - leader's safe message + if ((throneCheck || baalCheck) && me.inArea(sdk.areas.Harrogath)) { + console.log("ÿc4AutoBaal: ÿc0Trying to take TP to throne."); + Pather.usePortal(sdk.areas.ThroneofDestruction, null); + // move to a safe spot + Pather.moveTo(Config.AutoBaal.LeechSpot[0], Config.AutoBaal.LeechSpot[1]); + Precast.doPrecast(true); + Town.getCorpse(); + } + + if (!baalCheck && me.inArea(sdk.areas.ThroneofDestruction) && Config.AutoBaal.LongRangeSupport) { + longRangeSupport(); + } + // wait for baal signal - leader's baal message + if (baalCheck && me.inArea(sdk.areas.ThroneofDestruction)) { + // move closer to chamber portal + Pather.moveTo(15092, 5010); + Precast.doPrecast(false); + + // wait for baal to go through the portal + while (Game.getMonster(sdk.monsters.ThroneBaal)) { + delay(500); + } + + let portal = Game.getObject(sdk.objects.WorldstonePortal); + + delay(2000); // wait for others to enter first - helps with curses and tentacles from spawning around you + console.log("ÿc4AutoBaal: ÿc0Entering chamber."); + Pather.usePortal(null, null, portal) && Pather.moveTo(15166, 5903); // go to a safe position + Town.getCorpse(); + } + + let baal = Game.getMonster(sdk.monsters.Baal); + + if (baal) { + if (baal.dead) { + break; + } + + longRangeSupport(); + } + + me.mode === sdk.player.mode.Dead && me.revive(); + + delay(500); + } + } else { + throw new Error("Empty game."); + } + } finally { + removeEventListener("chatmsg", chatEvent); + } + + return true; + }, + { + startArea: sdk.areas.Harrogath, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Baal.js b/d2bs/kolbot/libs/scripts/Baal.js new file mode 100644 index 000000000..91351edce --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Baal.js @@ -0,0 +1,102 @@ +/** +* @filename Baal.js +* @author kolton, YGM, theBGuy +* @desc clear Throne of Destruction and kill Baal +* +*/ + +const Baal = new Runnable( + function Baal () { + include("core/Common/Baal.js"); + const announce = function () { + let count, string, souls, dolls; + let monster = Game.getMonster(); + + if (monster) { + count = 0; + + do { + if (monster.attackable && monster.y < 5094) { + monster.distance <= 40 && (count += 1); + !souls && monster.classid === sdk.monsters.BurningSoul1 && (souls = true); + !dolls && monster.classid === sdk.monsters.SoulKiller && (dolls = true); + } + } while (monster.getNext()); + } + + if (count > 30) { + string = "DEADLY!!!" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; + } else if (count > 20) { + string = "Lethal!" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; + } else if (count > 10) { + string = "Dangerous!" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; + } else if (count > 0) { + string = "Warm" + " " + count + " monster" + (count > 1 ? "s " : " ") + "nearby."; + } else { + string = "Cool TP. No immediate monsters."; + } + + if (souls) { + string += " Souls "; + dolls && (string += "and Dolls "); + string += "in area."; + } else if (dolls) { + string += " Dolls in area."; + } + + say(string); + }; + + if (!me.inArea(sdk.areas.WorldstoneLvl2)) { + Config.RandomPrecast + ? Precast.doRandomPrecast(true, sdk.areas.WorldstoneLvl2) + : Pather.useWaypoint(sdk.areas.WorldstoneLvl2) && Precast.doPrecast(true); + !me.inArea(sdk.areas.WorldstoneLvl2) && Pather.useWaypoint(sdk.areas.WorldstoneLvl2); + } + + if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], true)) { + throw new Error("Failed to move to Throne of Destruction."); + } + + Pather.moveToEx(15095, 5029, { callback: () => { + if (Config.Baal.DollQuit && Game.getMonster(sdk.monsters.SoulKiller)) { + say("Dolls found! NG."); + throw new ScriptError("Dolls found! NG."); + } + + if (Config.Baal.SoulQuit && Game.getMonster(sdk.monsters.BurningSoul1)) { + say("Souls found! NG."); + throw new ScriptError("Souls found! NG."); + } + } }); + + if (Config.PublicMode) { + announce(); + Pather.moveTo(15118, 5002); + Pather.makePortal(); + say(Config.Baal.HotTPMessage); + Attack.clear(15); + } + + Common.Baal.clearThrone(); + + if (Config.PublicMode) { + Pather.moveTo(15118, 5045); + Pather.makePortal(); + say(Config.Baal.SafeTPMessage); + Precast.doPrecast(true); + } + + if (!Common.Baal.clearWaves()) { + throw new Error("Couldn't clear baal waves"); + } + + Config.Baal.KillBaal && Common.Baal.killBaal(); + + return true; + }, + { + startArea: sdk.areas.WorldstoneLvl2, + bossid: sdk.monsters.Baal, + } +); diff --git a/d2bs/kolbot/libs/scripts/BaalAssistant.js b/d2bs/kolbot/libs/scripts/BaalAssistant.js new file mode 100644 index 000000000..1891e3e42 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BaalAssistant.js @@ -0,0 +1,508 @@ +/** +* @filename BaalAssistant.js +* @author kolton, YGM, theBGuy +* @desc Help or Leech Baal Runs. +* +*/ + +/** + * @todo + * - combine autobaal, baalhelper, and baalassistant into one script + * - track leaders area so we can do silent follow + * - override Misc.getShrinesInArea to end when we recieve safeCheck message + */ + +const BaalAssistant = new Runnable( + function BaalAssistant () { + include("core/Common/Baal.js"); + let Leader = Config.Leader; + let Helper = Config.BaalAssistant.Helper; + let firstAttempt = true; + let quitFlag = false; + let [hotCheck, safeCheck, baalCheck, ngCheck, baalIsDead] = [false, false, false, false, false]; + let [ShrineStatus, secondAttempt, throneStatus, killTracker] = [false, false, false, false]; + + // convert all messages to lowercase + Config.BaalAssistant.HotTPMessage.forEach((msg, i) => { + Config.BaalAssistant.HotTPMessage[i] = msg.toLowerCase(); + }); + + Config.BaalAssistant.SafeTPMessage.forEach((msg, i) => { + Config.BaalAssistant.SafeTPMessage[i] = msg.toLowerCase(); + }); + + Config.BaalAssistant.BaalMessage.forEach((msg, i) => { + Config.BaalAssistant.BaalMessage[i] = msg.toLowerCase(); + }); + + Config.BaalAssistant.NextGameMessage.forEach((msg, i) => { + Config.BaalAssistant.NextGameMessage[i] = msg.toLowerCase(); + }); + + const chatEvent = function (nick, msg) { + if (nick === Leader) { + if ((Config.BaalAssistant.DollQuit && msg === "Dolls found! NG.") + || (Config.BaalAssistant.SoulQuit && msg === "Souls found! NG.")) { + quitFlag = true; + + return; + } + + msg = msg.toLowerCase(); + + for (let i = 0; i < Config.BaalAssistant.HotTPMessage.length; i += 1) { + if (msg.includes(Config.BaalAssistant.HotTPMessage[i])) { + hotCheck = true; + break; + } + } + + for (let i = 0; i < Config.BaalAssistant.SafeTPMessage.length; i += 1) { + if (msg.includes(Config.BaalAssistant.SafeTPMessage[i])) { + safeCheck = true; + break; + } + } + + for (let i = 0; i < Config.BaalAssistant.BaalMessage.length; i += 1) { + if (msg.includes(Config.BaalAssistant.BaalMessage[i])) { + baalCheck = true; + break; + } + } + + for (let i = 0; i < Config.BaalAssistant.NextGameMessage.length; i += 1) { + if (msg.includes(Config.BaalAssistant.NextGameMessage[i])) { + ngCheck = true; + killTracker = true; + break; + } + } + } + }; + + const baalDeathEvent = function (bytes = []) { + if (!bytes.length || bytes.length !== 2) return; + + if (bytes[0] === sdk.packets.recv.UniqueEvents && bytes[1] === 0x13) { + baalIsDead = true; + } + }; + + const checkParty = function () { + for (let i = 0; i < Config.BaalAssistant.Wait; i += 1) { + let partycheck = getParty(); + if (partycheck) { + do { + if (partycheck.area === sdk.areas.ThroneofDestruction) return false; + if (partycheck.area === sdk.areas.RiverofFlame || partycheck.area === sdk.areas.ChaosSanctuary) return true; + } while (partycheck.getNext()); + } + + delay(1000); + } + + return false; + }; + + // Start + const Worker = require("../modules/Worker"); + + if (Leader) { + if (!Misc.poll(() => Misc.inMyParty(Leader), Time.seconds(30), 1000)) { + throw new Error("BaalAssistant: Leader not partied"); + } + } + + try { + addEventListener("chatmsg", chatEvent); + + let killLeaderTracker = false; + if (!Leader && (Config.BaalAssistant.KillNihlathak || Config.BaalAssistant.FastChaos)) { + // run background auto detect so we don't miss messages while running add ons + let leadTick = getTickCount(); + + Worker.runInBackground.leaderTracker = function () { + if (killLeaderTracker || killTracker) return false; + // check every 3 seconds + if (getTickCount() - leadTick < 3000) return true; + leadTick = getTickCount(); + + // check again in another 3 seconds if game wasn't ready + if (!me.gameReady) return true; + if (Misc.getPlayerCount() <= 1) return false; + + let party = getParty(); + + if (party) { + do { + // Player is in Throne of Destruction or Worldstone Chamber + if ([sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(party.area)) { + Leader = party.name; + console.log(sdk.colors.DarkGold + "Autodected " + Leader); + return false; + } + } while (party.getNext()); + } + + return true; + }; + } + + Config.BaalAssistant.KillNihlathak && Loader.runScript("Nihlathak"); + Config.BaalAssistant.FastChaos && Loader.runScript("Diablo", () => Config.Diablo.Fast = true); + + Town.goToTown(5); + Town.doChores(); + + if (Leader + || (Leader = Misc.autoLeaderDetect({ + destination: sdk.areas.WorldstoneLvl3, + quitIf: (area) => [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(area) + })) + || (Leader = Misc.autoLeaderDetect({ + destination: sdk.areas.ThroneofDestruction, + quitIf: (area) => [sdk.areas.WorldstoneChamber].includes(area) + }))) { + print("ÿc hotCheck, Time.seconds(Config.BaalAssistant.Wait), 1000); + + if (!hotCheck) { + print("ÿc1Leader didn't tell me to start hunting for an experience shrine."); + ShrineStatus = true; + } + } + + // don't waste time looking for seal if party is already killing baal, he'll be dead by the time we find one + if (!ShrineStatus && !baalCheck) { + Pather.useWaypoint(sdk.areas.StonyField); + Precast.doPrecast(true); + let i; + + for (i = sdk.areas.StonyField; i > sdk.areas.RogueEncampment; i -= 1) { + if (safeCheck) { + break; + } + if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { + break; + } + } + + if (!safeCheck) { + if (i === sdk.areas.RogueEncampment) { + Town.goToTown(); + Pather.useWaypoint(sdk.areas.DarkWood); + Precast.doPrecast(true); + + for (i = sdk.areas.DarkWood; i < sdk.areas.DenofEvil; i += 1) { + if (safeCheck) { + break; + } + if (Misc.getShrinesInArea(i, sdk.shrines.Experience, true)) { + break; + } + } + } + } + } + + Town.goToTown(5); + ShrineStatus = true; + } + + if (firstAttempt && !secondAttempt && !safeCheck + && !baalCheck && !me.inArea(sdk.areas.ThroneofDestruction) + && !me.inArea(sdk.areas.WorldstoneChamber)) { + !!Config.RandomPrecast + ? Precast.doRandomPrecast(true, sdk.areas.WorldstoneLvl2) + : Pather.useWaypoint(sdk.areas.WorldstoneLvl2) && Precast.doPrecast(true); + } + + if (!me.inArea(sdk.areas.ThroneofDestruction) && !me.inArea(sdk.areas.WorldstoneChamber)) { + if (Config.BaalAssistant.SkipTP) { + if (firstAttempt && !secondAttempt) { + !me.inArea(sdk.areas.WorldstoneLvl2) && Pather.useWaypoint(sdk.areas.WorldstoneLvl2); + if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], false)) { + throw new Error("Failed to move to WSK3."); + } + + checkParty(); + let entrance = Misc.poll(() => Game.getStairs(sdk.exits.preset.NextAreaWorldstone), 1000, 200); + if (entrance) { + let [x, y] = [ + entrance.x > me.x ? entrance.x - 5 : entrance.x + 5, + entrance.y > me.y ? entrance.y - 5 : entrance.y + 5 + ]; + Pather.moveTo(x, y); + } + + if (!Pather.moveToExit(sdk.areas.WorldstoneLvl3, true) || !Pather.moveTo(15118, 5002)) { + throw new Error("Failed to move to Throne of Destruction."); + } + + Pather.moveToEx(15095, 5029, { callback: () => { + if (Config.BaalAssistant.DollQuit && Game.getMonster(sdk.monsters.SoulKiller)) { + console.log("Undead Soul Killers found, ending script."); + throw new ScriptError("Undead Soul Killers found, ending script."); + } + + if (Config.BaalAssistant.SoulQuit && Game.getMonster(sdk.monsters.BurningSoul1)) { + console.log("Burning Souls found, ending script."); + throw new ScriptError("Burning Souls found, ending script."); + } + } }); + + Pather.moveTo(15118, 5002); + Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); + + secondAttempt = true; + safeCheck = true; + } else { + if (me.inTown) { + Town.move("portalspot"); + Pather.usePortal(sdk.areas.ThroneofDestruction, null); + Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); + } + } + } else { + if (firstAttempt && !secondAttempt) { + !me.inArea(sdk.areas.Harrogath) && Pather.useWaypoint(sdk.areas.Harrogath); + Town.move("portalspot"); + + if (Config.BaalAssistant.WaitForSafeTP + && !Misc.poll(() => safeCheck, Time.seconds(Config.BaalAssistant.Wait), 1000)) { + throw new Error("No safe TP message."); + } + + if ((Config.BaalAssistant.SoulQuit || Config.BaalAssistant.DollQuit) && quitFlag) { + throw new ScriptError("Burning Souls or Undead Soul Killers found, ending script."); + } + + if (!Misc.poll( + () => Pather.usePortal(sdk.areas.ThroneofDestruction, null), + Time.seconds(Config.BaalAssistant.Wait), + 1000 + )) { + throw new Error("No portals to Throne."); + } + + if ((Config.BaalAssistant.SoulQuit + && Game.getMonster(sdk.monsters.BurningSoul1)) + || (Config.BaalAssistant.DollQuit && Game.getMonster(sdk.monsters.SoulKiller))) { + throw new ScriptError("Burning Souls or Undead Soul Killers found, ending script."); + } + + Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); + secondAttempt = true; + safeCheck = true; + } else { + if (me.inTown) { + Town.move("portalspot"); + Pather.usePortal(sdk.areas.ThroneofDestruction, null); + Helper ? Attack.clear(15) && Pather.moveTo(15118, 5002) : Pather.moveTo(15117, 5045); + } + } + } + } + + if (safeCheck && !baalCheck && me.inArea(sdk.areas.ThroneofDestruction)) { + if (!baalCheck && !throneStatus) { + if (Helper) { + Attack.clear(15); + Common.Baal.clearThrone(); + Pather.moveTo(15094, me.paladin ? 5029 : 5038); + Precast.doPrecast(true); + } + + let tick = getTickCount(); + + MainLoop: while (true) { + if (Helper) { + if (getDistance(me, 15094, me.paladin ? 5029 : 5038) > 3) { + Pather.moveTo(15094, me.paladin ? 5029 : 5038); + } + } + + if (!Game.getMonster(sdk.monsters.ThroneBaal)) { + break; + } + + switch (Common.Baal.checkThrone(Helper)) { + case 1: + Helper && Attack.clear(40); + tick = getTickCount(); + + break; + case 2: + Helper && Attack.clear(40); + tick = getTickCount(); + + break; + case 4: + Helper && Attack.clear(40); + tick = getTickCount(); + + break; + case 3: + Helper && Attack.clear(40) && Common.Baal.checkHydra(); + tick = getTickCount(); + + break; + case 5: + if (Helper) { + Attack.clear(40); + } else { + while ([sdk.monsters.ListerTheTormenter, sdk.monsters.Minion1, sdk.monsters.Minion2] + .map((unitId) => Game.getMonster(unitId)) + .filter(Boolean).some((unit) => unit.attackable)) { + delay(1000); + } + + delay(1000); + } + + break MainLoop; + default: + if (getTickCount() - tick < 7e3) { + if (me.paladin && me.getState(sdk.states.Poison) + && Skill.setSkill(sdk.skills.Cleansing, sdk.skills.hand.Right)) { + break; + } + } + + if (Helper && !Common.Baal.preattack()) { + delay(100); + } + + break; + } + delay(10); + } + throneStatus = true; + baalCheck = true; + } + } + + if ((throneStatus || baalCheck) + && Config.BaalAssistant.KillBaal + && me.inArea(sdk.areas.ThroneofDestruction)) { + Helper ? Pather.moveTo(15090, 5008) && delay(2000) : Pather.moveTo(15090, 5010); + Precast.doPrecast(true); + !Helper && addEventListener("gamepacket", baalDeathEvent); + + while (Game.getMonster(sdk.monsters.ThroneBaal)) { + delay(500); + } + + let portal = Game.getObject(sdk.objects.WorldstonePortal); + + if (portal) { + delay((Helper ? 1000 : 4000)); + Pather.usePortal(null, null, portal); + } else { + throw new Error("Couldn't find portal."); + } + + if (Helper) { + delay(1000); + Pather.moveTo(15134, 5923); + Attack.kill(sdk.monsters.Baal); + Pickit.pickItems(); + } else { + Pather.moveTo(15177, 5952); + let baal = Game.getMonster(sdk.monsters.Baal); + + while (!!baal && baal.attackable && !baalIsDead) { + delay(1000); + } + } + + } else { + // how to accurately know when to end script in the instance of no ngCheck + // listen for baal death packet maybe? + while (!ngCheck && !baalIsDead) { + delay(500); + } + } + + delay(500); + } + } catch (e) { + console.error(e); + } finally { + killTracker = true; + } + } else { + throw new Error("Empty game."); + } + } finally { + removeEventListener("chatmsg", chatEvent); + removeEventListener("gamepacket", baalDeathEvent); + } + + return true; + }, + { + startArea: sdk.areas.Harrogath, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/BaalHelper.js b/d2bs/kolbot/libs/scripts/BaalHelper.js new file mode 100644 index 000000000..89e908b99 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BaalHelper.js @@ -0,0 +1,153 @@ +/** +* @filename BaalHelper.js +* @author kolton, theBGuy +* @desc help the leading player in clearing Throne of Destruction and killing Baal +* +*/ + + +const BaalHelper = new Runnable( + function BaalHelper () { + include("core/Common/Baal.js"); + + Town.goToTown(5); + Config.RandomPrecast && Precast.needOutOfTownCast() + ? Precast.doRandomPrecast(true, sdk.areas.Harrogath) + : Precast.doPrecast(true); + + if (Config.BaalHelper.SkipTP) { + !me.inArea(sdk.areas.WorldstoneLvl2) && Pather.useWaypoint(sdk.areas.WorldstoneLvl2); + + if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], false)) { + throw new Error("Failed to move to WSK3."); + } + if (!Misc.poll(() => { + let party = getParty(); + + if (party) { + do { + if ((!Config.Leader || party.name === Config.Leader) && party.area === sdk.areas.ThroneofDestruction) { + return true; + } + } while (party.getNext()); + } + + return false; + }, Time.minutes(Config.BaalHelper.Wait), 1000)) { + throw new Error( + "Player wait timed out (" + (Config.Leader ? "Leader not" : "No players") + " found in Throne)" + ); + } + + let entrance = Misc.poll(() => Game.getStairs(sdk.exits.preset.NextAreaWorldstone), 1000, 200); + if (entrance) { + let [x, y] = [ + entrance.x > me.x ? entrance.x - 5 : entrance.x + 5, + entrance.y > me.y ? entrance.y - 5 : entrance.y + 5 + ]; + Pather.moveTo(x, y); + } + + if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], false)) { + throw new Error("Failed to move to WSK3."); + } + if (!Pather.moveToExit(sdk.areas.ThroneofDestruction, true)) { + throw new Error("Failed to move to Throne of Destruction."); + } + Pather.moveToEx(15113, 5040, { callback: () => { + if (Config.BaalHelper.DollQuit && Game.getMonster(sdk.monsters.SoulKiller)) { + console.log("Undead Soul Killers found, ending script."); + throw new ScriptError("Undead Soul Killers found, ending script."); + } + + if (Config.BaalHelper.SoulQuit && Game.getMonster(sdk.monsters.BurningSoul1)) { + console.log("Burning Souls found, ending script."); + throw new ScriptError("Burning Souls found, ending script."); + } + } }); + } else { + Town.goToTown(5); + Town.move("portalspot"); + + let quitFlag = false; + + const chatEvent = function (nick, msg) { + if (nick === Config.Leader) { + if ((Config.BaalHelper.DollQuit && msg === "Dolls found! NG.") + || (Config.BaalHelper.SoulQuit && msg === "Souls found! NG.")) { + quitFlag = true; + } + } + }; + + if (Config.BaalHelper.DollQuit || Config.BaalHelper.SoulQuit) { + addEventListener("chatmsg", chatEvent); + } + + try { + if (!Misc.poll(() => { + if (Pather.getPortal(sdk.areas.ThroneofDestruction, Config.Leader || null)) { + if (quitFlag) throw new ScriptError("Burning Souls or Dolls found, ending script."); + if (Pather.usePortal(sdk.areas.ThroneofDestruction, Config.Leader || null)) { + return true; + } + } + + return false; + }, Time.minutes(Config.BaalHelper.Wait), 1000)) { + throw new Error("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); + } + } catch (e) { + console.log(e.message); + + return true; + } finally { + removeEventListener("chatmsg", chatEvent); + } + } + + if (Config.BaalHelper.DollQuit && Game.getMonster(sdk.monsters.SoulKiller)) { + print("Undead Soul Killers found."); + + return true; + } + + Precast.doPrecast(false); + Attack.clear(15); + Common.Baal.clearThrone(); + + if (!Common.Baal.clearWaves()) { + throw new Error("Couldn't clear baal waves"); + } + + if (Config.BaalHelper.KillBaal) { + Common.Baal.killBaal(); + } else { + Town.goToTown(); + while (true) { + delay(500); + } + } + + return true; + }, + { + preAction: function () { + Config.BaalHelper.KillNihlathak && Loader.runScript("Nihlathak"); + Config.BaalHelper.FastChaos && Loader.runScript("Diablo", () => Config.Diablo.Fast = true); + + if (getTickCount() - Town.lastChores > Time.minutes(1)) { + Town.doChores(); + } + } + } +); + +Object.defineProperty(BaalHelper, "startArea", { + get: function() { + if (Config.BaalHelper.KillNihlathak || !Config.BaalHelper.FastChaos) { + return sdk.areas.Harrogath; + } + return sdk.areas.RiverofFlame; + }, +}); diff --git a/d2bs/kolbot/libs/scripts/BattleOrders.js b/d2bs/kolbot/libs/scripts/BattleOrders.js new file mode 100644 index 000000000..763cb4cf6 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BattleOrders.js @@ -0,0 +1,310 @@ +/** +* @filename BattleOrders.js +* @author kolton, jmichelsen, theBGuy +* @desc give or receive Battle Orders buff +* +*/ + +// todo - define bo-er name, so bots who are getting bo know who is supposed to give it +// todo - use profile <-> profile communication so we don't need to set char names, Maybe shout global? +const BattleOrders = new Runnable( + function BattleOrders () { + this.gaveBo = false; + /** @type {Set} */ + const totalBoed = new Set(); + /** @type {Set} */ + const boGetters = new Set(Config.BattleOrders.Getters.map(name => name.toLowerCase())); + + const boMode = { + Give: 0, + Receive: 1 + }; + + function checkForPlayers () { + if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); + } + + function log (msg = "") { + console.log(msg); + me.overhead(msg); + } + + function tardy () { + let party; + + AreaInfoLoop: + while (true) { + try { + checkForPlayers(); + } catch (e) { + if (Config.BattleOrders.Wait) { + let counter = 0; + console.log("Waiting " + Config.BattleOrders.Wait + " seconds for other players..."); + + Misc.poll(() => { + counter++; + me.overhead( + "Waiting " + Math.round(((tick + Time.seconds(Config.BattleOrders.Wait)) - getTickCount()) / 1000) + + " Seconds for other players" + ); + if (counter % 5 === 0) { + return checkForPlayers(); + } + return false; + }, Time.seconds(Config.BattleOrders.Wait), Time.seconds(1)); + + continue; + } else { + console.error(e); + // emptry game, don't wait + return true; + } + } + + party = getParty(); + + if (party) { + do { + if (party.name !== me.name && party.area) { + break AreaInfoLoop; // Can read player area + } + } while (party.getNext()); + } + + delay(500); + } + + if (party) { + do { + if ([ + sdk.areas.MooMooFarm, + sdk.areas.ChaosSanctuary, + sdk.areas.ThroneofDestruction, + sdk.areas.WorldstoneChamber + ].includes(party.area)) { + log("ÿc1I'm late to BOs. Moving on..."); + + return true; + } + } while (party.getNext()); + } + + return false; // Not late; wait. + } + + // bo is AoE, lets build a list of all players near us so we can know who we boed + function giveBO () { + // more players might be showing up, give a moment and lets wait until the nearby player count is static + let nearPlayers = 0; + let tick = getTickCount(); + + // if we haven't already given a bo, lets wait to see if more players show up + if (!BattleOrders.gaveBo) { + nearPlayers = Misc.getNearbyPlayerCount(); + while (nearPlayers !== boGetters.size) { + if (getTickCount() - tick >= Time.seconds(30)) { + log("Begin"); + + break; + } + + me.overhead( + "Waiting " + Math.round(((tick + Time.seconds(30)) - getTickCount()) / 1000) + + " for all players to show up" + ); + nearPlayers = Misc.getNearbyPlayerCount(); + delay(1000); + } + } + + let boed = false; + const playersToBo = getUnits(sdk.unittype.Player) + .filter(p => boGetters.has(p.name.toLowerCase()) && p.distance < 20); + playersToBo.forEach(p => { + tick = getTickCount(); + + if (copyUnit(p).x) { + while (!p.getState(sdk.states.BattleOrders) && copyUnit(p).x) { + if (getTickCount() - tick >= Time.minutes(1)) { + log("ÿc1BO timeout fail."); + + if (Config.BattleOrders.QuitOnFailure) { + quit(); + } + + break; + } + + Precast.doPrecast(true); + delay(1000); + } + + totalBoed.add(p.name.toLowerCase()); + console.debug("Bo-ed " + p.name); + boed = true; + } + }); + + if (boed) { + delay(5000); + } + + return { + success: boed, + count: playersToBo.length + }; + } + + // START + try { + Pather.useWaypoint(sdk.areas.CatacombsLvl2, true); + } catch (wperror) { + log("ÿc1Failed to take waypoint."); + Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); + + return false; + } + + // don't bo until we are ready to do so + Precast.enabled = false; + Pather.moveTo(me.x + 6, me.y + 6); + + let tick = getTickCount(); + let failTimer = Time.minutes(2); + let nearPlayer; + + // Ready + Precast.enabled = true; + + /** + * @param {string} name + * @param {string} msg + */ + function chatEvent (name, msg) { + if (!msg | !name) return; + if (!boGetters.has(name.toLowerCase())) return; + if (msg === "got-bo") { + console.log(name + " got bo"); + totalBoed.add(name.toLowerCase()); + } + } + + /** @returns {string[]} */ + function getFailedToBO () { + return Config.BattleOrders.Getters.filter(name => !totalBoed.has(name.toLowerCase())); + } + + try { + if (Config.BattleOrders.Mode === boMode.Give) { + addEventListener("chatmsg", chatEvent); + } + + MainLoop: + while (true) { + if (Config.BattleOrders.SkipIfTardy && tardy()) { + break; + } + + switch (Config.BattleOrders.Mode) { + case boMode.Give: + // check if anyone is near us + nearPlayer = Game.getPlayer(); + + if (nearPlayer) { + do { + if (nearPlayer.name !== me.name) { + let nearPlayerName = nearPlayer.name.toLowerCase(); + // there is a player near us and they are in the list of players to bo and in my party + if (boGetters.has(nearPlayerName) + && !totalBoed.has(nearPlayerName) + && Misc.inMyParty(nearPlayerName)) { + let result = giveBO(); + if (result.success) { + if (result.count === boGetters.size + || totalBoed.size === boGetters.size) { + // we bo-ed everyone we are set to, don't wait around any longer + break MainLoop; + } + // reset fail tick + tick = getTickCount(); + // shorten waiting time since we've already started giving out bo's + BattleOrders.gaveBo = true; + } + } + } else { + me.overhead( + "Waiting " + Math.round(((tick + failTimer) - getTickCount()) / 1000) + + " Seconds for other players" + ); + + if (getTickCount() - tick >= failTimer) { + log("ÿc1Give BO timeout fail."); + log("Failed to bo: " + getFailedToBO().join(", ")); + Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); + + break MainLoop; + } + } + } while (nearPlayer.getNext()); + } else { + me.overhead( + "Waiting " + Math.round(((tick + failTimer) - getTickCount()) / 1000) + + " Seconds for other players" + ); + + if (getTickCount() - tick >= failTimer) { + log("ÿc1Give BO timeout fail."); + log("Failed to bo: " + getFailedToBO().join(", ")); + Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); + + break MainLoop; + } + } + + break; + case boMode.Receive: + if (me.getState(sdk.states.BattleOrders)) { + log("Got bo-ed"); + say("got-bo"); + delay(1000); + + break MainLoop; + } + + if (getTickCount() - tick >= failTimer) { + log("ÿc1BO timeout fail."); + Config.BattleOrders.QuitOnFailure && scriptBroadcast("quit"); + + break MainLoop; + } + + break; + } + + delay(500); + } + + if (Loader.nextScript && Loader.nextScript.startArea) { + Pather.useWaypoint(Loader.nextScript.startArea); + } else { + (Pather.useWaypoint(sdk.areas.RogueEncampment) || Town.goToTown()); + } + + // what's the point of this? + if (Config.BattleOrders.Mode === boMode.Give && Config.BattleOrders.Idle) { + for (let i = 0; i < Config.BattleOrders.Getters.length; i += 1) { + while (Misc.inMyParty(Config.BattleOrders.Getters[i])) { + delay(1000); + } + } + } + + return true; + } finally { + removeEventListener("chatmsg", chatEvent); + } + }, + { + startArea: sdk.areas.CatacombsLvl2 + } +); diff --git a/d2bs/kolbot/libs/scripts/BattlemaidSarina.js b/d2bs/kolbot/libs/scripts/BattlemaidSarina.js new file mode 100644 index 000000000..39c379d85 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BattlemaidSarina.js @@ -0,0 +1,27 @@ +/** +* @filename BattlemaidSarina.js +* @author theBGuy +* @desc kill Battlemaid Sarina +* +*/ + +const BattlemaidSarina = new Runnable( + function BattlemaidSarina () { + Pather.useWaypoint(sdk.areas.KurastBazaar); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.RuinedTemple, true) + || !Pather.moveToPresetObject(me.area, sdk.quest.chest.LamEsensTomeHolder)) { + throw new Error("Failed to move near Sarina"); + } + + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.BattlemaidSarina)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.KurastBazaar, + bossid: getLocaleString(sdk.locale.monsters.BattlemaidSarina), + } +); diff --git a/d2bs/kolbot/libs/scripts/Bishibosh.js b/d2bs/kolbot/libs/scripts/Bishibosh.js new file mode 100644 index 000000000..feb723307 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Bishibosh.js @@ -0,0 +1,23 @@ +/** +* @filename Bishibosh.js +* @author theBGuy +* @desc kill Bishibosh +* +*/ + +const Bishibosh = new Runnable( + function Bishibosh () { + Pather.useWaypoint(sdk.areas.ColdPlains); + Precast.doPrecast(true); + + Pather.moveToPresetMonster(sdk.areas.ColdPlains, sdk.monsters.preset.Bishibosh); + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Bishibosh)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.ColdPlains, + bossid: getLocaleString(sdk.locale.monsters.Bishibosh), + } +); diff --git a/d2bs/kolbot/libs/scripts/BoBarbHelper.js b/d2bs/kolbot/libs/scripts/BoBarbHelper.js new file mode 100644 index 000000000..f7f0c0d95 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BoBarbHelper.js @@ -0,0 +1,109 @@ +/** +* @filename BoBarbHelper.js +* @author nag0k +* @desc give Battle Orders buff modded for hardcore, with barbarian waiting whole game on Catacombs 2 wp by default +* get the required lines for character config files from ...\libs\config\_BaseConfigFile.js +* +*/ + +const BoBarbHelper = new Runnable( + function BoBarbHelper () { + if (!me.barbarian && Config.BoBarbHelper.Mode !== 0) return true; + + const townNearbyMonster = true; // go to town if monsters nearby + const townLowMana = 20; // go refill mana if mana drops below this percent + const shouldHealMana = amount => me.mp < Math.floor(me.mpmax * amount / 100); + + const healMana = () => { + Pather.useWaypoint(sdk.areas.RogueEncampment); + Town.initNPC("Heal", "heal"); + Pather.useWaypoint(Config.BoBarbHelper.Wp); + }; + + const shouldBuff = unit => ( + Misc.inMyParty(unit) && + getDistance(me, unit) < 10 && + unit.name !== me.name && + !unit.dead && + !unit.inTown + ); + + const giveBuff = () => { + const unit = Game.getPlayer(); + + do { + if (shouldBuff(unit)) { + Precast.doPrecast(true); + delay(50); + } + } while (unit.getNext()); + }; + + const monsterNear = () => { + const unit = Game.getMonster(); + + if (unit) { + do { + if (unit.attackable && getDistance(me, unit) < 20) { + return true; + } + } while (unit.getNext()); + } + + return false; + }; + + if (!Config.QuitList) { + showConsole(); + console.log("set Config.QuitList in character settings"); + console.log("if you don't I will idle indefinitely"); + } + + if (me.hardcore && Config.LifeChicken <= 0) { + showConsole(); + console.log("on HARDCORE"); + console.log("you should set Config.LifeChicken"); + console.log("monsters can find their way to wps ..."); + delay(2000); + hideConsole(); + me.overhead("set LifeChiken to 40"); + Config.LifeChicken = 40; + } + + shouldHealMana(townLowMana) && Town.initNPC("Heal", "heal"); + Town.heal(); // in case our life is low as well + + try { + Pather.useWaypoint(Config.BoBarbHelper.Wp); + } catch (e) { + showConsole(); + console.log("Failed to move to BO WP"); + console.log("make sure I have " + getAreaName(Config.BoBarbHelper.Wp) + " waypoint"); + delay(20000); + + return true; + } + + Pather.moveTo(me.x + 4, me.y + 4); + + while (true) { + giveBuff(); + + if (townNearbyMonster && monsterNear()) { + if (!Pather.useWaypoint(sdk.areas.RogueEncampment)) { + break; + } + } + + shouldHealMana(townLowMana) && healMana(); + delay(25); + } + + Town.goToTown(); + + return true; + }, + { + startArea: Config.BoBarbHelper.Wp + } +); diff --git a/d2bs/kolbot/libs/scripts/BoneAsh.js b/d2bs/kolbot/libs/scripts/BoneAsh.js new file mode 100644 index 000000000..46f635fc3 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BoneAsh.js @@ -0,0 +1,24 @@ +/** +* @filename BoneAsh.js +* @author kolton, theBGuy +* @desc kill Bone Ash +* +*/ + +const BoneAsh = new Runnable( + function BoneAsh () { + Pather.useWaypoint(sdk.areas.InnerCloister); + Precast.doPrecast(true); + + if (!Pather.moveTo(20047, 4898)) throw new Error("Failed to move to Bone Ash"); + + Attack.kill(getLocaleString(sdk.locale.monsters.BoneAsh)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.InnerCloister, + bossid: getLocaleString(sdk.locale.monsters.BoneAsh), + } +); diff --git a/d2bs/kolbot/libs/scripts/Bonesaw.js b/d2bs/kolbot/libs/scripts/Bonesaw.js new file mode 100644 index 000000000..352b76802 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Bonesaw.js @@ -0,0 +1,27 @@ +/** +* @filename Bonesaw.js +* @author kolton +* @desc kill Bonesaw Breaker +* +*/ + +const Bonesaw = new Runnable( + function Bonesaw () { + Pather.useWaypoint(sdk.areas.GlacialTrail); + Precast.doPrecast(true); + + if (!Pather.moveToPresetObject(sdk.areas.GlacialTrail, sdk.objects.LargeSparklyChest, { offX: 15, offY: 15 })) { + throw new Error("Failed to move to Bonesaw"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.BonesawBreaker)); + if (Config.Bonesaw.ClearDrifterCavern && Pather.moveToExit(sdk.areas.DrifterCavern, true)) { + Attack.clearLevel(Config.ClearType); + } + return true; + }, + { + startArea: sdk.areas.GlacialTrail, + bossid: getLocaleString(sdk.locale.monsters.BonesawBreaker), + } +); diff --git a/d2bs/kolbot/libs/scripts/ChestMania.js b/d2bs/kolbot/libs/scripts/ChestMania.js new file mode 100644 index 000000000..edd362fea --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ChestMania.js @@ -0,0 +1,50 @@ +/** +* @filename ChestMania.js +* @author kolton, theBGuy +* @desc Open chests in configured areas +* +*/ + +// todo - if we have run ghostsbusters before this then some of these areas don't need to be re-run + +const ChestMania = new Runnable( + function ChestMania () { + Config.OpenChests._enabled = Config.OpenChests.Enabled; + Config.OpenChests.Enabled = true; + const nextToTown = [ + sdk.areas.BloodMoor, + sdk.areas.RockyWaste, + sdk.areas.SpiderForest, + sdk.areas.OuterSteppes, + sdk.areas.BloodyFoothills + ]; + + Object.values(Config.ChestMania) + .forEach(function (act) { + for (let area of act) { + if (nextToTown.includes(area)) { + // if we precast as soon as we step out of town it sometimes crashes - so do precast somewhere else first + Precast.doRandomPrecast(false); + } + try { + Pather.journeyTo(area); + Precast.doPrecast(false); + Misc.openChestsInArea(area); + } catch (e) { + console.error(e); + } + } + + Town.doChores(); + }); + + return true; + }, + { + startArea: Object.values(Config.ChestMania).find((act) => act.length > 0)[0][0], + cleanup: function () { + Config.OpenChests.Enabled = Config.OpenChests._enabled; + delete Config.OpenChests._enabled; + } + } +); diff --git a/d2bs/kolbot/libs/scripts/ClassicChaosAssistant.js b/d2bs/kolbot/libs/scripts/ClassicChaosAssistant.js new file mode 100644 index 000000000..2f4bdd839 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ClassicChaosAssistant.js @@ -0,0 +1,135 @@ +/** +* @filename ClassicChaosAssistant.js +* @author YGM +* @desc Assistant to help sorcs in public chaos runs games on classic. +* +*/ + +// redo this, maybe different keys or chat commands instead? + +const ClassicChaosAssistant = new Runnable( + function ClassicChaosAssistant () { + include("core/Common/Diablo.js"); + let stargo, infgo, seisgo, vizgo, infseal, seisseal, vizseal, diablopickup, normalpickup = false; + + addEventListener("keyup", + function (key) { + switch (key) { + case sdk.keys.Numpad1: + stargo = true; + + break; + case sdk.keys.Numpad2: + infgo = true; + + break; + case sdk.keys.Numpad3: + infseal = true; + + break; + case sdk.keys.Numpad4: + seisgo = true; + + break; + case sdk.keys.Numpad5: + seisseal = true; + + break; + case sdk.keys.Numpad6: + vizgo = true; + + break; + case sdk.keys.Numpad7: + vizseal = true; + + break; + case sdk.keys.Numpad8: // (Open last seal, teleport to star and pickup for 30 seconds) + diablopickup = true; + + break; + case sdk.keys.Numpad9: // (Pickup at current location) + normalpickup = true; + + break; + default: + break; + } + }); + + while (true) { + switch (me.area) { + case sdk.areas.ChaosSanctuary: + if (infgo) { + Common.Diablo.infLayout === 1 ? Pather.moveTo(7893, 5306) : Pather.moveTo(7929, 5294); + Pather.makePortal() && say("Infector of Souls TP Up!"); + infgo = false; + } + + if (seisgo) { + Common.Diablo.seisLayout === 1 ? Pather.moveTo(7773, 5191) : Pather.moveTo(7794, 5189); + Pather.makePortal() && say("Lord De Seis TP Up!"); + seisgo = false; + } + + if (vizgo) { + Common.Diablo.vizLayout === 1 ? Pather.moveTo(7681, 5302) : Pather.moveTo(7675, 5305); + Pather.makePortal() && say("Grand Vizier of Chaos TP Up!"); + vizgo = false; + } + + if (infseal) { + Common.Diablo.openSeal(sdk.objects.DiabloSealInfector2); + Common.Diablo.openSeal(sdk.objects.DiabloSealInfector) && say("Infector of Souls spawned!"); + Common.Diablo.infLayout === 1 ? Pather.moveTo(7893, 5306) : Pather.moveTo(7929, 5294); + infseal = false; + } + + if (seisseal) { + Common.Diablo.openSeal(sdk.objects.DiabloSealSeis) && say("Lord De Seis spawned!"); + Common.Diablo.seisLayout === 1 ? Pather.moveTo(7773, 5191) : Pather.moveTo(7794, 5189); + seisseal = false; + } + + if (vizseal) { + Common.Diablo.openSeal(sdk.objects.DiabloSealVizier2) && say("Grand Vizier of Chaos spawned!"); + Common.Diablo.vizLayout === 1 ? Pather.moveTo(7681, 5302) : Pather.moveTo(7675, 5305); + vizseal = false; + } + + if (diablopickup) { + Common.Diablo.openSeal(sdk.objects.DiabloSealVizier); + Pather.moveToPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, 255); + for (let i = 0; i < 300; i += 1) { + Pickit.pickItems(); + delay(100); + } + diablopickup = false; + } + + if (normalpickup) { + Pickit.pickItems(); + normalpickup = false; + } + + break; + default: + if (stargo) { + if (me.inArea(sdk.areas.RiverofFlame)) { + Precast.doPrecast(true); + Pather.moveToPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, 255); + Common.Diablo.initLayout(); + break; + } + stargo = false; + } + + break; + } + + delay(10); + } + }, + { + startArea: sdk.areas.RiverofFlame + } +); diff --git a/d2bs/kolbot/libs/scripts/ClearAnyArea.js b/d2bs/kolbot/libs/scripts/ClearAnyArea.js new file mode 100644 index 000000000..1840740ef --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ClearAnyArea.js @@ -0,0 +1,28 @@ +/** +* @filename ClearAnyArea.js +* @author kolton +* @desc Clears any area +* +*/ + +const ClearAnyArea = new Runnable( + function ClearAnyArea () { + for (let area of Config.ClearAnyArea.AreaList) { + try { + if (Pather.journeyTo(area)) { + Attack.clearLevel(Config.ClearType); + } + } catch (e) { + console.error(e); + } + } + + return true; + } +); + +Object.defineProperty(ClearAnyArea, "startArea", { + get: function () { + return Config.ClearAnyArea.AreaList[0]; + } +}); diff --git a/d2bs/kolbot/libs/scripts/Coldcrow.js b/d2bs/kolbot/libs/scripts/Coldcrow.js new file mode 100644 index 000000000..496e46054 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Coldcrow.js @@ -0,0 +1,26 @@ +/** +* @filename Coldcrow.js +* @author njomnjomnjom +* @desc kill Coldcrow +* +*/ + +const Coldcrow = new Runnable( + function Coldcrow () { + Pather.useWaypoint(sdk.areas.ColdPlains); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.CaveLvl1, true, false)) throw new Error("Failed to move to Cave"); + if (!Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Coldcrow, 0, 0, false)) { + throw new Error("Failed to move to Coldcrow"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.Coldcrow)); + + return true; + }, + { + startArea: sdk.areas.ColdPlains, + bossid: getLocaleString(sdk.locale.monsters.Coldcrow), + } +); diff --git a/d2bs/kolbot/libs/scripts/Coldworm.js b/d2bs/kolbot/libs/scripts/Coldworm.js new file mode 100644 index 000000000..e23cb2531 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Coldworm.js @@ -0,0 +1,44 @@ +/** +* @filename Coldworm.js +* @author kolton, edited by 13ack.Stab +* @desc kill Coldworm; optionally kill Beetleburst and clear Maggot Lair +* +*/ + +const Coldworm = new Runnable( + function Coldworm () { + Pather.useWaypoint(sdk.areas.FarOasis); + Precast.doPrecast(true); + + // Beetleburst, added by 13ack.Stab + if (Config.Coldworm.KillBeetleburst && !Attack.haveKilled(getLocaleString(sdk.locale.monsters.Beetleburst))) { + try { + if (!Pather.moveToPresetMonster(me.area, sdk.monsters.preset.Beetleburst)) { + throw new Error("Failed to move to Beetleburst"); + } + Attack.kill(getLocaleString(sdk.locale.monsters.Beetleburst)); + } catch (e) { + console.error(e); // not the main part of this script so simply log and move on + } + } + + if (!Pather.moveToExit([sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3], true)) { + throw new Error("Failed to move to Coldworm"); + } + + if (Config.Coldworm.ClearMaggotLair) { + Attack.clearLevel(Config.ClearType); + } else { + if (!Pather.moveToPresetObject(me.area, sdk.quest.chest.ShaftoftheHoradricStaffChest)) { + throw new Error("Failed to move to Coldworm"); + } + Attack.kill(sdk.monsters.ColdwormtheBurrower); + } + + return true; + }, + { + startArea: sdk.areas.FarOasis, + bossid: sdk.monsters.ColdwormtheBurrower, + } +); diff --git a/d2bs/kolbot/libs/scripts/ControlBot.js b/d2bs/kolbot/libs/scripts/ControlBot.js new file mode 100644 index 000000000..958bc9f32 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ControlBot.js @@ -0,0 +1,1496 @@ +/** +* @filename ControlBot.js +* @author theBGuy +* @credits kolton (for the original Enchant.js), +* magace (for the inspiration to add rush commands) +* @desc Chat controlled bot for other players. Can open cow portal, give waypoints on command, bo, or enchant +* +*/ + +const ControlBot = new Runnable( + function ControlBot () { + // Quests + const { + log, + playerIn, + andariel, + bloodraven, + smith, + cube, + radament, + amulet, + staff, + summoner, + duriel, + gidbinn, + lamesen, + brain, + heart, + eye, + travincal, + // mephisto, + izual, + diablo, + shenk, + anya, + ancients, + baal, + } = require("../systems/autorush/AutoRush"); + const { + AutoRush, + RushModes, + } = require("../systems/autorush/RushConfig"); + const Worker = require("../modules/Worker"); + const AreaData = require("../core/GameData/AreaData"); + + /** @param {string} [nick] */ + const mephisto = function (nick) { + log("starting mephisto"); + + Town.doChores(); + Pather.useWaypoint(sdk.areas.DuranceofHateLvl2, true) && Precast.doPrecast(true); + if (!Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true)) { + throw new Error("Failed to move to durance 3"); + } + Pather.moveTo(17617, 8069); + Attack.securePosition(me.x, me.y, 30, 3000); + Pather.moveTo(17591, 8070) && Attack.securePosition(me.x, me.y, 20, 3000); + let hydra = Game.getMonster(getLocaleString(sdk.locale.monsters.Hydra)); + + if (hydra) { + do { + while (!hydra.dead && hydra.hp > 0) { + delay(500); + } + } while (hydra.getNext()); + } + Pather.makePortal(); + Pather.moveTo(17581, 8070); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Pather.moveTo(17591, 8070); + Attack.kill(sdk.monsters.Mephisto); + Pickit.pickItems(); + log("meph dead"); + log(AutoRush.playersOut); + Pather.usePortal(null); + + return true; + }; + + AutoRush.rushMode = RushModes.chanter; + AutoRush.playersIn = "in"; + AutoRush.playersOut = "out"; + AutoRush.allIn = "all in"; + + const ngVote = new function () { + /** @type {Set} */ + this.votesYes = new Set(); + /** @type {Set} */ + this.votesNo = new Set(); + this.active = false; + this.tick = 0; + this.nextGame = false; + + this.votesNeeded = function () { + return Math.max(1, Math.floor((Misc.getPlayerCount() - 2) / 2)); + }; + this.reset = function () { + this.votesYes.clear(); + this.votesNo.clear(); + this.tick = 0; + this.active = false; + }; + this.begin = function () { + this.active = true; + this.votesYes.clear(); + this.votesNo.clear(); + this.tick = getTickCount(); + }; + this.checkCount = function () { + // ensure we've counted everyones votes when checking for a draw + if (Misc.getPartyCount() === this.votesYes.size + this.votesNo.size) { + if (this.votesYes.size === this.votesNo.size) { + Chat.say("Not enough votes to start ng we have a draw."); + this.reset(); + return false; + } + } + const votesNeeded = this.votesNeeded(); + if (this.votesNo.size >= votesNeeded) { + Chat.say("ng rejected by majority."); + this.reset(); + return false; + } + const reqMet = this.votesYes.size >= votesNeeded; + if (reqMet) { + Chat.say("ng approved by majority."); + ngVote.nextGame = true; + this.reset(); + } + return reqMet; + }; + /** + * @param {string} nick + * @param {"yes" | "no"} type + */ + this.vote = function (nick, type) { + if (!this.active) return; + if (type === "yes") { + this.votesNo.delete(nick); + this.votesYes.add(nick); + } else if (type === "no") { + this.votesYes.delete(nick); + this.votesNo.add(nick); + } + }; + this.elapsed = function () { + return getTickCount() - this.tick; + }; + }; + const MAX_CHAT_LENGTH = 180; + const MIN_GOLD = 500000; + const startTime = getTickCount(); + const maxTime = Time.minutes(Config.ControlBot.GameLength); + const chantDuration = Skill.getDuration(sdk.skills.Enchant); + /** @type {Map} */ + const players = new Map(); + /** @type {Set} */ + const givenGold = new Set(); + + const Chat = { + overheadTick: 0, + /** @type {string[]} */ + queue: [], + + /** + * Send a message in chat + * @param {string} msg + */ + say: function (msg) { + Chat.queue.push(msg); + }, + + /** + * Display a message overhead + * @param {string} msg + * @param {boolean} [force] + */ + overhead: function (msg, force = false) { + if (!force && getTickCount() - Chat.overheadTick < 0) return; + // allow overhead messages every ~3-4 seconds + Chat.overheadTick = getTickCount() + Time.seconds(3) + rand(250, 1500); + say("!" + msg); + }, + + /** + * Whisper a chat to a user + * @param {string} nick + * @param {string} msg + */ + whisper: function (nick, msg) { + if (!players.has(nick) && !Misc.findPlayer(nick)) { + console.debug("Player not found: " + nick); + return; + } + let who = players.get(nick) || nick; + Chat.queue.push("/w " + who + " " + msg); + }, + + /** + * Private message a chat to a user + * @param {string} nick + * @param {string} msg + */ + message: function (nick, msg) { + Chat.queue.push("/m " + nick + " " + msg); + }, + }; + + Worker.runInBackground.chat = (function () { + let tick = getTickCount(); + + return function () { + if (!Chat.queue.length) return true; + if (getTickCount() - tick < 0) return true; + // check if next msg is going to be a whisper + if (Chat.queue[0].startsWith("/w")) { + // check if the player is in the game and if not, don't send the whisper + } + // allow say messages every ~1.7 seconds + tick = getTickCount() + Time.seconds(1) + rand(500, 950); + console.debug("(" + Chat.queue[0] + ")"); + if (Chat.queue[0].length > MAX_CHAT_LENGTH) { + console.debug("Message too long, splitting."); + Chat.queue[0] = Chat.queue[0].substring(0, MAX_CHAT_LENGTH); + } + say(Chat.queue.shift()); + return true; + }; + })(); + + /** @constructor */ + function PlayerTracker () { + this.firstCmd = getTickCount(); + this.commands = 0; + this.ignored = false; + this.seenHelpMsg = false; + } + + PlayerTracker.prototype.resetCmds = function () { + this.firstCmd = getTickCount(); + this.commands = 0; + }; + + PlayerTracker.prototype.unIgnore = function () { + this.ignored = false; + this.commands = 0; + }; + + /** @constructor */ + function ChantTracker () { + this.lastChant = getTickCount(); + } + + ChantTracker.prototype.reChant = function () { + return getTickCount() - this.lastChant >= chantDuration - Time.minutes(1); + }; + + ChantTracker.prototype.update = function () { + this.lastChant = getTickCount(); + }; + + /** @constructor */ + function WpTracker () { + this.timer = getTickCount(); + this.requests = 0; + this.singleWpRequests = 0; + } + + WpTracker.prototype.update = function () { + this.timer = getTickCount(); + this.requests++; + }; + + WpTracker.prototype.updateSingle = function () { + this.timer = getTickCount(); + this.singleWpRequests++; + }; + + WpTracker.prototype.timeSinceLastRequest = function () { + return getTickCount() - this.timer; + }; + + /** @type {Map} */ + const cmdNicks = new Map(); + /** @type {Map} */ + const wpNicks = new Map(); + /** @type {Array} */ + const greet = []; + /** @type {Map} */ + const wps = new Map([ + [1, [ + sdk.areas.ColdPlains, sdk.areas.StonyField, + sdk.areas.DarkWood, sdk.areas.BlackMarsh, + sdk.areas.OuterCloister, sdk.areas.JailLvl1, + sdk.areas.InnerCloister, sdk.areas.CatacombsLvl2 + ] + ], + [2, [ + sdk.areas.A2SewersLvl2, sdk.areas.DryHills, + sdk.areas.HallsoftheDeadLvl2, sdk.areas.FarOasis, + sdk.areas.LostCity, sdk.areas.PalaceCellarLvl1, + sdk.areas.ArcaneSanctuary, sdk.areas.CanyonofMagic + ] + ], + [3, [ + sdk.areas.SpiderForest, sdk.areas.GreatMarsh, + sdk.areas.FlayerJungle, sdk.areas.LowerKurast, + sdk.areas.KurastBazaar, sdk.areas.UpperKurast, + sdk.areas.Travincal, sdk.areas.DuranceofHateLvl2 + ] + ], + [4, [ + sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame + ] + ], + [5, [ + sdk.areas.FrigidHighlands, sdk.areas.ArreatPlateau, + sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail, + sdk.areas.FrozenTundra, sdk.areas.AncientsWay, sdk.areas.WorldstoneLvl2 + ] + ] + ]); + + /** @type {[string, string][]} */ + const queue = []; + const running = { + nick: "", + command: "", + }; + + /** + * @param {string} nick + * @returns {boolean} + */ + const enchant = function (nick) { + try { + if (!Misc.inMyParty(nick)) { + throw new ScriptError("Accept party invite, noob."); + } + + let unit = Game.getPlayer(nick); + + if (unit && unit.distance > 35) { + throw new ScriptError("Get closer."); + } + + if (!unit) { + let partyUnit = getParty(nick); + + if (!Misc.poll(() => partyUnit.inTown, 500, 50)) { + throw new ScriptError("You need to be in one of the towns."); + } + // wait until party area is readable? + Chat.say("Wait for me at waypoint."); + Town.goToTown(sdk.areas.actOf(partyUnit.area)); + + unit = Game.getPlayer(nick); + } + + if (unit) { + do { + // player is alive + if (!unit.dead) { + if (unit.distance >= 35) { + throw new ScriptError("You went too far away."); + } + Packet.enchant(unit); + if (Misc.poll(() => unit.getState(sdk.states.Enchant), 500, 50)) { + chantList.has(unit.name) + ? chantList.get(unit.name).update() + : chantList.set(unit.name, new ChantTracker()); + } + } + } while (unit.getNext()); + } else { + Chat.say("I don't see you"); + } + + unit = Game.getMonster(); + + if (unit) { + do { + // merc or any other owned unit + let parent = unit.getParent(); + if (!parent) continue; + if (parent.name === nick) { + Packet.enchant(unit); + delay(500); + } + } while (unit.getNext()); + } + + return true; + } catch (e) { + if (e instanceof ScriptError) { + Chat.say((typeof e === "object" && e.message ? e.message : typeof e === "string" && e)); + } else { + console.error(e); + Chat.say("Internal Error"); + } + + return false; + } + }; + + /** + * @param {string} nick + * @returns {boolean} + */ + const bo = function (nick) { + if (!Config.ControlBot.Bo) return false; + + try { + if (!Misc.inMyParty(nick)) { + throw new ScriptError("Accept party invite, noob."); + } + + let partyUnit = getParty(nick); + + // wait until party area is readable? + if (!Misc.poll(() => Pather.wpAreas.includes(partyUnit.area), 500, 50)) { + throw new ScriptError("Can't find you or you're not somewhere with a waypoint"); + } + if (partyUnit.inTown) { + let a1Wp = Object.values(sdk.areas) + .filter(function (area) { + if (area < sdk.areas.ColdPlains || area > sdk.areas.CatacombsLvl2) return false; + return Pather.wpAreas.includes(area) && me.haveWaypoint(area); + }).random(); + Chat.whisper(nick, "Go to act 1 waypoint " + getAreaName(a1Wp) + " and wait for me."); + Pather.useWaypoint(a1Wp); + } else { + Pather.useWaypoint(partyUnit.area); + } + + let unit = Misc.poll(function () { + return Game.getPlayer(nick); + }, Time.minutes(1), 1000); + + if (unit && unit.distance > 15) { + Chat.say("Get closer."); + + if (!Misc.poll(() => unit.distance <= 15, Time.seconds(30), 50)) { + throw new ScriptError("You took to long. Going back to town"); + } + } + + if (unit && unit.distance <= 15 && !unit.dead) { + Misc.poll(function () { + Precast.doPrecast(true); + return unit.getState(sdk.states.BattleOrders); + }, 5000, 1000); + Pather.useWaypoint(sdk.areas.RogueEncampment); + } else { + throw new ScriptError("I don't see you"); + } + + return true; + } catch (e) { + if (e instanceof ScriptError) { + Chat.say((typeof e === "object" && e.message ? e.message : typeof e === "string" && e)); + } else { + console.error(e); + Chat.say("Internal Error"); + } + + return false; + } + }; + + const autoChant = function () { + if (!Config.ControlBot.Chant.Enchant) return false; + + let chanted = []; + let unit = Game.getPlayer(); + + if (unit) { + do { + if (unit === me.name || unit.dead) continue; + if (me.shitList.has(unit.name)) continue; + if (!Misc.inMyParty(unit.name) || unit.distance > 40) continue; + // allow rechanting someone if it's going to run out soon for them + if (!unit.getState(sdk.states.Enchant) + || (chantList.has(unit.name) && chantList.get(unit.name).reChant())) { + Packet.enchant(unit); + if (Misc.poll(() => unit.getState(sdk.states.Enchant), 500, 50)) { + chanted.push(unit.name); + chantList.has(unit.name) + ? chantList.get(unit.name).update() + : chantList.set(unit.name, new ChantTracker()); + } + } + } while (unit.getNext()); + } + + unit = Game.getMonster(); + + if (unit) { + do { + if (unit.getParent() + && chantList.has(unit.getParent().name) + && !unit.getState(sdk.states.Enchant) + && unit.distance <= 40) { + Packet.enchant(unit); + // not going to re-enchant the minions for now though, will think on how best to handle that later + if (Misc.poll(() => unit.getState(sdk.states.Enchant), 500, 50)) { + chanted.push(unit.name); + } + } + } while (unit.getNext()); + } + + return true; + }; + + const getLeg = function () { + if (me.getItem(sdk.quest.item.WirtsLeg)) { + return me.getItem(sdk.quest.item.WirtsLeg); + } + + let leg, gid, wrongLeg; + + if (!Config.ControlBot.Cows.GetLeg) { + leg = Game.getItem(sdk.items.quest.WirtsLeg); + + if (leg) { + do { + if (leg.name.includes("ÿc1")) { + wrongLeg = true; + } else if (leg.distance <= 15) { + gid = leg.gid; + Pickit.pickItem(leg); + + return me.getItem(-1, -1, gid); + } + } while (leg.getNext()); + } + + Chat.say("Bring the leg " + (wrongLeg ? "from this difficulty" : "") + " close to me."); + + return false; + } + + if (!Pather.journeyTo(sdk.areas.Tristram)) { + Chat.say("Failed to enter Tristram :("); + Town.goToTown(); + + return false; + } + + Pather.moveTo(25048, 5177); + + let wirt = Game.getObject(sdk.quest.chest.Wirt); + + for (let i = 0; i < 8; i += 1) { + wirt.interact(); + delay(500); + + leg = Game.getItem(sdk.quest.item.WirtsLeg); + + if (leg) { + gid = leg.gid; + + Pickit.pickItem(leg); + Town.goToTown(); + + return me.getItem(-1, -1, gid); + } + } + + Town.goToTown(); + Chat.say("Failed to get the leg :("); + + return false; + }; + + const getTome = function () { + let tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); + + if (tpTome.length < 2) { + if (!Storage.Inventory.CanFit({ sizex: 1, sizey: 2 })) { + if (tpTome.length === 1) { + return tpTome.first(); + } + } + let npc = Town.initNPC("Shop", "buyTpTome"); + if (!getInteractedNPC()) throw new Error("Failed to find npc"); + + let tome = npc.getItem(sdk.items.TomeofTownPortal); + + if (!!tome && tome.getItemCost(sdk.items.cost.ToBuy) < me.gold && tome.buy()) { + delay(500); + tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); + tpTome.forEach(function (book) { + if (book.isInInventory) { + let scroll = npc.getItem(sdk.items.ScrollofTownPortal); + + while (book.getStat(sdk.stats.Quantity) < 20) { + if (!!scroll && scroll.getItemCost(sdk.items.cost.ToBuy) < me.gold) { + scroll.buy(true); + } else { + break; + } + + delay(20); + } + } + }); + } else { + throw new Error("Failed to buy tome"); + } + } + + return tpTome.last(); + }; + + /** + * @param {string} nick + * @returns {boolean} + */ + const openPortal = function (nick) { + if (!Config.ControlBot.Cows.MakeCows) return false; + try { + if (!Misc.inMyParty(nick)) throw new ScriptError("Accept party invite, noob."); + if (Pather.getPortal(sdk.areas.MooMooFarm)) throw new ScriptError("Cow portal already open."); + // king dead or cain not saved + if (me.cows) throw new ScriptError("Can't open the portal because I killed Cow King."); + if (Config.ControlBot.Cows.GetLeg && !me.tristram && !!Config.Leader && !getParty(Config.Leader)) { + throw new ScriptError("Can't get leg because I don't have Cain quest."); + } + if (!me.diffCompleted) throw new ScriptError("Final quest incomplete."); + } catch (e) { + if (e instanceof ScriptError) { + Chat.say((typeof e === "object" && e.message ? e.message : typeof e === "string" && e)); + } else { + console.error(e); + Chat.say("Internal Error"); + } + return false; + } + + let leg = getLeg(); + if (!leg) return false; + if (!Storage.Inventory.CanFit({ sizex: 1, sizey: 2 })) { + // we don't have any space, put the leg in the stash to make room in invo + Storage.Stash.MoveTo(leg); + me.cancelUIFlags(); + } + + let tome = getTome(); + if (!tome) return false; + + if (!Town.openStash() + || !Cubing.emptyCube() + || !Storage.Cube.MoveTo(leg) + || !Storage.Cube.MoveTo(tome) + || !Cubing.openCube()) { + return false; + } + + transmute(); + delay(500); + + for (let i = 0; i < 10; i += 1) { + if (Pather.getPortal(sdk.areas.MooMooFarm)) { + return true; + } + + delay(200); + } + + Chat.say("Failed to open cow portal."); + + return false; + }; + + /** + * @param {string} nick + * @param {number} areaId + * @returns {boolean} + */ + const giveWp = function (nick, areaId) { + try { + if (!Misc.inMyParty(nick)) { + throw new ScriptError("Accept party invite, noob."); + } + + Chat.say("Giving wp " + getAreaName(areaId)); + + if (!wpNicks.has(nick)) { + wpNicks.set(nick, new WpTracker()); + } + + const check = wpNicks.get(nick); + if (check.singleWpRequests > 12) { + throw new ScriptError("You have spent all your waypoint requests for this game."); + } else if (check.requests > 1 && check.timeSinceLastRequest() < 60000) { + throw new ScriptError( + "You may request wp again in " + + Math.max(0, (60 - Math.floor(check.timeSinceLastRequest() / 1000))) + + " seconds." + ); + } + + let act = Misc.getPlayerAct(nick); + if (!wps.has(act)) return false; + + Pather.useWaypoint(areaId, true); + if (Config.ControlBot.Wps.SecurePortal) { + Attack.securePosition(me.x, me.y, 20, 1000); + } + Pather.makePortal(); + Chat.say(getAreaName(me.area) + " TP up"); + + if (!Misc.poll(() => (Game.getPlayer(nick)), Time.seconds(30), Time.seconds(1))) { + Chat.say("Aborting wp giving."); + } + + Town.doChores(); + Town.goToTown(1); + Town.move("portalspot"); + + check.updateSingle(); + + return true; + } catch (e) { + if (e instanceof ScriptError) { + Chat.say((typeof e === "object" && e.message ? e.message : typeof e === "string" && e)); + } else { + console.error(e); + Chat.say("Internal Error"); + } + + return false; + } + }; + + /** + * @param {string} nick + * @returns {boolean} + */ + const giveWps = function (nick) { + let next = false; + let stop = false; + /** + * @param {string} who + * @param {string} msg + */ + const nextWatcher = function (who, msg) { + if (who !== nick) return; + if (msg === "next") { + next = true; + } else if (msg === "stop") { + stop = true; + } + }; + + try { + if (!Misc.inMyParty(nick)) { + throw new ScriptError("Accept party invite, noob."); + } + + if (!wpNicks.has(nick)) { + wpNicks.set(nick, new WpTracker()); + } + + let check = wpNicks.get(nick); + if (check.requests > 4) { + throw new ScriptError("You have spent all your waypoint requests for this game."); + } else if (check.requests > 1 && check.timeSinceLastRequest() < 60000) { + throw new ScriptError( + "You may request wp again in " + + Math.max(0, (60 - Math.floor(check.timeSinceLastRequest() / 1000))) + + " seconds." + ); + } + + let act = Misc.getPlayerAct(nick); + if (!wps.has(act)) return false; + addEventListener("chatmsg", nextWatcher); + + for (let wp of wps.get(act)) { + if (stop || checkHostiles()) { + break; + } + + try { + if (next) { + next = false; + continue; + } + + Pather.useWaypoint(wp, true); + if (Config.ControlBot.Wps.SecurePortal) { + Attack.securePosition(me.x, me.y, 20, 1000); + } + Pather.makePortal(); + Chat.say(getAreaName(me.area) + " TP up"); + + if (!Misc.poll(() => (Game.getPlayer(nick) || next), Time.seconds(30), Time.seconds(1))) { + Chat.say("Aborting wp giving."); + + break; + } + next = false; + + delay(5000); + } catch (error) { + continue; + } + } + + Town.doChores(); + Town.goToTown(1); + Town.move("portalspot"); + + check.update(); + + return true; + } catch (e) { + if (e instanceof ScriptError) { + Chat.say((typeof e === "object" && e.message ? e.message : typeof e === "string" && e)); + } else { + console.error(e); + Chat.say("Internal Error"); + } + + return false; + } finally { + removeEventListener("chatmsg", nextWatcher); + } + }; + + const checkHostiles = function () { + let rval = false; + let party = getParty(); + + if (party) { + do { + if (party.name !== me.name && getPlayerFlag(me.gid, party.gid, 8)) { + rval = true; + + if (Config.ShitList && !me.shitList.has(party.name)) { + me.shitList.add(party.name); + } + } + } while (party.getNext()); + } + + return rval; + }; + + /** + * @param {string} command + * @returns {boolean} + */ + const floodCheck = function (command) { + if (!command || command.length < 2) return false; + let [cmd, nick] = command; + + // ignore overhead messages + if (!nick) return true; + // ignore messages not related to our commands + if (!actions.has(cmd.toLowerCase())) return false; + + if (!cmdNicks.has(nick)) { + cmdNicks.set(nick, new PlayerTracker()); + } + const player = cmdNicks.get(nick); + + if (player.ignored) { + if (getTickCount() - player.ignored < Time.minutes(1)) { + return true; // ignore flooder + } + + // unignore flooder + player.unIgnore(); + } + + player.commands += 1; + + if (getTickCount() - player.firstCmd < Time.seconds(10)) { + if (player.commands > 5) { + player.ignored = getTickCount(); + Chat.whisper(nick, "You are being ignored for 60 seconds because of flooding."); + } + } else { + player.resetCmds(); + } + + return false; + }; + + const pickGoldPiles = function () { + /** @type {PathNode} */ + const startPos = { x: me.x, y: me.y }; + let gold = Game.getItem(sdk.items.Gold); + + if (gold) { + do { + if (gold.onGroundOrDropping && gold.distance <= 20 && Pickit.canPick(gold)) { + Pickit.pickItem(gold) && Chat.overhead("Thank you!", true); + if (startPos.distance > 5) { + Pather.move(startPos); + } + } + } while (gold.getNext()); + } + }; + + const dropGold = function (nick) { + try { + if (me.gold < MIN_GOLD) { + throw new ScriptError("Not enough gold to drop."); + } + if (givenGold.has(nick)) { + throw new ScriptError("Already dropped gold this game for you. Don't be greedy."); + } + + let unit = Game.getPlayer(nick); + + if (unit && unit.distance > 15) { + throw new ScriptError("Get closer."); + } + + if (!unit) { + let partyUnit = getParty(nick); + + if (!Misc.poll(() => partyUnit.inTown, 500, 50)) { + throw new ScriptError("You need to be in one of the towns."); + } + // wait until party area is readable? + Chat.say("Wait for me at waypoint."); + Town.goToTown(sdk.areas.actOf(partyUnit.area)); + + unit = Game.getPlayer(nick); + } + + if (unit) { + if (me.getStat(sdk.stats.Gold) < 5000) { + Town.openStash() && gold(5000, 4); + me.cancelUIFlags(); + } + + // drop the gold + gold(5000); + /** @type {ItemUnit} */ + let droppedGold = Misc.poll(function () { + let _gold = Game.getItem(sdk.items.Gold); + if (_gold && _gold.onGroundOrDropping && _gold.getStat(sdk.stats.Gold) === 5000) { + return _gold; + } + return false; + }, Time.seconds(30), 1000); + + if (!droppedGold) { + throw new ScriptError("Failed to drop gold."); + } + + // watch for the gold dissapearing + let picked = false; + Misc.poll(function () { + let _gold = Game.getItem(sdk.items.Gold, sdk.items.mode.onGround, droppedGold.gid); + if (_gold) return false; + picked = true; + return !_gold; + }, Time.seconds(30), 1000); + + if (!picked) { + Pickit.pickItem(droppedGold); + throw new ScriptError("Failed to pick gold."); + } else { + givenGold.add(nick); + Chat.say("yw " + nick); + } + } else { + throw new ScriptError("I don't see you"); + } + } catch (e) { + if (e instanceof ScriptError) { + Chat.say((typeof e === "object" && e.message ? e.message : typeof e === "string" && e)); + } else { + console.error(e); + Chat.say("Internal Error"); + } + } + }; + + /** + * @param {string} nick + * @param {string} msg + * @returns {boolean} + */ + function chatEvent (nick, msg) { + if (!nick || !msg) return; + if (nick === me.name) return; + msg = msg.toLowerCase(); + const full = msg.replace(/[\'\<\>\[\]\{\}\(\)\!\@\#\$\%\^\&\*\_\+\=\|\~\`\;\:\"\?\,\.\/\\]/g, ""); + if (msg.match(/^rush /gi)) { + msg = msg.split(" ")[1]; + } else if (msg.match(/^givewp /gi)) { + msg = msg.slice(0, 6).trim(); + } + if (commandAliases.has(msg)) { + msg = commandAliases.get(msg); + } + if (!actions.has(msg)) { + return; + } + if (me.shitList.has(nick)) { + Chat.say("No commands for the shitlisted."); + } else { + if (running.nick === nick && running.command === msg) { + console.debug("Command already running."); + return; + } + if (!floodCheck([msg, nick])) { + if (["help", "timeleft", "ngyes", "ngno"].includes(msg)) { + actions.get(msg).run(nick); + return; + } + } + let index = queue.findIndex(function (cmd) { + return cmd[0] === msg && cmd[1] === nick; + }); + if (index > -1) { + Chat.whisper(nick, "You already requested this command. Queue position: " + (index + 1)); + } else { + queue.push([msg, nick, full]); + console.log(queue); + if (queue.length > 1 || running.nick !== "") { + Chat.whisper(nick, msg + " has been added to the queue. Queue position: " + (queue.length + 1)); + } + } + } + } + + // eslint-disable-next-line no-unused-vars + function gameEvent (mode, param1, param2, name1, name2) { + switch (mode) { + case 0x02: // "%Name1(%Name2) joined our world. Diablo's minions grow stronger." + // idle in town + me.inTown && me.mode === sdk.player.mode.StandingInTown && greet.push(name1); + if (name2) { + players.set(name1, "*" + name2); + } else { + players.set(name1, ""); + } + + break; + case 0x00: // "%Name1(%Name2) dropped due to time out." + case 0x01: // "%Name1(%Name2) dropped due to errors." + case 0x03: // "%Name1(%Name2) left our world. Diablo's minions weaken." + players.delete(name1); + if (ngVote.active) { + ngVote.votesYes.delete(name1); + ngVote.votesNo.delete(name1); + } + + break; + } + } + + /** + * @typedef {Object} Action + * @property {string} desc + * @property {boolean} hostileCheck + * @property {boolean} [complete] + * @property {function(): void} [markAsComplete] + * @property {function(): boolean | void} run + */ + /** @type {Map boolean} run + */ + function RushAction (desc, run) { + this.desc = desc; + this.hostileCheck = true; + this.complete = false; + this.run = run; + } + RushAction.prototype.markAsComplete = function () { + this.complete = true; + }; + /** @type {Map { + if (!value.desc.length) return; + if (value.complete) return; + if (value.desc.includes("Rush")) return; + // let desc = (key + " (" + value.desc + "), "); + let desc = value.desc.includes("experimental") ? ("(" + value.desc + "), ") : (key + ", "); + if (str.length + desc.length > MAX_CHAT_LENGTH - (nick.length + 2)) { + msg.push(str); + str = ""; + } + str += desc; + }); + str.length && msg.push(str); + str = "Rush cmds: "; + _actions.forEach((value, key) => { + if (!value.desc.length) return; + if (value.complete) return; + if (!value.desc.includes("Rush")) return; + let desc = (key + ", "); + if (str.length + desc.length > MAX_CHAT_LENGTH - (nick.length + 2)) { + msg.push(str); + str = ""; + } + str += desc; + }); + str.length && msg.push(str); + + !cmdNicks.has(nick) && cmdNicks.set(nick, new PlayerTracker()); + if (cmdNicks.has(nick) && cmdNicks.get(nick).seenHelpMsg) { + Chat.message(nick, "You have seen the help menu before this game please refer to message log"); + } else { + msg.forEach(function (m) { + // Chat.whisper(nick, m); + Chat.say(m); + }); + } + cmdNicks.get(nick).seenHelpMsg = true; + } + }); + _actions.set("timeleft", { + desc: "Remaining time for this game", + hostileCheck: false, + run: function () { + let tick = Time.minutes(Config.ControlBot.GameLength) - getTickCount() + startTime; + let m = Math.floor(tick / 60000); + let s = Math.floor((tick / 1000) % 60); + + Chat.say( + "Time left: " + + (m ? m + " minute" + (m > 1 ? "s" : "") + + ", " : "") + s + " second" + (s > 1 ? "s." : ".") + ); + } + }); + _actions.set("ngvote", { + desc: "Vote for next game", + hostileCheck: false, + run: function (nick) { + if (ngVote.active) { + Chat.say("NGVote is already active. Current count: " + ngVote.votesYes.size); + return; + } + if (getTickCount() - startTime < Time.minutes(3)) { + Chat.say( + "Can't vote for ng yet. Must be in game for at least 3 minutes. Remaining: " + + Math.round((Time.minutes(3) - (getTickCount() - startTime)) / 1000) + " seconds." + ); + return; + } + ngVote.begin(); + ngVote.vote(nick, "yes"); + const votesNeeded = ngVote.votesNeeded(); + Chat.say(nick + " voted for next game. Votes Needed: " + votesNeeded + ". Type ngyes/ngno"); + } + }); + _actions.set("ngyes", { + desc: "", + hostileCheck: false, + run: function (nick) { + if (!ngVote.active) return; + ngVote.vote(nick, "yes"); + ngVote.checkCount(); + } + }); + _actions.set("ngno", { + desc: "", + hostileCheck: false, + run: function (nick) { + if (!ngVote.active) return; + ngVote.vote(nick, "no"); + ngVote.checkCount(); + } + }); + + if (Config.ControlBot.DropGold) { + _actions.set("dropgold", { + desc: "Drop 5k gold", + hostileCheck: false, + run: dropGold + }); + } + + if (Config.ControlBot.Chant.Enchant + && Skill.canUse(sdk.skills.Enchant)) { + _actions.set("chant", { + desc: "Give enchant", + hostileCheck: false, + run: enchant + }); + } else { + Config.ControlBot.Chant.AutoEnchant = false; + } + + if (Config.ControlBot.Cows.MakeCows && !me.cows) { + _actions.set("cows", { + desc: "Open cow level", + hostileCheck: true, + run: openPortal + }); + } + + if (Config.ControlBot.Wps.GiveWps) { + _actions.set("wps", { + desc: "Give wps in act", + hostileCheck: true, + run: giveWps + }); + + _actions.set("givewp", { + desc: "givewp - experimental", + hostileCheck: true, + run: giveWp + }); + } + + if (Config.ControlBot.Bo + && (Skill.canUse(sdk.skills.BattleOrders) || Precast.haveCTA > 0)) { + _actions.set("bo", { + desc: "Bo at wp", + hostileCheck: true, + run: bo + }); + } + + if (Config.ControlBot.Rush) { + if (Config.ControlBot.Rush.Andy) { + _actions.set("andy", new RushAction("Rush Andariel", andariel)); + } + if (Config.ControlBot.Rush.Bloodraven) { + _actions.set("raven", new RushAction("Rush Bloodraven", bloodraven)); + } + if (Config.ControlBot.Rush.Smith) { + _actions.set("smith", new RushAction("Rush Smith", smith)); + } + if (Config.ControlBot.Rush.Cube) { + _actions.set("cube", new RushAction("Rush Cube", cube)); + } + if (Config.ControlBot.Rush.Radament) { + _actions.set("rada", new RushAction("Rush Radament", radament)); + } + if (Config.ControlBot.Rush.Staff) { + _actions.set("staff", new RushAction("Rush Staff", staff)); + } + if (Config.ControlBot.Rush.Amulet) { + _actions.set("amu", new RushAction("Rush Amulet", amulet)); + } + if (Config.ControlBot.Rush.Summoner) { + _actions.set("summoner", new RushAction("Rush Summoner", summoner)); + } + if (Config.ControlBot.Rush.Duriel) { + _actions.set("duri", new RushAction("Rush Duriel", duriel)); + } + if (Config.ControlBot.Rush.Gidbinn) { + _actions.set("gidbinn", new RushAction("Rush Gidbinn", gidbinn)); + } + if (Config.ControlBot.Rush.LamEsen) { + _actions.set("lamesen", new RushAction("Rush Lamesen", lamesen)); + } + if (Config.ControlBot.Rush.Eye) { + _actions.set("eye", new RushAction("Rush Eye", eye)); + } + if (Config.ControlBot.Rush.Brain) { + _actions.set("brain", new RushAction("Rush Brain", brain)); + } + if (Config.ControlBot.Rush.Heart) { + _actions.set("heart", new RushAction("Rush Heart", heart)); + } + if (Config.ControlBot.Rush.Travincal) { + _actions.set("trav", new RushAction("Rush Travincal", travincal)); + } + if (Config.ControlBot.Rush.Mephisto) { + _actions.set("meph", new RushAction("Rush Mephisto", mephisto)); + } + if (Config.ControlBot.Rush.Izual) { + _actions.set("izzy", new RushAction("Rush Izual", izual)); + } + if (Config.ControlBot.Rush.Diablo) { + _actions.set("diablo", new RushAction("Rush Diablo", diablo)); + } + if (Config.ControlBot.Rush.Shenk) { + _actions.set("shenk", new RushAction("Rush Shenk", shenk)); + } + if (Config.ControlBot.Rush.Anya) { + _actions.set("anya", new RushAction("Rush Anya", anya)); + } + if (Config.ControlBot.Rush.Ancients) { + _actions.set("ancients", new RushAction("Rush Ancients", ancients)); + } + if (Config.ControlBot.Rush.Baal) { + _actions.set("baal", new RushAction("Rush Baal", baal)); + } + } + + return _actions; + })(); + + /** @type {Map} */ + const commandAliases = new Map([ + ["andariel", "andy"], + ["bloodraven", "raven"], + ["radament", "rada"], + ["amulet", "amu"], + ["ammy", "amu"], + ["duriel", "duri"], + ["dury", "duri"], + ["tome", "lamesen"], + ["travincal", "trav"], + ["mephisto", "meph"], + ["izual", "izzy"], + ["bome", "bo"], + ["time", "timeleft"], + ["enchant", "chant"], + ]); + + /** @param {[string, string, string]} command */ + const runAction = function (command) { + if (!command || command.length < 2) return false; + console.debug("Checking command: " + command); + let [cmd, nick, full] = command; + if (!Misc.inMyParty(nick)) { + Chat.say("Accept party invite, noob. Cmds only allowed for party members."); + return false; + } + if (cmd.match(/^rush /gi)) { + cmd = cmd.split(" ")[1]; + } + if (commandAliases.has(cmd.toLowerCase())) { + cmd = commandAliases.get(cmd.toLowerCase()); + } + + if (!actions.has(cmd.toLowerCase())) return false; + let action = actions.get(cmd.toLowerCase()); + if (action.desc.includes("Rush") && action.complete) { + Chat.whisper(nick, cmd + " disabled because it's already completed."); + return false; + } + if (action.hostileCheck && checkHostiles()) { + Chat.say("Command disabled because of hostiles."); + return false; + } + + if (full.match(/^givewp /gi)) { + let [, areaName] = full.split("givewp "); + if (areaName) { + /** @type {AreaDataObj} */ + let area = AreaData.findByName(areaName); + if (area.Waypoint === 255) { + Chat.say(area.LocaleString + " isn't a valid wp area to ask for"); + + return false; + } + + running.nick = nick; + running.command = cmd; + console.debug(running); + + return action.run(nick, area.Index); + } + } + + running.nick = nick; + running.command = cmd; + console.debug(running); + + return action.run(nick); + }; + + // START + let gameEndWarningAnnounced = false; + include("oog/ShitList.js"); + Config.ShitList && ShitList.read().forEach((name) => me.shitList.add(name)); + + try { + addEventListener("chatmsg", chatEvent); + addEventListener("gameevent", gameEvent); + Town.doChores(); + Town.goToTown(1); + Town.move("portalspot"); + + // check who is in game in cased we missed the gameevent or this was a restart + let party = getParty(); + if (party) { + do { + if (party.name !== me.name && !players.has(party.name)) { + players.set(party.name, ""); + } + } while (party.getNext()); + } + + while (true) { + while (greet.length > 0) { + let nick = greet.shift(); + + if (!me.shitList.has(nick)) { + // Chat.say("Welcome, " + nick + "! For a list of commands say 'help'"); + Chat.overhead("Welcome, " + nick + "! For a list of commands say 'help'"); + } + } + + Town.getDistance("portalspot") > 5 && Town.move("portalspot"); + + if (queue.length > 0) { + try { + let command = queue.shift(); + if (command && !floodCheck(command)) { + if (runAction(command)) { + // check if command was for rush, if so we need to remove that as an option since its now completed + if (actions.get(running.command).desc.includes("Rush")) { + console.log("Disabling " + running.command + " from actions"); + actions.get(running.command).markAsComplete(); + } + } + } + } catch (e) { + Misc.errorReport(e); + } + running.nick = ""; + running.command = ""; + } + + me.act > 1 && Town.goToTown(1); + Config.ControlBot.Chant.AutoEnchant && autoChant(); + + if (me.gold < MIN_GOLD && players.size > 1) { + Chat.overhead( + "I am low on gold, to keep this service up please donate by dropping gold near me." + + " I need at least " + (MIN_GOLD - me.gold) + " gold." + ); + } + pickGoldPiles(); + + if (ngVote.active) { + if (ngVote.elapsed() > Time.minutes(2) && !ngVote.nextGame) { + Chat.say("Not enough votes to start next game. Votes gathered " + ngVote.votesYes.size); + ngVote.reset(); + } else if (ngVote.elapsed() > Time.seconds(30) && !ngVote.nextGame) { + ngVote.checkCount(); + } + } + + if (getTickCount() - startTime >= maxTime || ngVote.nextGame) { + if (Config.ControlBot.EndMessage) { + Chat.say(Config.ControlBot.EndMessage); + } + delay(1000); + + break; + } else if (!gameEndWarningAnnounced && getTickCount() - startTime >= maxTime - Time.seconds(30)) { + let remaining = Math.round((maxTime - (getTickCount() - startTime)) / 1000); + Chat.say("Next game in " + (Math.max(0, remaining)) + " seconds."); + gameEndWarningAnnounced = true; + } + + delay(200); + } + } finally { + removeEventListener("chatmsg", chatEvent); + removeEventListener("gameevent", gameEvent); + } + + return true; + }, + { + startArea: sdk.areas.RogueEncampment, + preAction: null, + } +); diff --git a/d2bs/kolbot/libs/scripts/Corpsefire.js b/d2bs/kolbot/libs/scripts/Corpsefire.js new file mode 100644 index 000000000..83bd8a449 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Corpsefire.js @@ -0,0 +1,27 @@ +/** +* @filename Corpsefire.js +* @author kolton +* @desc kill Corpsefire and optionally clear Den of Evil +* +*/ + +const Corpsefire = new Runnable( + function Corpsefire () { + Pather.useWaypoint(sdk.areas.ColdPlains); + Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.DenofEvil], true) + || !Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Corpsefire, 0, 0, false, true)) { + throw new Error("Failed to move to Corpsefire"); + } + + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Corpsefire)); + Config.Corpsefire.ClearDen && Attack.clearLevel(); + + return true; + }, + { + startArea: sdk.areas.ColdPlains, + bossid: getLocaleString(sdk.locale.monsters.Corpsefire), + } +); diff --git a/d2bs/kolbot/libs/scripts/Countess.js b/d2bs/kolbot/libs/scripts/Countess.js new file mode 100644 index 000000000..4c2e6a8b1 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Countess.js @@ -0,0 +1,39 @@ +/** +* @filename Countess.js +* @author kolton +* @desc kill The Countess and optionally kill Ghosts along the way +* +*/ + +const Countess = new Runnable( + function Countess () { + Pather.useWaypoint(sdk.areas.BlackMarsh); + Precast.doPrecast(true); + + if (!Pather.moveToExit([ + sdk.areas.ForgottenTower, sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, + sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TowerCellarLvl5 + ], true)) throw new Error("Failed to move to Countess"); + + let poi = Game.getPresetObject(me.area, sdk.objects.SuperChest); + if (!poi) throw new Error("Failed to move to Countess (preset not found)"); + + switch (poi.roomx * 5 + poi.x) { + case 12565: + Pather.moveTo(12578, 11043); + break; + case 12526: + Pather.moveTo(12548, 11083); + break; + } + + Attack.clear(20, 0, getLocaleString(sdk.locale.monsters.TheCountess)); + Config.OpenChests.Enabled && Misc.openChestsInArea(); + + return true; + }, + { + startArea: sdk.areas.BlackMarsh, + bossid: getLocaleString(sdk.locale.monsters.TheCountess), + } +); diff --git a/d2bs/kolbot/libs/scripts/Cows.js b/d2bs/kolbot/libs/scripts/Cows.js new file mode 100644 index 000000000..e67af2e67 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Cows.js @@ -0,0 +1,161 @@ +/** +* @filename Cows.js +* @author kolton, theBGuy +* @desc clear the Moo Moo Farm without killing the Cow King +* +*/ + +const Cows = new Runnable( + function Cows () { + include("core/Common/Cows.js"); + + const getLeg = function () { + if (me.wirtsleg) return me.wirtsleg; + + Pather.useWaypoint(sdk.areas.StonyField); + Precast.doPrecast(true); + Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 8, 8); + + if (!Misc.poll(() => { + let p = Pather.getPortal(sdk.areas.Tristram); + return (p && Pather.usePortal(sdk.areas.Tristram, null, p)); + }, Time.minutes(1), 1000)) { + throw new Error("Tristram portal not found"); + } + + Pather.moveTo(25048, 5177); + + let wirt = Game.getObject(sdk.quest.chest.Wirt); + + for (let i = 0; i < 8; i += 1) { + wirt.interact(); + delay(500); + + let leg = Game.getItem(sdk.quest.item.WirtsLeg); + + if (leg) { + let gid = leg.gid; + + Pickit.pickItem(leg); + Town.goToTown(); + + return me.getItem(-1, -1, gid); + } + } + + throw new Error("Failed to get the leg"); + }; + + const getTome = function () { + let tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); + + if (tpTome.length < 2) { + let npc = Town.initNPC("Shop", "buyTpTome"); + + if (!getInteractedNPC()) { + throw new Error("Failed to find npc"); + } + + let tome = npc.getItem(sdk.items.TomeofTownPortal); + + if (!!tome && tome.getItemCost(sdk.items.cost.ToBuy) < me.gold && tome.buy()) { + delay(500); + tpTome = me.findItems(sdk.items.TomeofTownPortal, sdk.items.mode.inStorage, sdk.storage.Inventory); + tpTome.forEach(function (book) { + if (book.isInInventory) { + let scroll = npc.getItem(sdk.items.ScrollofTownPortal); + while (book.getStat(sdk.stats.Quantity) < 20) { + if (!!scroll && scroll.getItemCost(sdk.items.cost.ToBuy) < me.gold) { + scroll.buy(true); + } else { + break; + } + + delay(20); + } + } + }); + } else { + throw new Error("Failed to buy tome"); + } + } + + return tpTome.last(); + }; + + const openPortal = function (leg, tome) { + if (!Town.openStash()) throw new Error("Failed to open stash"); + if (!Cubing.emptyCube()) throw new Error("Failed to empty cube"); + if (!Storage.Cube.MoveTo(leg) || !Storage.Cube.MoveTo(tome) || !Cubing.openCube()) { + throw new Error("Failed to cube leg and tome"); + } + + transmute(); + delay(1000); + me.cancelUIFlags(); + + for (let i = 0; i < 10; i += 1) { + if (Pather.getPortal(sdk.areas.MooMooFarm)) { + return true; + } + + delay(200); + } + + throw new Error("Portal not found"); + }; + + + // we can begin now + try { + if (!me.diffCompleted) throw new Error("Final quest incomplete."); + + Town.goToTown(1); + Town.doChores(); + Town.move("stash"); + + // Check to see if portal is already open, if not get the ingredients + if (!Pather.getPortal(sdk.areas.MooMooFarm)) { + if (Config.Cows.DontMakePortal) throw new Error("NOT PORTAL MAKER"); + if (!me.tristram) throw new Error("Cain quest incomplete"); + if (me.cows) throw new Error("Already killed the Cow King."); + + let leg = getLeg(); + let tome = getTome(); + openPortal(leg, tome); + } + } catch (e) { + typeof e === "object" && e.message && e.message !== "NOT PORTAL MAKER" && console.error(e); + + if (Misc.getPlayerCount() > 1) { + Town.goToTown(1); + Town.move("stash"); + console.log("ÿc9(Cows) :: ÿc0Waiting 1 minute to see if anyone else opens the cow portal"); + + if (!Misc.poll(() => Pather.getPortal(sdk.areas.MooMooFarm), Time.minutes(3), 2000)) { + throw new Error("No cow portal"); + } + } else { + return false; + } + } + + if (Config.Cows.JustMakePortal) { + if (Pather.getPortal(sdk.areas.MooMooFarm)) { + return true; + } else { + throw new Error("I failed to make cow portal"); + } + } + + Pather.usePortal(sdk.areas.MooMooFarm); + Precast.doPrecast(false); + Config.Cows.KillKing ? Attack.clearLevel() : Common.Cows.clearCowLevel(); + + return true; + }, + { + startArea: sdk.areas.RogueEncampment, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Crafting.js b/d2bs/kolbot/libs/scripts/Crafting.js new file mode 100644 index 000000000..1af6bb943 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Crafting.js @@ -0,0 +1,474 @@ +/** +* @filename Crafting.js +* @author kolton +* @desc Part of CraftingSystem +* +*/ + +let info; +let gameRequest = false; + +const Crafting = new Runnable( + function Crafting () { + info = CraftingSystem.getInfo(); + + if (!info || !info.worker) throw new Error("Bad Crafting System config."); + + me.maxgametime = 0; + Town.goToTown(1); + Town.doChores(); + Town.move("stash"); + updateInfo(); + pickItems(); + + addEventListener("copydata", + function (mode, msg) { + let obj, rval; + + if (mode === 0) { + try { + obj = JSON.parse(msg); + } catch (e) { + return false; + } + + if (obj) { + switch (obj.name) { + case "GetGame": + if (info.Collectors.includes(obj.profile)) { + print("GetGame: " + obj.profile); + sendCopyData(null, obj.profile, 4, me.gamename + "/" + me.gamepassword); + + gameRequest = true; + } + + break; + case "GetSetInfo": + if (info.Collectors.includes(obj.profile)) { + print("GetSetInfo: " + obj.profile); + + rval = []; + + for (let i = 0; i < info.Sets.length; i += 1) { + rval.push(info.Sets[i].Enabled ? 1 : 0); + } + + print(rval); + + sendCopyData(null, obj.profile, 4, JSON.stringify({ name: "SetInfo", value: rval })); + } + + break; + } + } + } + + return true; + }); + + for (let i = 0; i < Cubing.recipes.length; i += 1) { + Cubing.recipes[i].Level = 0; + } + + while (true) { + for (let i = 0; i < info.Sets.length; i += 1) { + switch (info.Sets[i].Type) { + case "crafting": + let num = 0; + let npcName = getNPCName(info.Sets[i].BaseItems); + + if (npcName) { + num = countItems(info.Sets[i].BaseItems, 4); + + if (num < info.Sets[i].SetAmount) { + shopStuff(npcName, info.Sets[i].BaseItems, info.Sets[i].SetAmount); + } + } + + break; + case "cubing": // Nothing to do currently + break; + case "runewords": // Nothing to do currently + break; + } + } + + me.act !== 1 && Town.goToTown(1) && Town.move("stash"); + + if (gameRequest) { + for (let i = 0; i < 10; i += 1) { + if (Misc.getPlayerCount() > 1) { + while (Misc.getPlayerCount() > 1) { + delay(200); + } + + break; + } else { + break; + } + } + + gameRequest = false; + } + + pickItems(); + Cubing.update(); + Runewords.buildLists(); + Cubing.doCubing(); + Runewords.makeRunewords(); + delay(2000); + } + }, + { + startArea: sdk.areas.RogueEncampment, + preAction: null + } +); + +function getNPCName (idList) { + for (let i = 0; i < idList.length; i += 1) { + switch (idList[i]) { + case sdk.items.LightBelt: + case sdk.items.SharkskinBelt: + return "elzix"; + case sdk.items.Belt: + case sdk.items.MeshBelt: + case sdk.items.LightPlatedBoots: + case sdk.items.BattleBoots: + return "fara"; + } + } + + return false; +} + +function countItems (idList, quality) { + let count = 0; + let item = me.getItem(-1, sdk.items.mode.inStorage); + + if (item) { + do { + if (idList.includes(item.classid) && item.quality === quality) { + count += 1; + } + } while (item.getNext()); + } + + return count; +} + +function updateInfo () { + if (info) { + let items = me.findItems(-1, sdk.items.mode.inStorage); + + for (let i = 0; i < info.Sets.length; i += 1) { + MainSwitch: + switch (info.Sets[i].Type) { + // Always enable crafting because the base can be shopped + // Recipes with bases that can't be shopped don't need to be used with CraftingSystem + case "crafting": + info.Sets[i].Enabled = true; + + break; + // Enable only if we have a viable item to cube + // Currently the base needs to be added manually to the crafter + case "cubing": + !items && (items = []); + + // Enable the recipe if we have an item that matches both bases list and Cubing list + // This is not a perfect check, it might not handle every case + for (let j = 0; j < items.length; j += 1) { + if (info.Sets[i].BaseItems.includes(items[j].classid) // Item is on the bases list + && AutoMule.cubingIngredient(items[j])) { // Item is a valid Cubing ingredient + print("Base found: " + items[j].classid); + + info.Sets[i].Enabled = true; + + break MainSwitch; + } + } + + info.Sets[i].Enabled = false; + + break; + // Enable only if we have a viable runeword base + // Currently the base needs to be added manually to the crafter + case "runewords": + !items && (items = []); + + // Enable the recipe if we have an item that matches both bases list and Cubing list + // This is not a perfect check, it might not handle every case + for (let j = 0; j < items.length; j += 1) { + if (info.Sets[i].BaseItems.includes(items[j].classid) // Item is on the bases list + && runewordIngredient(items[j])) { // Item is a valid Runeword ingredient + print("Base found: " + items[j].classid); + + info.Sets[i].Enabled = true; + + break MainSwitch; + } + } + + info.Sets[i].Enabled = false; + + break; + } + } + + return true; + } + + return false; +} + +function runewordIngredient (item) { + if (Runewords.validGids.includes(item.gid)) return true; + + let baseGids = []; + + for (let i = 0; i < Config.Runewords.length; i += 1) { + let base = (Runewords.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0)) + || Runewords.getBase(Config.Runewords[i][0], Config.Runewords[i][1], (Config.Runewords[i][2] || 0), true)); + + base && baseGids.push(base.gid); + } + + return baseGids.includes(item.gid); +} + +function pickItems () { + let items = []; + let item = Game.getItem(-1, sdk.items.mode.onGround); + + if (item) { + updateInfo(); + + do { + if (checkItem(item) || item.classid === sdk.items.Gold || Pickit.checkItem(item).result > 0) { + items.push(copyUnit(item)); + } + } while (item.getNext()); + } + + while (items.length) { + if (Pickit.canPick(items[0]) && Storage.Inventory.CanFit(items[0])) { + Pickit.pickItem(items[0]); + } + + items.shift(); + delay(1); + } + + Town.stash(); +} + +function checkItem (item) { + for (let i = 0; i < info.Sets.length; i += 1) { + if (info.Sets[i].Enabled) { + switch (info.Sets[i].Type) { + case "crafting": + // Magic item + // Valid crafting base + if (item.magic && info.Sets[i].BaseItems.includes(item.classid)) return true; + // Valid crafting ingredient + if (info.Sets[i].Ingredients.includes(item.classid)) return true; + + break; + case "cubing": + // There is no base check, item has to be put manually on the character + if (info.Sets[i].Ingredients.includes(item.classid)) return true; + + break; + case "runewords": + // There is no base check, item has to be put manually on the character + if (info.Sets[i].Ingredients.includes(item.classid)) return true; + + break; + } + } + } + + return false; +} + +function shopStuff (npcId, classids, amount) { + print("shopStuff: " + npcId + " " + amount); + + let wpArea, town, path, menuId, npc; + let leadTimeout = 30; + let leadRetry = 3; + + this.mover = function (npc, path) { + path = this.processPath(npc, path); + + for (let i = 0; i < path.length; i += 2) { + let j; + + Pather.moveTo(path[i] - 3, path[i + 1] - 3); + moveNPC(npc, path[i], path[i + 1]); // moving npc doesn't work, probably should be removed? + + for (j = 0; j < leadTimeout; j += 1) { + while (npc.mode === sdk.npcs.mode.Walking) { + delay(100); + } + + if (getDistance(npc.x, npc.y, path[i], path[i + 1]) < 4) { + break; + } + + if (j > 0 && j % leadRetry === 0) { + moveNPC(npc, path[i], path[i + 1]); + } + + delay(1000); + } + + if (j === leadTimeout) { + return false; + } + } + + delay(1000); + + return true; + }; + + this.processPath = function (npc, path) { + let cutIndex = 0; + let dist = 100; + + for (let i = 0; i < path.length; i += 2) { + if (getDistance(npc, path[i], path[i + 1]) < dist) { + cutIndex = i; + dist = getDistance(npc, path[i], path[i + 1]); + } + } + + return path.slice(cutIndex); + }; + + this.shopItems = function (classids, amount) { + let npc = getInteractedNPC(); + + if (npc) { + let items = npc.getItemsEx(); + + if (items.length) { + for (let i = 0; i < items.length; i += 1) { + if (Storage.Inventory.CanFit(items[i]) + && Pickit.canPick(items[i]) + && me.gold >= items[i].getItemCost(sdk.items.cost.ToBuy) + && classids.includes(items[i].classid)) { + + //print("Bought " + items[i].name); + items[i].buy(); + + let num = countItems(classids, sdk.items.quality.Magic); + + if (num >= amount) { + return true; + } + } + } + } + } + + return gameRequest; + }; + + Town.doChores(); + + switch (npcId.toLowerCase()) { + case "fara": + if (!Town.goToTown(2) || !Town.move(NPC.Fara)) throw new Error("Failed to get to NPC"); + + wpArea = sdk.areas.A2SewersLvl2; + town = sdk.areas.LutGholein; + path = [5112, 5094, 5092, 5096, 5078, 5098, 5070, 5085]; + menuId = "Repair"; + npc = Game.getNPC(NPC.Fara); + + break; + case "elzix": + if (!Town.goToTown(2) || !Town.move(NPC.Elzix)) throw new Error("Failed to get to NPC"); + + wpArea = sdk.areas.A2SewersLvl2; + town = sdk.areas.LutGholein; + path = [5038, 5099, 5059, 5102, 5068, 5090, 5067, 5086]; + menuId = "Shop"; + npc = Game.getNPC(NPC.Elzix); + + break; + case "drognan": + if (!Town.goToTown(2) || !Town.move(NPC.Drognan)) throw new Error("Failed to get to NPC"); + + wpArea = sdk.areas.A2SewersLvl2; + town = sdk.areas.LutGholein; + path = [5093, 5049, 5088, 5060, 5093, 5079, 5078, 5087, 5070, 5085]; + menuId = "Shop"; + npc = Game.getNPC(NPC.Drognan); + + break; + case "ormus": + if (!Town.goToTown(3) || !Town.move(NPC.Ormus)) throw new Error("Failed to get to NPC"); + + wpArea = sdk.areas.DuranceofHateLvl2; + town = sdk.areas.KurastDocktown; + path = [5147, 5089, 5156, 5075, 5157, 5063, 5160, 5050]; + menuId = "Shop"; + npc = Game.getNPC(NPC.Ormus); + + break; + case "anya": + if (!Town.goToTown(5) || !Town.move(NPC.Anya)) throw new Error("Failed to get to NPC"); + + wpArea = sdk.areas.WorldstoneLvl2; + town = sdk.areas.Harrogath; + path = [5122, 5119, 5129, 5105, 5123, 5087, 5115, 5068]; + menuId = "Shop"; + npc = Game.getNPC(NPC.Anya); + + break; + case "malah": + if (!Town.goToTown(5) || !Town.move(NPC.Malah)) throw new Error("Failed to get to NPC"); + + wpArea = sdk.areas.CrystalizedPassage; + town = sdk.areas.Harrogath; + path = [5077, 5032, 5089, 5025, 5100, 5021, 5106, 5051, 5116, 5071]; + menuId = "Shop"; + npc = Game.getNPC(NPC.Malah); + + break; + default: + throw new Error("Invalid shopbot NPC."); + } + + if (!npc) throw new Error("Failed to find NPC."); + if (!this.mover(npc, path)) throw new Error("Failed to move NPC"); + + Town.move("waypoint"); + + let tickCount = getTickCount(); + + while (true) { + if (me.area === town) { + if (npc.startTrade(menuId)) { + if (this.shopItems(classids, amount)) return true; + } + + me.cancel(); + } + + me.area === town && Pather.useWaypoint(wpArea); + me.area === wpArea && Pather.useWaypoint(town); + + // end script 5 seconds before we need to exit + if (getTickCount() - tickCount > me.maxgametime - 5000) { + break; + } + + delay(5); + } + + return true; +} diff --git a/d2bs/kolbot/libs/scripts/CreepingFeature.js b/d2bs/kolbot/libs/scripts/CreepingFeature.js new file mode 100644 index 000000000..043824f62 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/CreepingFeature.js @@ -0,0 +1,23 @@ +/** +* @filename CreepingFeature.js +* @author theBGuy +* @desc kill Creeping Feature +* +*/ + +const CreepingFeature = new Runnable( + function CreepingFeature () { + Town.goToTown(2); + + Pather.journeyTo(sdk.areas.StonyTombLvl2); + Pather.moveToPresetMonster(sdk.areas.StonyTombLvl2, sdk.monsters.preset.CreepingFeature); + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.CreepingFeature)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.LutGholein, + bossid: getLocaleString(sdk.locale.monsters.CreepingFeature), + } +); diff --git a/d2bs/kolbot/libs/scripts/CrushTele.js b/d2bs/kolbot/libs/scripts/CrushTele.js new file mode 100644 index 000000000..eed5a7632 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/CrushTele.js @@ -0,0 +1,58 @@ +/** +* @filename CrushTele.js +* @author kolton +* @desc Auto tele for classic rush only. Hit the "-" numpad in strategic areas. +* +*/ + +const CrushTele = new Runnable( + function CrushTele () { + let go = false; + + addEventListener("keyup", + function (key) { + key === sdk.keys.NumpadDash && (go = true); + } + ); + + while (true) { + if (go) { + switch (me.area) { + case sdk.areas.CatacombsLvl2: + Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true); + break; + case sdk.areas.HallsoftheDeadLvl2: + Pather.moveToExit(sdk.areas.HallsoftheDeadLvl3, true); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest); + break; + case sdk.areas.FarOasis: + Pather.moveToExit([sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3], true); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest); + break; + case sdk.areas.LostCity: + Pather.moveToExit([ + sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2 + ], true); + break; + case sdk.areas.CanyonofMagic: + Pather.moveToExit(getRoom().correcttomb, true); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder); + break; + case sdk.areas.ArcaneSanctuary: + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal, 0, 0, false, true); + break; + case sdk.areas.DuranceofHateLvl2: + Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true); + break; + case sdk.areas.RiverofFlame: + Pather.moveToPreset(sdk.areas.ChaosSanctuary, sdk.unittype.Object, sdk.objects.DiabloStar); + break; + } + + go = false; + } + + delay(10); + } + } +); diff --git a/d2bs/kolbot/libs/scripts/DeveloperMode.js b/d2bs/kolbot/libs/scripts/DeveloperMode.js new file mode 100644 index 000000000..dbee98448 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/DeveloperMode.js @@ -0,0 +1,340 @@ +/** +* @filename Developermode.js +* @author theBGuy +* @desc developer mode made easy - run commands or scripts from chat commands. View packets. See unit info +* +*/ + +const DeveloperMode = new Runnable( + function DeveloperMode () { + const className = sdk.player.class.nameOf(me.classid); + + /** + * @param {string} str + * @param {boolean} [toConsole=false] + * @param {number | string} [color=0] + */ + const log = function (str = "", toConsole = false, color = 0) { + console.log("ÿc8Dev Modeÿc0: " + str); + me.overhead(str); + + if (toConsole && typeof color === "string") { + color = color.capitalize(true); + color = !!sdk.colors.D2Bot[color] ? sdk.colors.D2Bot[color] : 0; + } + toConsole && D2Bot.printToConsole("Dev Mode :: " + str, color); + }; + + let [done, action, command, userAddon, test] = [false, false, false, false, false]; + let [watchSent, watchRecv, blockSent, blockRecv] = [[], [], [], []]; + + /** + * @param {string} msg + * @returns {void} + */ + const runCommand = function (msg) { + if (msg.length <= 1) return; + + let cmd = msg.split(" ")[0].split(".")[1]; + let msgList = msg.split(" "); + + switch (cmd.toLowerCase()) { + case "me": + log("Character Level: " + me.charlvl + " | Area: " + me.area + " | x: " + me.x + ", y: " + me.y); + + break; + case "useraddon": + userAddon = !userAddon; + me.overhead("userAddon set to " + userAddon); + + break; + case "run": + if (msgList.length < 2) { + console.log("ÿc1Missing arguments"); + } else { + action = msgList[1]; + } + + break; + case "done": + done = true; + + break; + case "testing": + test = true; + + break; + case "command": + if (msgList.length < 2) { + console.log("ÿc1Missing arguments"); + } else { + command = msgList.splice(1).join(" "); + } + + break; + case "watch": + if (msgList.length < 3) { + console.log("ÿc1Missing arguments"); + break; + } + + switch (msgList[1].toLowerCase()) { + case "sent": + if (msgList[2] === "list") { + console.log("Watching sent packets : ÿc8" + watchSent.join(", ")); + break; + } + + watchSent.push(msgList[2]); + console.log("Added ÿc80x" + msgList[2] + "ÿc0 (sent) to watch list"); + break; + + case "recv": + if (msgList[2] === "list") { + console.log("Watching received packets : ÿc8" + watchRecv.join(", ")); + break; + } + + watchRecv.push(msgList[2]); + console.log("Added ÿc80x" + msgList[2] + "ÿc0 (recv) to watch list"); + break; + + default: + console.log("ÿc1Invalid argument : " + msgList[1]); + break; + } + + break; + + case "!watch": + if (msgList.length < 3) { + console.log("ÿc1Missing arguments"); + break; + } + + switch (msgList[1].toLowerCase()) { + case "sent": + if (watchSent.indexOf(msgList[2]) > -1) watchSent.splice(watchSent.indexOf(msgList[2]), 1); + console.log("Removed packet ÿc80x" + msgList[2] + "ÿc0 (sent) from watch list"); + break; + + case "recv": + if (watchRecv.indexOf(msgList[2]) > -1) watchRecv.splice(watchRecv.indexOf(msgList[2]), 1); + console.log("Removed packet ÿc80x" + msgList[2] + "ÿc0 (recv) from watch list"); + break; + + default: + console.log("ÿc1Invalid argument : " + msgList[1]); + break; + } + + break; + + case "block": + if (msgList.length < 3) { + console.log("ÿc1Missing arguments"); + break; + } + + switch (msgList[1].toLowerCase()) { + case "sent": + if (msgList[2] === "list") { + console.log("Blocking sent packets : ÿc8" + blockSent.join(", ")); + break; + } + + blockSent.push(msgList[2]); + console.log("Added ÿc80x" + msgList[2] + "ÿc0 (sent) to block list"); + break; + + case "recv": + if (msgList[2] === "list") { + console.log("Blocking received packets : ÿc8" + blockRecv.join(", ")); + break; + } + + blockRecv.push(msgList[2]); + console.log("Added ÿc80x" + msgList[2] + "ÿc0 (recv) to block list"); + break; + + default: + console.log("ÿc1Invalid argument : " + msgList[1]); + break; + } + + break; + + case "!block": + if (msgList.length < 3) { + console.log("ÿc1Missing arguments"); + break; + } + + switch (msgList[1].toLowerCase()) { + case "sent": + if (blockSent.indexOf(msgList[2]) > -1) blockSent.splice(blockSent.indexOf(msgList[2]), 1); + console.log("Removed packet ÿc80x" + msgList[2] + "ÿc0 (sent) from block list"); + break; + + case "recv": + if (blockRecv.indexOf(msgList[2]) > -1) blockRecv.splice(blockRecv.indexOf(msgList[2]), 1); + console.log("Removed packet ÿc80x" + msgList[2] + "ÿc0 (recv) from block list"); + break; + + default: + console.log("ÿc1Invalid argument : " + msgList[1]); + break; + } + + break; + } + }; + + // Received packet handler + const packetReceived = function (pBytes) { + let ID = pBytes[0].toString(16); + + // Block received packets from list + if (blockRecv.includes(ID)) return true; + + if (watchRecv.includes(ID)) { + let size = pBytes.length; + let array = [].slice.call(pBytes); + array.shift(); + console.log("ÿc2S ÿc8" + ID + "ÿc0 " + array.join(" ") + " ÿc5(" + size + " Bytes)"); + } + + return false; + }; + + // Sent packet handler + const packetSent = function (pBytes) { + let ID = pBytes[0].toString(16); + + // Block all commands or irc chat from being sent to server + if (ID === "15") { + if (pBytes[3] === 46) { + let str = ""; + + for (let b = 3; b < pBytes.length - 3; b++) { + str += String.fromCharCode(pBytes[b]); + } + + if (pBytes[3] === 46) { + runCommand(str); + return true; + } + } + } + + // Block sent packets from list + if (blockSent.includes(ID)) return true; + + if (watchSent.includes(ID)) { + let size = pBytes.length; + let array = [].slice.call(pBytes); + array.shift(); + console.log("ÿc2C ÿc8" + ID + "ÿc0 " + array.join(" ") + " ÿc5(" + size + " Bytes)"); + } + + return false; + }; + + /** + * @param {number} key + */ + const keyEvent = function (key) { + switch (key) { + case sdk.keys.Spacebar: + FileTools.copy("libs/config/" + className + ".js", "libs/config/" + className + "." + me.name + ".js"); + log("libs/config/" + className + "." + me.name + ".js has been created.", true); + log("Please configure your bot and reload or restart", true); + + break; + } + }; + + const copiedConfig = copyObj(Config); + const UnitInfo = new (require("../modules/UnitInfo")); + + try { + console.log("starting developermode"); + me.overhead("Started developer mode"); + addEventListener("gamepacketsent", packetSent); + addEventListener("gamepacket", packetReceived); + + if (!FileTools.exists("libs/config/" + className + "." + me.name + ".js")) { + console.log("ÿc4UserAddonÿc0: Press HOME and then press SPACE if you want to create character config."); + addEventListener("keyup", keyEvent); + } + Config.Silence = false; + + while (!done) { + if (action) { + try { + let script = Loader.fileList.find(function (el) { + return String.isEqual(el, action); + }); + + if (!script) { + throw new Error("Failed to find script: " + action); + } + includeIfNotIncluded("scripts/" + script + ".js"); + + UnitInfo.check(); + + if (isIncluded("scripts/" + script + ".js")) { + try { + Loader.runScript(script); + } catch (e) { + console.error(e); + } + } else { + console.warn("Failed to include: " + script); + } + } catch (e) { + console.error(e); + } + + me.overhead("Done with action"); + action = false; + } + + if (command) { + UnitInfo.check(); + + try { + eval(command); + } catch (e) { + console.error(e); + } + + me.overhead("Done with action"); + command = false; + } + + if (userAddon) { + UnitInfo.createInfo(Game.getSelectedUnit()); + } + + if (test) { + me.overhead("done"); + test = false; + } + + delay(100); + } + } finally { + removeEventListener("keyup", keyEvent); + removeEventListener("gamepacketsent", packetSent); + removeEventListener("gamepacket", packetReceived); + Config = copiedConfig; + UnitInfo.remove(); + } + + return true; + }, + { + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Diablo.js b/d2bs/kolbot/libs/scripts/Diablo.js new file mode 100644 index 000000000..2f9ad97a8 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Diablo.js @@ -0,0 +1,97 @@ +/** +* @filename Diablo.js +* @author kolton, theBGuy +* @desc clear Chaos Sanctuary and kill Diablo +* @configurable +* run only Vizier - intended for classic sorc, only kills Vizier +* clear safe spot around seals for leechers - used in conjuction with SealLeecher +* run Fast Diablo - focuses only on popping the seals quickly +* +*/ + +const Diablo = new Runnable( + function Diablo () { + include("core/Common/Diablo.js"); + Pather._teleport = Pather.teleport; + Common.Diablo.clearRadius = Config.Diablo.ClearRadius; + + // START + if (!me.inArea(sdk.areas.RiverofFlame)) { + !!Config.RandomPrecast + ? Precast.doRandomPrecast(true, sdk.areas.RiverofFlame) + : Pather.useWaypoint(sdk.areas.RiverofFlame) && Precast.doPrecast(true); + !me.inArea(sdk.areas.RiverofFlame) && Pather.useWaypoint(sdk.areas.RiverofFlame); + } + + if (!Pather.moveToExit(sdk.areas.ChaosSanctuary, true) && !Pather.moveTo(7790, 5544)) { + throw new Error("Failed to move to Chaos Sanctuary"); + } + + Common.Diablo.initLayout(); + + if (Config.Diablo.JustViz) { + Common.Diablo.vizLayout === 1 ? Pather.moveTo(7708, 5269) : Pather.moveTo(7647, 5267); + Config.PublicMode && Pather.makePortal(); + Common.Diablo.vizierSeal(true); + + return true; + } + + try { + if (Config.Diablo.Entrance && !Config.Diablo.Fast) { + Attack.clear(30, 0, false, Common.Diablo.sort); + Pather.moveTo(7790, 5544); + + if (Config.PublicMode && Pather.makePortal()) { + say(Config.Diablo.EntranceTP); + if (Config.Diablo.WalkClear) { + Pather.teleport = false; + } + } + + Pather.moveTo(7790, 5544); + Precast.doPrecast(true); + Attack.clear(30, 0, false, Common.Diablo.sort); + Common.Diablo.followPath(Common.Diablo.entranceToStar); + } else { + Pather.moveTo(7774, 5305); + Attack.clear(15, 0, false, Common.Diablo.sort); + } + + Pather.moveTo(7791, 5293); + + if (Config.PublicMode && Pather.makePortal()) { + say(Config.Diablo.StarTP); + Pather.teleport = !Config.Diablo.WalkClear && Pather._teleport; + } + + Attack.clear(30, 0, false, Common.Diablo.sort); + + try { + Common.Diablo.runSeals(Config.Diablo.SealOrder); + // maybe instead of throwing error if we fail to open seal, add it to an array to re-check before diabloPrep then if that fails throw and error + Config.PublicMode && say(Config.Diablo.DiabloMsg); + console.log("Attempting to find Diablo"); + Common.Diablo.diabloPrep(); + } catch (error) { + console.warn("Diablo wasn't found. Checking seals."); + Common.Diablo.runSeals(Config.Diablo.SealOrder, true, true); + Common.Diablo.diabloPrep(); + } + + Attack.kill(sdk.monsters.Diablo); + Pickit.pickItems(); + Config.Diablo.SealLeader && say("done"); + } finally { + if (Pather.teleport !== Pather._teleport) { + Pather.teleport = Pather._teleport; + } + } + + return true; + }, + { + startArea: sdk.areas.RiverofFlame, + bossid: sdk.monsters.Diablo, + } +); diff --git a/d2bs/kolbot/libs/scripts/DiabloHelper.js b/d2bs/kolbot/libs/scripts/DiabloHelper.js new file mode 100644 index 000000000..f42c09981 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/DiabloHelper.js @@ -0,0 +1,183 @@ +/** +* @filename DiabloHelper.js +* @author kolton, theBGuy +* @desc help leading player in clearing Chaos Sanctuary and killing Diablo +* +*/ + +const DiabloHelper = new Runnable( + function DiabloHelper () { + include("core/Common/Diablo.js"); + this.Leader = Config.Leader; + Common.Diablo.waitForGlow = true; + Common.Diablo.clearRadius = Config.DiabloHelper.ClearRadius; + const Worker = require("../modules/Worker"); + + try { + addEventListener("gamepacket", Common.Diablo.diabloLightsEvent); + + if (Config.DiabloHelper.SkipIfBaal) { + let leadTick = getTickCount(); + + Worker.runInBackground.leaderTracker = function () { + if (Common.Diablo.done) return false; + // check every 3 seconds + if (getTickCount() - leadTick < 3000) return true; + leadTick = getTickCount(); + + // check again in another 3 seconds if game wasn't ready + if (!me.gameReady) return true; + if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); + let party = getParty(); + + if (party) { + do { + // Player is in Throne of Destruction or Worldstone Chamber + if ([sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(party.area)) { + if (Loader.scriptName() === "DiabloHelper") { + throw new Error("Party leader is running baal"); + } else { + // kill process + return false; + } + } + } while (party.getNext()); + } + + return true; + }; + } + + Config.DiabloHelper.SafePrecast && Precast.needOutOfTownCast() + ? Precast.doRandomPrecast(true, sdk.areas.PandemoniumFortress) + : Precast.doPrecast(true); + + if (Config.DiabloHelper.SkipTP) { + !me.inArea(sdk.areas.RiverofFlame) && Pather.useWaypoint(sdk.areas.RiverofFlame); + + if (!Pather.moveTo(7790, 5544)) throw new Error("Failed to move to Chaos Sanctuary"); + !Config.DiabloHelper.Entrance && Pather.moveTo(7774, 5305); + + if (!Misc.poll(() => { + let party = getParty(); + + if (party) { + do { + if ((!DiabloHelper.Leader || party.name === DiabloHelper.Leader) + && party.area === sdk.areas.ChaosSanctuary) { + return true; + } + } while (party.getNext()); + } + + Attack.clear(30, 0, false, Common.Diablo.sort); + + return false; + }, Time.minutes(Config.DiabloHelper.Wait), 1000)) { + throw new Error("Player wait timed out (" + (Config.Leader ? "Leader not" : "No players") + " found in Chaos)"); + } + } else { + Town.goToTown(4); + Town.move("portalspot"); + if (!DiabloHelper.Leader) { + DiabloHelper.Leader = Misc.autoLeaderDetect({ + destination: sdk.areas.ChaosSanctuary, + quitIf: (area) => [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(area), + timeout: Time.minutes(2) + }); + } + + if (!Misc.poll(() => { + if (Pather.getPortal(sdk.areas.ChaosSanctuary, Config.Leader || null) + && Pather.usePortal(sdk.areas.ChaosSanctuary, Config.Leader || null)) { + return true; + } + + return false; + }, Time.minutes(Config.DiabloHelper.Wait), 1000)) { + throw new Error("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); + } + } + + Common.Diablo.initLayout(); + + let diaTick = getTickCount(); + + Worker.runInBackground.diaSpawned = function () { + if (Common.Diablo.done) return false; + // check every 1/4 second + if (getTickCount() - diaTick < 250) return true; + diaTick = getTickCount(); + + if (Common.Diablo.diabloSpawned) throw new Error("Diablo spawned"); + + return true; + }; + + try { + if (Config.DiabloHelper.Entrance && Common.Diablo.starCoords.distance > Common.Diablo.entranceCoords.distance) { + Attack.clear(35, 0, false, Common.Diablo.sort); + Common.Diablo.followPath(Common.Diablo.entranceToStar); + } else { + Pather.moveTo(7774, 5305); + Attack.clear(35, 0, false, Common.Diablo.sort); + } + + Pather.moveTo(7774, 5305); + Attack.clear(35, 0, false, Common.Diablo.sort); + Common.Diablo.runSeals(Config.DiabloHelper.SealOrder, Config.DiabloHelper.OpenSeals); + Common.Diablo.moveToStar(); + Misc.poll(() => { + if (Common.Diablo.diabloSpawned) return true; + if (Game.getMonster(sdk.monsters.Diablo)) return true; + if ([ + sdk.areas.WorldstoneLvl3, + sdk.areas.ThroneofDestruction, + sdk.areas.WorldstoneChamber + ].includes(Misc.getPlayerArea(DiabloHelper.Leader))) { + throw new Error("END"); + } + return false; + }, Time.minutes(2), 500); + } catch (e) { + let eMsg = e.message ? e.message : e; + console.log(eMsg); + + if (eMsg === "END") { + return true; + } + } + + try { + !Common.Diablo.diabloSpawned && (Common.Diablo.diaWaitTime += Time.minutes(1)); + console.log("Attempting to find Diablo"); + Common.Diablo.diabloPrep(); + } catch (error) { + console.log("Diablo wasn't found"); + if (Config.DiabloHelper.RecheckSeals) { + try { + console.log("Rechecking seals"); + Common.Diablo.runSeals(Config.DiabloHelper.SealOrder, Config.DiabloHelper.OpenSeals); + Misc.poll(() => Common.Diablo.diabloSpawned, Time.minutes(2), 500); + Common.Diablo.diabloPrep(); + } catch (e2) { + // + } + } + } + + Attack.kill(sdk.monsters.Diablo); + Pickit.pickItems(); + } catch (e) { + console.error(e); + } finally { + Common.Diablo.done = true; + removeEventListener("gamepacket", Common.Diablo.diabloLightsEvent); + } + + return true; + }, + { + startArea: sdk.areas.PandemoniumFortress + } +); diff --git a/d2bs/kolbot/libs/scripts/Duriel.js b/d2bs/kolbot/libs/scripts/Duriel.js new file mode 100644 index 000000000..c8690e4cc --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Duriel.js @@ -0,0 +1,85 @@ +/** +* @filename Duriel.js +* @author kolton, theBGuy +* @desc kill Duriel +* +*/ + +const Duriel = new Runnable( + function Duriel () { + const killDuriel = function () { + let target = Misc.poll(function () { + return Game.getMonster(sdk.monsters.Duriel); + }, 1000, 200); + if (!target) throw new Error("Duriel not found."); + + if (Config.MFLeader && Pather.makePortal()) { + say("kill " + sdk.monsters.Duriel); + } + + for (let i = 0; i < 300 && target.attackable; i += 1) { + ClassAttack.doAttack(target); + target.distance <= 10 && Pather.moveTo(22638, me.y < target.y ? 15722 : 15693); + } + + return target.dead; + }; + + if (!me.inArea(sdk.areas.CanyonofMagic)) { + Town.doChores(); + Pather.useWaypoint(sdk.areas.CanyonofMagic); + } + + Precast.doPrecast(true); + + if (!Pather.moveToExit(getRoom().correcttomb, true)) { + throw new Error("Failed to move to Tal Rasha's Tomb"); + } + /** @type {ObjectUnit} */ + let lairEntrance = null; + if (!Pather.moveToPresetObject(me.area, sdk.quest.chest.HoradricStaffHolder, + { offX: -11, offY: 3, callback: function () { + lairEntrance = Game.getObject(sdk.objects.PortaltoDurielsLair); + return lairEntrance && lairEntrance.distance < 20; + } })) { + throw new Error("Failed to move to Orifice"); + } + + // me.hardcore && !me.sorceress && Attack.clear(5); + if (lairEntrance && Skill.useTK(lairEntrance)) { + if (lairEntrance.distance > 20) { + Attack.getIntoPosition(lairEntrance, 20, sdk.collision.LineOfSight); + } + Misc.poll(function () { + Packet.telekinesis(lairEntrance) && delay(100); + return me.inArea(sdk.areas.DurielsLair); + }, 1000, 200); + } + + let [type, id, target] = [ + sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair + ]; + + if (!me.inArea(sdk.areas.DurielsLair) + && !Pather.useUnit(type, id, target)) { + Attack.clear(10); + Pather.useUnit(type, id, target); + } + + if (!me.inArea(sdk.areas.DurielsLair)) { + throw new Error("Failed to move to Duriel"); + } + + me.sorceress && me.classic + ? killDuriel() + : Attack.kill(sdk.monsters.Duriel); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.CanyonofMagic, + bossid: sdk.monsters.Duriel, + preAction: null, + } +); diff --git a/d2bs/kolbot/libs/scripts/Eldritch.js b/d2bs/kolbot/libs/scripts/Eldritch.js new file mode 100644 index 000000000..25451b411 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Eldritch.js @@ -0,0 +1,55 @@ +/** +* @filename Eldritch.js +* @author kolton, theBGuy +* @desc kill Eldritch the Rectifier, optionally kill Shenk the Overseer, Dac Farren and open chest +* +*/ + +const Eldritch = new Runnable( + function Eldritch () { + Pather.useWaypoint(sdk.areas.FrigidHighlands); + Precast.doPrecast(true); + let { x, y } = me; + Pather.moveTo(3745, 5084); + Attack.kill(getLocaleString(sdk.locale.monsters.EldritchtheRectifier)); + + try { + // FrigidHighlands returns invalid size with getBaseStat('leveldefs', 111, ['SizeX', 'SizeX(N)', 'SizeX(H)'][me.diff]); + // Could this be causing crashes here? + if (Config.Eldritch.OpenChest + && Pather.moveNearPreset(sdk.areas.FrigidHighlands, sdk.unittype.Object, sdk.objects.LargeSparklyChest, 10)) { + Misc.openChest(sdk.objects.FrigidHighlandsChest) && Pickit.pickItems(); + // check distance from current location to shenk and if far tp to town and use wp instead + if ([x, y].distance > 120 || !Pather.canTeleport()) { + Town.goToTown() && Pather.useWaypoint(sdk.areas.FrigidHighlands); + } + } + } catch (e) { + console.warn("(Eldritch) :: Failed to open chest. " + e); + } + + try { + + if (Config.Eldritch.KillShenk + && !Attack.haveKilled(getLocaleString(sdk.locale.monsters.ShenktheOverseer)) + && Pather.moveToExit(sdk.areas.BloodyFoothills, false) + && Pather.moveTo(3876, 5130)) { + Attack.kill(getLocaleString(sdk.locale.monsters.ShenktheOverseer)); + } + } catch (e) { + console.warn("(Eldritch) :: Failed to Kill Shenk. " + e); + } + + if (Config.Eldritch.KillDacFarren + && !Attack.haveKilled(getLocaleString(sdk.locale.monsters.DacFarren)) + && Pather.moveNearPreset(sdk.areas.BloodyFoothills, sdk.unittype.Monster, sdk.monsters.preset.DacFarren, 10) + && Pather.moveTo(4478, 5108)) { + Attack.kill(getLocaleString(sdk.locale.monsters.DacFarren)); + } + + return true; + }, + { + startArea: sdk.areas.FrigidHighlands + } +); diff --git a/d2bs/kolbot/libs/scripts/Endugu.js b/d2bs/kolbot/libs/scripts/Endugu.js new file mode 100644 index 000000000..e80d2ebfb --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Endugu.js @@ -0,0 +1,32 @@ +/** +* @filename Endugu.js +* @author kolton, theBGuy +* @desc kill Witch Doctor Endugu +* +*/ + +const Endugu = new Runnable( + function Endugu () { + Pather.useWaypoint(sdk.areas.FlayerJungle); + Precast.doPrecast(true); + + const exits = [ + sdk.areas.FlayerDungeonLvl1, + sdk.areas.FlayerDungeonLvl2, + sdk.areas.FlayerDungeonLvl3 + ]; + + if (!Pather.moveToExit(exits, true) + || !Pather.moveToPresetObject(me.area, sdk.quest.chest.KhalimsBrainChest)) { + throw new Error("Failed to move to Endugu"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.WitchDoctorEndugu)); + + return true; + }, + { + startArea: sdk.areas.FlayerJungle, + bossid: getLocaleString(sdk.locale.monsters.WitchDoctorEndugu), + } +); diff --git a/d2bs/kolbot/libs/scripts/Eyeback.js b/d2bs/kolbot/libs/scripts/Eyeback.js new file mode 100644 index 000000000..0c0247daf --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Eyeback.js @@ -0,0 +1,25 @@ +/** +* @filename Eyeback.js +* @author kolton, theBGuy +* @desc kill Eyeback the Unleashed +* +*/ + +const Eyeback = new Runnable( + function Eyeback () { + Pather.useWaypoint(sdk.areas.ArreatPlateau); + Precast.doPrecast(true); + + if (!Pather.moveToPresetMonster(sdk.areas.FrigidHighlands, sdk.monsters.preset.EyebacktheUnleashed)) { + throw new Error("Failed to move to Eyeback the Unleashed"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.EyebacktheUnleashed)); + + return true; + }, + { + startArea: sdk.areas.ArreatPlateau, + bossid: getLocaleString(sdk.locale.monsters.EyebacktheUnleashed), + } +); diff --git a/d2bs/kolbot/libs/scripts/Fangskin.js b/d2bs/kolbot/libs/scripts/Fangskin.js new file mode 100644 index 000000000..15ed5f984 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Fangskin.js @@ -0,0 +1,31 @@ +/** +* @filename Fangskin.js +* @author theBGuy +* @desc kill Fangskin +* +*/ + +const Fangskin = new Runnable( + function Fangskin () { + Pather.useWaypoint(sdk.areas.LostCity); + Precast.doPrecast(true); + + if (!Pather.moveToExit([ + sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2 + ], true)) { + throw new Error("Failed to move to Fangskin"); + } + + // casters can kill fangskin from the altar spot for better safety + Pather.canTeleport() && Skill.getRange(Config.AttackSkill[1] > 10) && Pather.moveTo(15044, 14045); + + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Fangskin)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.LostCity, + bossid: getLocaleString(sdk.locale.monsters.Fangskin), + } +); diff --git a/d2bs/kolbot/libs/scripts/Follower.js b/d2bs/kolbot/libs/scripts/Follower.js new file mode 100644 index 000000000..d979103a7 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Follower.js @@ -0,0 +1,1031 @@ +/** +* @filename Follower.js +* @author kolton, theBGuy +* @desc Controllable bot to follow around leader like an additonal merc +* @Commands +* @Main +* 1 - take leader's tp from town / move to leader's town +* 2 - take leader's tp to town +* 3 - town manager +* c - get corpse +* p - pick items +* r - revive +* s - toggle stop +* pre - precast +* - tell specific character to perform action +* @Attack +* a - attack toggle for all +* aon - attack on for all +* aoff - attack off for all +* - tell specific character to perform action +* @Teleport *** characters without teleport skill will ignore tele command *** +* tele - toggle teleport for all +* tele on - teleport on for all +* tele off - teleport off for all +* - tell specific character to perform action +* @Skills *** refer to skills.txt or modules/sdk.js *** +* skill - change skill character(s) +* ~~~~~~~~~~~~~~~~~~~ Examples ~~~~~~~~~~~~~~~~~~~~~~ +* | NOTE: *** any part of class name will do *** | +* | "sorc skill 36", "zon skill 0", "din skill 106" | +* | "all skill 0", "myhdin skill 112" | +* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* Auras: *** refer to skills.txt or modules/sdk.js *** +* all aura - change aura for all paladins +* aura - change aura for +* @Town +* a2-5 - move to appropriate act (after quest) !NOTE: Disable 'no sound' or game will crash! +* talk - talk to a npc in town +* chug - buy and drink special potions potions from Akara +* ~~~~~~~~~~~~~~~~~~~ Examples ~~~~~~~~~~~~~~~~~~~~~~ +* | NOTE: *** type can be: a, antidote, t, thawing, s, stamina *** | +* | "chug a 20" will buy and drink 20 antidote potions | +* | "chug t" will buy and drink 10 thawing potions | +* | "slowpoke chug s 5" slowpoke to buy and drink 5 stamina potions| +* |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| +* - tell specific character to perform action +* @Misc +* tp - make a TP. Needs a TP tome if not using custom libs. +* quiet - stop announcing in chat +* cow - enter red cow portal +* wp - all players activate a nearby wp +* bo - barbarian precast +* move - move in a random direction (use if you're stuck by followers) +* reload - reload script. Use only in case of emergency, or after editing character config. +* taxi - travels to area and makes portal. +* taxi - travels to area and makes portal. See taxiMap below or use "taxi list" for list of areas. +* taxi list - list of areas for taxi command +* quit - exit game +* - tell specific character to perform action +* @todo +* run - run a script +* run - run a script on +* skills - list current attack skills +* skills - list current attack skills for +* +*/ + +const Follower = new Runnable( + function Follower () { + const QuestData = require("../core/GameData/QuestData"); + /** @type {Map} */ + const areas = new Map(); + /** @type {Set} */ + const _players = new Set(); + /** @type {Array} */ + const actions = []; + /** @type {Set} */ + const commanders = new Set(); + Config.Leader && commanders.add(Config.Leader); + let [allowSay, attack, openContainers, stop] = [true, true, true, false]; + + /** @type {Map _actions.set(key, () => { + Pather.teleport = !Pather.teleport; + announce("Teleport " + (Pather.teleport ? "on" : "off")); + })); + ["tele off", (me.name + " tele off")] + .forEach(key => _actions.set(key, () => { + Pather.teleport = false; + announce("Teleport off."); + })); + ["tele on", (me.name + " tele on")] + .forEach(key => _actions.set(key, () => { + Pather.teleport = true; + announce("Teleport on."); + })); + ["a", (me.name + " a")] + .forEach(key => _actions.set(key, () => { + attack = !attack; + announce("Attack " + (attack ? "on" : "off")); + })); + ["aon", (me.name + " aon")] + .forEach(key => _actions.set(key, () => { + attack = true; + announce("Attack on."); + })); + ["aoff", (me.name + " aoff")] + .forEach(key => _actions.set(key, () => { + attack = false; + announce("Attack off."); + })); + ["s", (me.name + " s")] + .forEach(key => _actions.set(key, () => { + stop = !stop; + announce((stop ? "Stopping." : "Resuming.")); + })); + ["quiet", (me.name + " quiet")] + .forEach(key => _actions.set(key, () => { + allowSay = !allowSay; + console.log("Allow say: " + allowSay); + })); + + return _actions; + })(); + + /** @type {Map _actions.set(key, () => { + Packet.flash(me.gid, me.getPingDelay()); + })); + ["quit", (me.name + " quit")] + .forEach(key => _actions.set(key, () => { + quit(); + })); + ["r", (me.name + " r")] + .forEach(key => _actions.set(key, () => { + revive(); + })); + ["move", (me.name + " move")] + .forEach(key => _actions.set(key, () => { + let coord = CollMap.getRandCoordinate(me.x, -5, 5, me.y, -5, 5); + Pather.moveTo(coord.x, coord.y); + })); + ["pre", (me.name + " pre")] + .forEach(key => _actions.set(key, () => { + Precast.doPrecast(true); + })); + ["bo", (me.name + " bo")] + .forEach(key => _actions.set(key, () => { + !me.inTown && Precast.doPrecast(true); + })); + ["h", (me.name + " h")] + .forEach(key => _actions.set(key, () => { + !me.inTown && Skill.cast(sdk.skills.Howl); + })); + + return _actions; + })(); + + /** @type {Map _actions.set(key, () => { + if (me.inArea(sdk.areas.MooMooFarm)) return; + Town.goToTown(1); + Town.move("portalspot"); + if (!Pather.usePortal(sdk.areas.MooMooFarm)) { + announce("Failed to enter red cow portal."); + } + })); + ["wp", (me.name + " wp")] + .forEach(key => _actions.set(key, () => { + if (me.inTown) return; + if (me.haveWaypoint(me.area)) return; + if (!Pather.wpAreas.includes(me.area)) return; + if (Pather.getWP(me.area)) { + announce("Got Wp in " + getAreaName(me.area)); + } + })); + ["c", (me.name + " c")] + .forEach(key => _actions.set(key, () => { + !me.inTown && Town.getCorpse(); + })); + ["p", (me.name + " p")] + .forEach(key => _actions.set(key, () => { + announce("!Picking items."); + Pickit.pickItems(); + openContainers && Misc.openChests(20); + announce("!Done picking."); + })); + ["1", (me.name + " 1")] + .forEach(key => _actions.set(key, () => { + if (me.inTown && Leader.partyUnit.inTown && Misc.getPlayerAct(Config.Leader) !== me.act) { + announce("Going to leader's town."); + Town.goToTown(Misc.getPlayerAct(Config.Leader)); + Town.move("portalspot"); + } else if (me.inTown) { + announce("Going outside."); + Town.goToTown(Misc.getPlayerAct(Config.Leader)); + Town.move("portalspot"); + + if (!Pather.usePortal(null, Leader.partyUnit.name)) { + if (!checkExit(Leader.partyUnit, Leader.partyUnit.area)) { + return; + } + } + + let _timeout = getTickCount() + Time.minutes(2); + while (!Misc.getPlayerUnit(Config.Leader) && !me.dead) { + if (getTickCount() > _timeout) { + announce("Leader not found."); + Town.goToTown(); + break; + } + Attack.clear(10); + delay(200); + } + } + })); + ["2", (me.name + " 2")] + .forEach(key => _actions.set(key, () => { + if (!me.inTown) { + delay(150); + announce("Going to town."); + getUnits(sdk.unittype.Object) + .filter(unit => unit.classid === sdk.objects.BluePortal + && unit.area === me.area && [Leader.partyUnit.name, me.name].includes(unit.getParent())) + .sort((a, b) => a.distance - b.distance) + .some(portal => Pather.usePortal(null, null, portal)); + // Pather.usePortal(null, Leader.unit.name) || Pather.usePortal(sdk.areas.townOf(me.area)); + } + })); + ["3", (me.name + " 3")] + .forEach(key => _actions.set(key, () => { + if (!me.inTown) return; + announce("Running town chores"); + Town.doChores(); + Town.move("portalspot"); + announce("Ready"); + })); + ["a2", "a3", "a4", "a5"] + .forEach((key, index) => { + _actions.set(key, () => { changeAct(index + 2); }); + _actions.set(me.name + " " + key, () => { changeAct(index + 2); }); + }); + _actions.set(me.name + " tp", () => { + if (!Pather.makePortal()) { + announce("No TP scrolls or tomes."); + } + }); + + return _actions; + })(); + + /** @type {Map { + Pather.journeyTo(sdk.areas.DenofEvil); + } + ], + [ + "bloodraven", () => { + Pather.journeyTo(sdk.areas.BurialGrounds); + Pather.moveNearPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.BloodRaven, 15); + } + ], + [ + "tree", () => { + Pather.journeyTo(sdk.areas.DarkWood); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5); + } + ], + [ + "trist", () => { + Pather.journeyTo(sdk.areas.Tristram); + } + ], + [ + "pit", () => { + Pather.journeyTo(sdk.areas.PitLvl1); + } + ], + [ + "countess", () => { + Pather.journeyTo(sdk.areas.TowerCellarLvl5); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.SuperChest); + } + ], + [ + "andy", () => { + Pather.journeyTo(sdk.areas.CatacombsLvl4); + } + ], + [ + "rad", () => { + Pather.journeyTo(sdk.areas.A2SewersLvl3); + Pather.moveNearPreset(me.area, sdk.unittype.Object, sdk.objects.HoradricScrollChest, 5); + } + ], + [ + "cube", () => { + Pather.journeyTo(sdk.areas.HallsoftheDeadLvl3); + Pather.moveNearPreset(me.area, sdk.unittype.Object, sdk.objects.HoradricCubeChest, 15); + } + ], + [ + "amulet", () => { + Pather.journeyTo(sdk.areas.ClawViperTempleLvl2); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ViperAmuletChest); + } + ], + [ + "staff", () => { + Pather.journeyTo(sdk.areas.HallsoftheDeadLvl3); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest); + } + ], + [ + "summoner", () => { + Pather.journeyTo(sdk.areas.ArcaneSanctuary); + Pather.moveNearPreset(me.area, sdk.unittype.Object, sdk.objects.Journal, 15); + } + ], + [ + "staff-altar", () => { + Pather.journeyTo(sdk.areas.CanyonofMagic); + Pather.moveToExit(getRoom().correcttomb, true); + Pather.moveNearPreset(me.area, sdk.unittype.Object, sdk.objects.HoradricStaffHolder, 5); + } + ], + [ + "duriel", () => { + Pather.journeyTo(sdk.areas.DurielsLair); + } + ], + [ + "eye", () => { + Pather.journeyTo(sdk.areas.SpiderCavern); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsEyeChest); + } + ], + [ + "brain", () => { + Pather.journeyTo(sdk.areas.FlayerDungeonLvl3); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsBrainChest); + } + ], + [ + "heart", () => { + Pather.journeyTo(sdk.areas.A3SewersLvl2); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsHeartChest); + } + ], + [ + "council", () => { + Pather.journeyTo(sdk.areas.Travincal); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.CompellingOrb); + } + ], + [ + "meph", () => { + Pather.journeyTo(sdk.areas.DuranceofHateLvl3); + Pather.moveTo(17590, 8068); + } + ], + [ + "izual", () => { + Pather.journeyTo(sdk.areas.PlainsofDespair); + Pather.moveNearPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Izual, 20); + } + ], + [ + "forge", () => { + Pather.journeyTo(sdk.areas.RiverofFlame); + Pather.moveNearPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge, 5); + } + ], + [ + "chaos", () => { + Pather.journeyTo(sdk.areas.ChaosSanctuary); + } + ], + [ + "viz", () => { + Pather.journeyTo(sdk.areas.ChaosSanctuary); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.DiabloSealVizier); + } + ], + [ + "seis", () => { + Pather.journeyTo(sdk.areas.ChaosSanctuary); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.DiabloSealSeis); + } + ], + [ + "infector", () => { + Pather.journeyTo(sdk.areas.ChaosSanctuary); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.DiabloSealInfector); + } + ], + [ + "anya", () => { + Pather.journeyTo(sdk.areas.FrozenRiver); + Pather.moveNearPreset(me.area, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform, 5); + } + ], + [ + "ancients", () => { + Pather.journeyTo(sdk.areas.ArreatSummit); + } + ], + [ + "nith", () => { + Pather.journeyTo(sdk.areas.HallsofVaught); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.NihlathaksPlatform); + } + ], + [ + "throne", () => { + Pather.journeyTo(sdk.areas.ThroneofDestruction); + } + ], + ]); + + /** + * @todo allow user to use skill name and try to match it to skill id + */ + const _skillsMap = (function () { + const _skills = new Map(); + + for (let value of Object.values(sdk.skills)) { + if (typeof value === "number") { + _skills.set(getSkillById(value), value); + } + } + + return _skills; + })(); + + const announce = function (msg = "") { + if (!allowSay) return; + say(msg); + }; + + const revive = function () { + while (!me.inTown) { + me.revive(); + delay(1000); + } + + Town.move("portalspot"); + announce("I'm alive!"); + }; + + const playerInGame = function (name = "") { + if (!name) return false; + if (_players.has(name.toLowerCase())) return true; + let player = getParty(); + + if (player) { + do { + _players.add(player.name.toLowerCase()); + } while (player.getNext()); + } + return _players.has(name.toLowerCase()); + }; + + /** + * Change areas to where leader is + * @param {Party} unit + * @param {number} area + * @returns {boolean} + */ + const checkExit = function (unit, area) { + if (unit.inTown && me.inTown) return false; + + /** @type {Exit[]} */ + let exits = []; + if (areas.has(me.area)) { + exits = areas.get(me.area).exits; + } else { + let _area = getArea(me.area); + if (_area) { + exits = _area.exits; + areas.set(me.area, _area); + } + } + + for (let exit of exits) { + if (exit.target === area) { + announce("Taking exit to " + getAreaName(exit.target)); + return Pather.moveToExit(area, true); + } + } + + if (unit.inTown) { + let wp = Game.getObject("waypoint"); + + if (wp && wp.distance < 30) { + announce("Taking waypoint to " + getAreaName(area)); + return Pather.useWaypoint(area, true); + } + } + + let target = Game.getObject("portal"); + + if (target) { + do { + if (target.objtype === area) { + announce("Taking portal to " + getAreaName(area)); + return Pather.usePortal(null, null, target); + } + } while (target.getNext()); + } + + // Arcane<->Cellar portal + if ((me.inArea(sdk.areas.ArcaneSanctuary) && area === sdk.areas.PalaceCellarLvl3) + || (me.inArea(sdk.areas.PalaceCellarLvl3) && area === sdk.areas.ArcaneSanctuary)) { + announce("Special transit to " + getAreaName(area)); + return Pather.usePortal(null); + } + + // Tal-Rasha's tomb->Duriel's lair + if (me.area >= sdk.areas.TalRashasTomb1 + && me.area <= sdk.areas.TalRashasTomb7 + && area === sdk.areas.DurielsLair) { + announce("Special transit to " + getAreaName(area)); + return Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, area); + } + + // durance 3 -> pandemonium fortress + if (me.inArea(sdk.areas.DuranceofHateLvl3) && area === sdk.areas.PandemoniumFortress) { + announce("Special transit to " + getAreaName(area)); + return Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct4, sdk.areas.PandemoniumFortress); + } + + // Throne->Chamber + if (me.inArea(sdk.areas.ThroneofDestruction) && area === sdk.areas.WorldstoneChamber) { + let wsp = Game.getObject(sdk.objects.WorldstonePortal); + + if (wsp) { + announce("Special transit to " + getAreaName(area)); + return Pather.usePortal(null, null, wsp); + } + } + + return false; + }; + + /** + * Talk to a NPC + * @param {string} name + * @returns {boolean} + */ + const talk = function (name) { + try { + if (!me.inTown) throw new Error("I'm not in town!"); + if (typeof name !== "string") throw new Error("No NPC name given."); + Town.npcInteract(name); + + return true; + } catch (e) { + console.error(e); + announce( + (typeof e === "object" && e.message + ? e.message + : typeof e === "string" + ? e + : "Failed to talk to " + name) + ); + + return false; + } finally { + Town.move("portalspot"); + } + }; + + /** + * Change act after completing last act quest + * @param {number} act + * @returns {boolean} + */ + const changeAct = function (act) { + let preArea = me.area; + + if (me.area >= sdk.areas.townOfAct(act)) { + announce("My current act is higher than " + act); + return false; + } + + const npcTravel = new Map([ + [1, ["Warriv", sdk.areas.RogueEncampment]], + [2, [(me.act === 1 ? "Warriv" : "Meshif"), sdk.areas.LutGholein]], + [3, ["Meshif", sdk.areas.KurastDocktown]], + [4, ["", sdk.areas.PandemoniumFortress]], + [5, ["Tyrael", sdk.areas.Harrogath]], + ]); + + const preCheck = new Map([ + [ + 2, + () => QuestData.get(sdk.quest.id.SistersToTheSlaughter).complete(true) + ], + [ + 3, + () => { + if (QuestData.get(sdk.quest.id.TheSevenTombs).complete()) return true; + if (!QuestData.get(sdk.quest.id.TheSevenTombs).checkState(4/*talked to jerhyn*/)) { + Town.npcInteract("Jerhyn"); + if (me.getTpTool()) { + Pather.moveToExit(sdk.areas.HaremLvl1, true); + Pather.usePortal(null) || Pather.makePortal(true); + } + } + return QuestData.get(sdk.quest.id.TheSevenTombs).checkState(4/*talked to jerhyn*/); + } + ], + [ + 4, + () => { + if (me.inTown) { + if (!QuestData.get(sdk.quest.id.TheBlackenedTemple).complete()) { + Town.npcInteract("Cain"); + } + Town.move("portalspot"); + Pather.usePortal(sdk.areas.DuranceofHateLvl3, null); + } + return me.inArea(sdk.areas.DuranceofHateLvl3); + } + ], + [ + 5, + () => { + if (!QuestData.get(sdk.quest.id.TerrorsEnd).checkState(9/*talked to tyrael*/)) { + Town.npcInteract("Tyrael"); + } + return QuestData.get(sdk.quest.id.TerrorsEnd).checkState(9/*talked to tyrael*/); + } + ] + ]); + + if (!preCheck.get(act)()) { + announce("Failed act " + act + " precheck"); + return false; + } + + if (act !== 4) { + let [npc, loc] = npcTravel.get(act); + if (!npc) return false; + + !me.inTown && Town.goToTown(); + let npcUnit = Town.npcInteract(npc); + let timeout = getTickCount() + 3000; + let pingDelay = me.getPingDelay(); + + if (!npcUnit) { + while (!npcUnit && timeout < getTickCount()) { + Town.move(NPC[npc]); + Packet.flash(me.gid, pingDelay); + delay(pingDelay * 2 + 100); + npcUnit = Game.getNPC(npc); + } + } + + if (npcUnit) { + for (let i = 0; i < 5; i++) { + new PacketBuilder() + .byte(sdk.packets.send.EntityAction) + .dword(0) + .dword(npcUnit.gid) + .dword(loc) + .send(); + delay(1000); + + if (me.act === act) { + break; + } + } + } + } else { + if (me.inArea(sdk.areas.DuranceofHateLvl3)) { + let target = Game.getObject(sdk.objects.RedPortalToAct4); + target && Pather.moveTo(target.x - 3, target.y - 1); + + Pather.usePortal(null); + } + } + + while (!me.gameReady) { + delay(100); + } + + if (me.area === preArea) { + me.cancel(); + Town.move("portalspot"); + announce("Act change failed."); + + return false; + } + + Town.move("portalspot"); + announce("Act change successful."); + act === 2 && announce("Don't forget to talk to Drognan after getting the Viper Amulet!"); + + return true; + }; + + const pickPotions = function (range = 5) { + if (me.dead) return false; + + me.clearBelt(); + + while (!me.idle) { + delay(40); + } + + let pickList = []; + let item = Game.getItem(); + + if (item) { + do { + if (item.onGroundOrDropping && item.itemType >= sdk.items.type.HealingPotion + && item.itemType <= sdk.items.type.RejuvPotion && item.distance <= range) { + pickList.push(copyUnit(item)); + } + } while (item.getNext()); + } + + pickList.sort(Pickit.sortItems); + + while (pickList.length > 0) { + item = pickList.shift(); + + if (item && copyUnit(item).x) { + let status = Pickit.checkItem(item).result; + + if (status && Pickit.canPick(item)) { + Pickit.pickItem(item, status); + } + } + } + + return true; + }; + + /** + * @param {string} nick + * @param {string} msg + */ + const chatEvent = function (nick, msg) { + if (msg && nick === Config.Leader) { + if (toggleActions.has(msg)) { + toggleActions.get(msg)(); + + return; + } else if (quickActions.has(msg)) { + quickActions.get(msg)(); + + return; + } else if (specialActions.has(msg)) { + actions.push(msg); + + return; + } + let piecewise = msg.split(" "); + let who = piecewise.length > 1 && piecewise.first() || ""; + const isForMe = (charClass.includes(who) || who === me.name || who === "all"); + + if (me.paladin && isForMe && msg.includes("aura ")) { + let aura = parseInt(msg.split(" ")[2], 10); + + if (me.getSkill(aura, sdk.skills.subindex.SoftPoints)) { + announce("Active aura is: " + aura); + + Config.AttackSkill[2] = aura; + Config.AttackSkill[4] = aura; + + Skill.setSkill(aura, sdk.skills.hand.Right); + } else { + announce("I don't have that aura."); + } + } else if (isForMe && msg.includes("skill ")) { + let skill = parseInt(msg.split(" ")[2], 10); + + if (me.getSkill(skill, sdk.skills.subindex.SoftPoints)) { + announce("Attack skill is: " + skill); + + Config.AttackSkill[1] = skill; + Config.AttackSkill[3] = skill; + } else { + announce("I don't have that skill."); + } + } else { + if (who) { + if (isForMe) { + msg = msg.replace(who, "").trim(); + } else if (playerInGame(who)) { + return; + } + } + actions.push(msg); + } + } else if (msg && msg.split(" ")[0] === "leader" && (commanders.has(nick) || !commanders.size)) { + let piece = msg.split(" ")[1]; + + if (typeof piece === "string") { + commanders.add(piece); + announce("Switching leader to " + piece); + + Config.Leader = piece; + Leader.partyUnit = Misc.findPlayer(Config.Leader); + Leader.unit = Misc.getPlayerUnit(Config.Leader); + } + } + }; + + const gameEvent = function (mode, param1, param2, name1, name2) { + console.log("gameevent", mode, param1, param2, name1, name2); + if (name1 === Config.Leader + && mode === 0x07 + && param1 === 0x02 + && param2 === 0x09) { + recheck = true; + } + }; + + // START + let recheck = false; + addEventListener("chatmsg", chatEvent); + addEventListener("gameevent", gameEvent); + openContainers && Config.OpenChests.enabled && Config.OpenChests.Types.push("all"); + + // Override config values that use TP + Config.TownCheck = false; + Config.TownHP = 0; + Config.TownMP = 0; + const charClass = sdk.player.class.nameOf(me.classid).toLowerCase(); + const Leader = new function () { + /** @type {Party} */ + this.partyUnit = null; + /** @type {Player} */ + this.unit = null; + }; + + Leader.partyUnit = Misc.poll( + () => Misc.findPlayer(Config.Leader), + Time.minutes(3), + Time.seconds(1) + ); + + if (!Leader.partyUnit) { + announce("Leader not found."); + scriptBroadcast("quit"); + + return true; + } + + announce("Leader found :: " + Leader.partyUnit.name); + + while (!Misc.inMyParty(Config.Leader)) { + delay(500); + } + + announce("Partied."); + + if (Leader.partyUnit.inTown && !me.inTown) { + announce("Going to leader's town."); + Town.goToTown(sdk.areas.actOf(Leader.partyUnit.area)); + } + me.inTown && Town.move("portalspot"); + Leader.unit = Misc.getPlayerUnit(Config.Leader); + + // Main Loop + while (true) { + if (recheck) { + if (!Misc.poll(() => Misc.inMyParty(Config.Leader), Time.minutes(1), Time.seconds(1))) { + announce("Leader left party."); + + break; + } + recheck = false; + } + if (me.mode === sdk.player.mode.Dead) { + revive(); + } + + while (stop) { + delay(500); + } + + if (!me.inTown) { + try { + if (!Leader.unit) throw new Error("Leader not found."); + if (!Leader.partyUnit) throw new Error("party unit not found."); + if (me.inArea(Leader.partyUnit.area) && copyUnit(Leader.unit).x) { + if (Leader.unit.distance > 10) { + // Pather.moveToUnit(Leader.unit); + Pather.moveToEx( + Leader.unit.x, Leader.unit.y, { callback: () => ( + Leader.unit && Leader.unit.distance < 10 + ) } + ); + } + } else { + if (!me.inArea(Leader.partyUnit.area) && !me.inTown) { + while (Leader.partyUnit.area === 0) delay(100); + checkExit(Leader.partyUnit, Leader.partyUnit.area); + + while (me.area === 0) { + delay(100); + } + } + } + } catch (e) { + console.error(e); + if (Leader.partyUnit) { + if (me.inArea(Leader.partyUnit.area)) { + Pather.moveToEx( + Leader.partyUnit.x, Leader.partyUnit.y, { callback: () => { + Leader.unit = Misc.getPlayerUnit(Config.Leader); + return Leader.unit && Leader.unit.distance < 10; + } } + ); + } else if (Leader.partyUnit.inTown) { + // go back to town if there are there for awhile + if (!Misc.poll( + () => (Leader.unit = Misc.getPlayerUnit(Config.Leader)), + Time.seconds(45), + Time.seconds(1)) + ) { + Town.goToTown(sdk.areas.actOf(Leader.partyUnit.area)); + Town.move("portalspot"); + } + } + } else { + Leader.partyUnit = Misc.findPlayer(Config.Leader); + } + } + + if (attack) { + // custom attack needs to be done so we can keep track of leader while we attack in break + // early if leader moves on + Attack.clear(20, false, false, false, true); + pickPotions(20); + } + + if (me.paladin && Config.AttackSkill[2] > 0) { + Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); + } + } else if (!actions.length && getTickCount() - Town.lastChores > Time.minutes(3)) { + // no actions currently, lets do some town chores + if (me.gold > 1000 + && (me.needPotions() || me.checkScrolls(sdk.items.TomeofTownPortal) < 5)) { + Town.doChores(); + } + Town.getDistance("portalspot") > 5 && Town.move("portalspot"); + } + + if (actions.length) { + let action = actions.shift(); + + if (specialActions.has(action)) { + specialActions.get(action)(); + } else { + switch (action) { + case "reload": + case me.name + "reload": + scriptBroadcast("reload"); + + return true; + default: + console.log(action); + if (action.includes("talk")) { + talk(action.split(" ")[1]); + } else if (action.includes("chug")) { + let temp = action.toLowerCase().split(" "); + let [, type, amount] = temp; + amount === undefined && (amount = 10); + typeof amount === "string" && (amount = parseInt(amount, 10)); + + if (type === "a" || type === "antidote") { + Town.buyPots(amount, sdk.items.AntidotePotion, true, true); + } else if (type === "t" || type === "thawing") { + Town.buyPots(amount, sdk.items.ThawingPotion, true, true); + } else if (type === "s" || type === "stamina") { + Town.buyPots(amount, sdk.items.StaminaPotion, true, true); + } + } else if (action.includes("taxi")) { + let [, where] = action.split(" "); + console.log(where); + if (where) { + try { + if (taxiMap.has(where)) { + taxiMap.get(where)(); + } else { + let _areaId = parseInt(where, 10); + Pather.journeyTo(_areaId); + } + Pather.makePortal(); + } catch (e) { + console.error(e); + announce("Failed to taxi to " + where); + Town.goToTown(); + } + } else if (action === "taxi list") { + announce("Taxi destinations: " + Array.from(taxiMap.keys()).join(", ")); + } + } + } + } + } + + delay(100); + } + + removeEventListener("chatmsg", chatEvent); + removeEventListener("gameevent", gameEvent); + + return true; + } +); diff --git a/d2bs/kolbot/libs/scripts/Frozenstein.js b/d2bs/kolbot/libs/scripts/Frozenstein.js new file mode 100644 index 000000000..0934e4d1a --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Frozenstein.js @@ -0,0 +1,34 @@ +/** +* @filename Frozenstein.js +* @author kolton, theBGuy +* @desc kill Frozenstein and optionally clear Frozen River +* +*/ + +const Frozenstein = new Runnable( + function Frozenstein () { + Pather.useWaypoint(sdk.areas.CrystalizedPassage); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.FrozenRiver, true)) { + throw new Error("Failed to move to frozen river"); + } + + if (!Attack.haveKilled(getLocaleString(sdk.locale.monsters.Frozenstein))) { + if (!Pather.moveToExit(sdk.areas.FrozenRiver, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform, -5, -5)) { + throw new Error("Failed to move to Frozenstein"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.Frozenstein)); + } else { + console.log("Frozenstein already dead"); + } + Config.Frozenstein.ClearFrozenRiver && Attack.clearLevel(Config.ClearType); + + return true; + }, + { + startArea: sdk.areas.CrystalizedPassage + } +); diff --git a/d2bs/kolbot/libs/scripts/Gamble.js b/d2bs/kolbot/libs/scripts/Gamble.js new file mode 100644 index 000000000..5bf462515 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Gamble.js @@ -0,0 +1,66 @@ +/** +* @filename Gamble.js +* @author kolton, theBGuy (added anti-idle) +* @desc keep gambling while other players supply you with gold +* +*/ + +const Gamble = new Runnable( + function Gamble () { + let info = Gambling.getInfo(); + if (!info) throw new Error("Bad Gambling System config."); + + let idleTick = 0; + let needGold = false; + + me.maxgametime = 0; + Town.goToTown(1); + + addEventListener("copydata", + function (mode, msg) { + if (needGold && mode === 0 && info.goldFinders.indexOf(msg) > -1) { + print("Got game request from " + msg); + sendCopyData(null, msg, 4, me.gamename + "/" + me.gamepassword); + } + }); + + while (true) { + Town.needGamble() ? Town.gamble() : (needGold = true) && (idleTick = 0); + Town.move("stash"); + + while (needGold) { + // should there be a player count check before getting into this loop? + // Or maybe gamevent for player join/leave, or itemevent for gold dropping? + while (true) { + Town.needGamble() && (needGold = false); + Town.stash(); + + let gold = Game.getItem(sdk.items.Gold, sdk.items.mode.onGround); + + if (!gold || !Pickit.canPick(gold)) { + break; + } + + Pickit.pickItem(gold); + delay(500); + + } + + if (needGold && getTickCount() - idleTick > 0) { + Packet.questRefresh(); + idleTick += rand(1200, 1500) * 1000; + } + + delay(500); + } + + delay(1000); + } + + // eslint-disable-next-line no-unreachable + return true; + }, + { + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/GemHunter.js b/d2bs/kolbot/libs/scripts/GemHunter.js new file mode 100644 index 000000000..7bdaf09b4 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GemHunter.js @@ -0,0 +1,45 @@ +/** +* @filename GemHunter.js +* @author icommitdesnet +* @desc hunt gem shrines +* +*/ + +/** + * @todo If this script is going to be run, and we run across a gem shrine in a different one we should: + * - Call check if we have an gems to upgrade in the stash instead of always keep some in invo as that takes up space. + * If we do, go get the gem from the stash before activating shrine. + * - We should also then keep track of where the shrine was, (I don't remember if gem shrines regen, so check this) + * - Take into account the next area and sort the shrines to bring us to the exit if its connected + */ +const GemHunter = new Runnable( + function GemHunter () { + Town.doChores(); + Town.getGem(); + if (Town.getGemsInInv().length === 0) { + console.log("ÿc4GemHunterÿc0: no gems in inventory - aborting."); + return false; + } + + for (let area of Config.GemHunter.AreaList) { + if (Town.getGemsInInv().length > 0) { + console.log("ÿc4GemHunterÿc0: Moving to " + getAreaName(area)); + Pather.journeyTo(area); + if (i % 2 === 0) Precast.doPrecast(true); + if (Misc.getShrinesInArea(area, sdk.shrines.Gem, true)) { + Pickit.pickItems(); + console.log("ÿc4GemHunterÿc0: found a gem Shrine"); + if ((Town.getGemsInInv().length === 0) && (Town.getGemsInStash().length > 0)) { + console.log("ÿc4GemHunterÿc0: Getting a new Gem in Town."); + Town.visitTown(); // Go to Town and do chores. Will throw an error if it fails to return from Town. + Town.getGem(); + } + } + } + } + return true; + }, + { + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/GetCube.js b/d2bs/kolbot/libs/scripts/GetCube.js new file mode 100644 index 000000000..481efc24f --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GetCube.js @@ -0,0 +1,41 @@ +/** +* @filename GetCube.js +* @author theBGuy +* @desc Go get the cube from Halls if we don't have it are enabled to cube +* +*/ + +const GetCube = new Runnable( + function GetCube () { + // Can't get the cube if we can't access the act + if (!me.accessToAct(2)) return false; + + console.log("Getting cube"); + me.overhead("Getting cube"); + + Pather.useWaypoint(sdk.areas.HallsoftheDeadLvl2, true); + Precast.doPrecast(true); + + if (Pather.moveToExit(sdk.areas.HallsoftheDeadLvl3, true) + && Pather.moveToPresetObject(me.area, sdk.quest.chest.HoradricCubeChest)) { + let chest = Game.getObject(sdk.quest.chest.HoradricCubeChest); + + if (chest) { + Misc.openChest(chest); + Misc.poll(function () { + let cube = Game.getItem(sdk.quest.item.Cube); + return !!cube && Pickit.pickItem(cube); + }, 1000, 2000); + } + } + + Town.goToTown(); + let cube = me.getItem(sdk.quest.item.Cube); + + return (!!cube && Storage.Stash.MoveTo(cube)); + }, + { + startArea: sdk.areas.HallsoftheDeadLvl2, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/GetEssences.js b/d2bs/kolbot/libs/scripts/GetEssences.js new file mode 100644 index 000000000..002670f7f --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GetEssences.js @@ -0,0 +1,63 @@ +/** +* @filename GetEssences.js +* @author magace +* @credits kolton for the original GetKeys +* @desc get essences for Token of Absolution +* +*/ + +const GetEssences = new Runnable( + function GetEssences () { + /** + * @param {number} essence + * @returns {number} + */ + const count = function (essence) { + return me.getItemsEx(essence, sdk.items.mode.inStorage).length; + }; + + if (count(sdk.quest.item.TwistedEssenceofSuffering) < 1) { + try { + Loader.runScript("Andariel"); + } catch (e) { + console.error("ÿc1Andariel failed :: ", e); + } + } + + if (Config.GetEssences.RunDuriel && count(sdk.quest.item.TwistedEssenceofSuffering) < 1) { + try { + Loader.runScript("Duriel"); + } catch (e) { + console.error("ÿc1Duriel failed :: ", e); + } + } + + if (count(sdk.quest.item.ChargedEssenceofHatred) < 1) { + try { + Config.Mephisto.MoatTrick = Config.GetEssences.MoatMeph; + Loader.runScript("Mephisto"); + } catch (e) { + console.error("ÿc1Mephisto failed :: ", e); + } + } + + if (count(sdk.quest.item.BurningEssenceofTerror) < 1) { + try { + Config.Diablo.Fast = Config.GetEssences.FastDiablo; + Loader.runScript("Diablo"); + } catch (e) { + console.error("ÿc1Diablo failed :: ", e); + } + } + + if (count(sdk.quest.item.FesteringEssenceofDestruction) < 1) { + try { + Loader.runScript("Baal"); + } catch (e) { + console.error("ÿc1Baal failed :: ", e); + } + } + + return true; + } +); diff --git a/d2bs/kolbot/libs/scripts/GetFade.js b/d2bs/kolbot/libs/scripts/GetFade.js new file mode 100644 index 000000000..13f329993 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GetFade.js @@ -0,0 +1,72 @@ +/** +* @filename GetFade.js +* @author theBGuy +* @desc Get fade in River of Flames - only works if we are wearing an item with ctc Fade +* +*/ + +const GetFade = new Runnable( + function GetFade () { + // Can't get use river if we can't access the act - TODO: use another area if we can't access river + if (!me.accessToAct(4)) return false; + // already have fade + if (me.getState(sdk.states.Fade)) return true; + + /** @type {{ have: boolean, item: ItemUnit }} */ + const fadeItem = me.findFirst([ + { name: sdk.locale.items.Treachery, equipped: true }, + { name: sdk.locale.items.LastWish, equipped: true }, + { name: sdk.locale.items.SpiritWard, equipped: true } + ]); + if (!fadeItem) throw new Error("No item with ctc Fade equipped"); + + console.log("Getting fade"); + me.overhead("Getting fade"); + + Pather.useWaypoint(sdk.areas.RiverofFlame, true); + Precast.doPrecast(true); + + /** @type {PathSettings} */ + const pathSettings = { minDist: 2 }; + const leftFire = new PathNode(7787, 5873); + const rightFire = new PathNode(7811, 5872); + + // check if item is on switch + let mainSlot; + + Pather.move(rightFire, pathSettings); + + if (fadeItem.have && fadeItem.item.isOnSwap && me.weaponswitch !== sdk.player.slot.Secondary) { + mainSlot = me.weaponswitch; + me.switchWeapons(sdk.player.slot.Secondary); + } + + Skill.canUse(sdk.skills.Salvation) && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right); + + try { + let retry = 0; + let timeout = getTickCount() + Time.minutes(1); + while (!me.getState(sdk.states.Fade)) { + if (getTickCount() > timeout) { + retry++; + if (retry > 5) { + throw new Error("Failed to get fade"); + } + Pather.randMove(-1, 1, -1, 1, 3); + retry % 2 === 0 + ? Pather.move(leftFire, pathSettings) + : Pather.move(rightFire, pathSettings); + timeout = getTickCount() + Time.minutes(1); + } + delay(3); + } + return me.getState(sdk.states.Fade); + } finally { + mainSlot !== undefined && me.weaponswitch !== mainSlot && me.switchWeapons(mainSlot); + } + }, + { + startArea: sdk.areas.RiverofFlame, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/GetKeys.js b/d2bs/kolbot/libs/scripts/GetKeys.js new file mode 100644 index 000000000..219282cc2 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GetKeys.js @@ -0,0 +1,44 @@ +/** +* @filename GetKeys.js +* @author kolton, theBGuy +* @desc get them keys +* +*/ + +const GetKeys = new Runnable( + function GetKeys () { + /** + * @param {number} id + * @returns {number} + */ + const count = function (id) { + return me.getItemsEx(id, sdk.items.mode.inStorage).length; + }; + + if (count(sdk.items.quest.KeyofTerror) < 3) { + try { + Loader.runScript("Countess"); + } catch (e) { + console.error("ÿc1Countess failed :: ", e); + } + } + + if (count(sdk.items.quest.KeyofHate) < 3) { + try { + Loader.runScript("Summoner", () => Config.Summoner.FireEye = false); + } catch (e) { + console.error("ÿc1Summoner failed :: ", e); + } + } + + if (count(sdk.items.quest.KeyofDestruction) < 3) { + try { + Loader.runScript("Nihlathak"); + } catch (e) { + console.error("ÿc1Nihlathak failed :: ", e); + } + } + + return true; + } +); diff --git a/d2bs/kolbot/libs/scripts/GhostBusters.js b/d2bs/kolbot/libs/scripts/GhostBusters.js new file mode 100644 index 000000000..38d74b1fd --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GhostBusters.js @@ -0,0 +1,121 @@ +/** +* @filename GhostBusters.js +* @author kolton, theBGuy +* @desc who you gonna call? +* +*/ + +const GhostBusters = new Runnable( + function GhostBusters () { + const clearGhosts = function () { + let room = getRoom(); + if (!room) return false; + + const rooms = []; + /** @param {Monster} monster */ + const check = function (monster) { + return monster.isGhost && monster.distance <= 30; + }; + + do { + rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]); + } while (room.getNext()); + + while (rooms.length > 0) { + rooms.sort(Sort.points); + room = rooms.shift(); + + let result = Pather.getNearestWalkable(room[0], room[1], 15, 2); + + if (result) { + Pather.moveTo(result[0], result[1], 3); + + let monList = Attack.buildMonsterList(check); + if (!monList.length) continue; + + if (!Attack.clearList(monList)) { + return false; + } + } + } + + return true; + }; + + const tasks = new Map([ + ["cellar", () => { + Pather.useWaypoint(sdk.areas.BlackMarsh); + Precast.doPrecast(true); + + for (let i = sdk.areas.ForgottenTower; i <= sdk.areas.TowerCellarLvl5; i += 1) { + Pather.moveToExit(i, true) && clearGhosts(); + } + }], + ["jail", () => { + // gonna use inner cloister wp and travel backwards + Pather.useWaypoint(sdk.areas.InnerCloister); + Precast.doPrecast(true); + + for (let i = sdk.areas.JailLvl3; i >= sdk.areas.JailLvl1; i -= 1) { + Pather.moveToExit(i, true) && clearGhosts(); + } + }], + ["cathedral", () => { + Pather.useWaypoint(sdk.areas.InnerCloister); + Precast.doPrecast(true); + Pather.moveToExit(sdk.areas.Cathedral, true); + clearGhosts(); + }], + ["tombs", () => { + Pather.useWaypoint(sdk.areas.CanyonofMagic); + Precast.doPrecast(true); + + for (let i = sdk.areas.TalRashasTomb1; i <= sdk.areas.TalRashasTomb7; i += 1) { + Pather.moveToExit(i, true) && clearGhosts(); + Pather.moveToExit(sdk.areas.CanyonofMagic, true); + } + }], + ["flayerDungeon", () => { + Pather.useWaypoint(sdk.areas.FlayerJungle); + Precast.doPrecast(true); + + [sdk.areas.FlayerDungeonLvl1, sdk.areas.FlayerDungeonLvl2, sdk.areas.FlayerDungeonLvl3].forEach(area => { + Pather.moveToExit(area, true) && clearGhosts(); + }); + }], + ["crystalinePassage", () => { + Pather.useWaypoint(sdk.areas.CrystalizedPassage); + Precast.doPrecast(true); + clearGhosts(); + Pather.moveToExit(sdk.areas.FrozenRiver, true) && clearGhosts(); + }], + ["glacialTrail", () => { + Pather.useWaypoint(sdk.areas.GlacialTrail); + Precast.doPrecast(true); + clearGhosts(); + Pather.moveToExit(sdk.areas.DrifterCavern, true) && clearGhosts(); + }], + ["icyCellar", () => { + Pather.useWaypoint(sdk.areas.AncientsWay); + Precast.doPrecast(true); + Pather.moveToExit(sdk.areas.IcyCellar, true) && clearGhosts(); + }] + ]); + + tasks.forEach(task => { + Town.doChores(); + + try { + task(); + } finally { + Town.goToTown(); + } + }); + + return true; + }, + { + startArea: sdk.areas.BlackMarsh, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Hephasto.js b/d2bs/kolbot/libs/scripts/Hephasto.js new file mode 100644 index 000000000..1858ecdd2 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Hephasto.js @@ -0,0 +1,34 @@ +/** +* @filename Hephasto.js +* @author kolton +* @desc kill Hephasto the Armorer - optionally clear river +* +*/ + +const Hephasto = new Runnable( + function Hephasto () { + Pather.useWaypoint(sdk.areas.RiverofFlame); + Precast.doPrecast(true); + + if (!Attack.haveKilled(sdk.monsters.Hephasto)) { + if (!Pather.moveToPresetObject(me.area, sdk.quest.chest.HellForge)) { + throw new Error("Failed to move to Hephasto"); + } + + try { + Attack.kill(sdk.monsters.Hephasto); + } catch (e) { + console.log("Heph not found. Carry on"); + } + + Pickit.pickItems(); + } + + Config.Hephasto.ClearRiver && Attack.clearLevel(Config.Hephasto.ClearType); + + return true; + }, + { + startArea: sdk.areas.RiverofFlame + } +); diff --git a/d2bs/kolbot/libs/scripts/IPHunter.js b/d2bs/kolbot/libs/scripts/IPHunter.js new file mode 100644 index 000000000..28dee9ee5 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/IPHunter.js @@ -0,0 +1,64 @@ +/** +* @filename IPHunter.js +* @author kolton, Mercoory +* @desc search for a "hot" IP and stop if the correct server is found +* @changes 2020.01 - more beeps and movements (anti drop measure) when IP is found +* overhead messages with countdown timer; logs to D2Bot console +* +*/ + +const IPHunter = new Runnable( + function IPHunter () { + const ip = Number(me.gameserverip.split(".")[3]); + + if (Config.IPHunter.IPList.includes(ip)) { + load("threads/antiidle.js"); + const foundMsg = "IPHunter: IP found! - [" + ip + "] Game is : " + me.gamename + "//" + me.gamepassword; + D2Bot.printToConsole(foundMsg, sdk.colors.D2Bot.DarkGold); + console.log(foundMsg); + me.overhead(":D IP found! - [" + ip + "]"); + me.maxgametime = 0; + + for (let i = 12; i > 0; i -= 1) { + me.overhead(":D IP found! - [" + ip + "]" + (i - 1) + " beep left"); + beep(); // works if windows sounds are enabled + delay(250); + } + + while (true) { + /* // remove comment if you want beeps at every movement + for (let i = 12; i != 0; i -= 1) { + me.overhead(":D IP found! - [" + ip + "]" + (i-1) + " beep left"); + beep(); // works if windows sounds are enabled + delay(250); + } + */ + + me.overhead(":D IP found! - [" + ip + "]"); + try { + Town.move("waypoint"); + Town.move("stash"); + } catch (e) { + // ensure it doesnt leave game by failing to walk due to desyncing. + } + + for (let i = (12 * 60); i > 0; i -= 1) { + me.overhead(":D IP found! - [" + ip + "] Next movement in: " + i + " sec."); + delay(1000); + } + } + } + + for (let i = (Config.IPHunter.GameLength * 60); i > 0; i -= 1) { + me.overhead(":( IP : [" + (ip) + "] NG: " + i + " sec"); + delay(1000); + } + + D2Bot.printToConsole("IPHunter: IP was [" + ip + "]", sdk.colors.D2Bot.Gray); + + return true; + }, + { + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Icehawk.js b/d2bs/kolbot/libs/scripts/Icehawk.js new file mode 100644 index 000000000..022d6d995 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Icehawk.js @@ -0,0 +1,25 @@ +/** +* @filename Icehawk.js +* @author kolton, theBGuy +* @desc kill Icehawk Riftwing +* +*/ + +const Icehawk = new Runnable( + function Icehawk () { + Pather.useWaypoint(sdk.areas.KurastBazaar); + Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2], false)) { + throw new Error("Failed to move to Icehawk"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.IcehawkRiftwing)); + + return true; + }, + { + startArea: sdk.areas.KurastBazaar, + bossid: getLocaleString(sdk.locale.monsters.IcehawkRiftwing), + } +); diff --git a/d2bs/kolbot/libs/scripts/Idle.js b/d2bs/kolbot/libs/scripts/Idle.js new file mode 100644 index 000000000..463d7c60d --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Idle.js @@ -0,0 +1,64 @@ +/** +* @filename Idle.js +* @author theBGuy +* @desc Idle companion script +* +*/ + +const Idle = new Runnable( + function Idle () { + const greet = []; + // eslint-disable-next-line no-unused-vars + function gameEvent (mode, param1, param2, name1, name2) { + switch (mode) { + case 0x02: + // idle in town + me.inTown && me.mode === sdk.player.mode.StandingInTown && greet.push(name1); + + break; + } + } + + try { + const startTime = getTickCount(); + let idleTick = getTickCount() + Time.seconds(rand(1200, 1500)); + if (Config.Idle.Advertise) { + addEventListener("gameevent", gameEvent); + } + + while (true) { + if (!me.inArea(sdk.areas.RogueEncampment)) { + Town.goToTown(1); + } else if (Town.getDistance("stash") > 10) { + Town.move("stash"); + } + if (Config.Idle.MaxGameLength > 0 + && getTickCount() - startTime > Time.minutes(Config.Idle.MaxGameLength)) { + break; + } + if (Config.Idle.Advertise) { + while (greet.length) { + let name = greet.shift(); + say("!Welcome " + name + ". " + Config.Idle.AdvertiseMessage); + } + } + if (getTickCount() - idleTick > 0) { + Packet.questRefresh(); + idleTick += Time.seconds(rand(1200, 1500)); + console.log("Sent anti-idle packet, next refresh in: (" + Time.format(idleTick - getTickCount()) + ")"); + } + + delay(1000); + } + + return true; + } finally { + // cleanup + removeEventListener("gameevent", gameEvent); + } + }, + { + startArea: sdk.areas.RogueEncampment, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Izual.js b/d2bs/kolbot/libs/scripts/Izual.js new file mode 100644 index 000000000..94a642132 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Izual.js @@ -0,0 +1,26 @@ +/** +* @filename Izual.js +* @author kolton +* @desc kill Izual +* +*/ + +const Izual = new Runnable( + function Izual () { + Pather.useWaypoint(sdk.areas.CityoftheDamned); + Precast.doPrecast(true); + + if (!Pather.moveToPresetMonster(sdk.areas.PlainsofDespair, sdk.monsters.Izual)) { + throw new Error("Failed to move to Izual."); + } + + Attack.kill(sdk.monsters.Izual); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.CityoftheDamned, + bossid: sdk.monsters.Izual, + } +); diff --git a/d2bs/kolbot/libs/scripts/KillDclone.js b/d2bs/kolbot/libs/scripts/KillDclone.js new file mode 100644 index 000000000..f8a80b2db --- /dev/null +++ b/d2bs/kolbot/libs/scripts/KillDclone.js @@ -0,0 +1,30 @@ +/** +* @filename KillDclone.js +* @author kolton +* @desc Go to Palace Cellar level 3 and kill Diablo Clone. +* +*/ + +const KillDclone = new Runnable( + function KillDclone () { + Pather.useWaypoint(sdk.areas.ArcaneSanctuary); + Precast.doPrecast(true); + + if (!Pather.usePortal(null)) { + throw new Error("Failed to move to Palace Cellar"); + } + + Attack.kill(sdk.monsters.DiabloClone); + Pickit.pickItems(); + + if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) { + scriptBroadcast("muleAnni"); + } + + return true; + }, + { + startArea: sdk.areas.ArcaneSanctuary, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/KurastTemples.js b/d2bs/kolbot/libs/scripts/KurastTemples.js new file mode 100644 index 000000000..3be1a0227 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/KurastTemples.js @@ -0,0 +1,47 @@ +/** +* @filename KurastTemples.js +* @author kolton, theBGuy +* @desc clear Kurast Temples +* +*/ + +const KurastTemples = new Runnable( + function KurastTemples () { + Pather.useWaypoint(sdk.areas.KurastBazaar); + + [ + { base: sdk.areas.KurastBazaar, temples: [sdk.areas.RuinedTemple, sdk.areas.DisusedFane] }, + { base: sdk.areas.UpperKurast, temples: [sdk.areas.ForgottenReliquary, sdk.areas.ForgottenTemple] }, + { base: sdk.areas.KurastCauseway, temples: [sdk.areas.RuinedFane, sdk.areas.DisusedReliquary] }, + ].forEach(area => { + try { + if (!me.inArea(area.base)) { + // maybe journeyTo instead? + if (!Pather.moveToExit(area.base, true)) throw new Error("Failed to change area"); + } + let precastTimeout = getTickCount() + Time.minutes(2); + /** @type {Map area.temples.some(temple => temple === exit.target)) + .forEach(exit => _temples.set(exit.target, { x: exit.x, y: exit.y })); + area.temples + .sort((a, b) => _temples.get(a).distance - _temples.get(b).distance) + .forEach(temple => { + if (!Pather.moveToExit(temple, true)) throw new Error("Failed to move to the temple"); + Attack.clearLevel(Config.ClearType); + if (!Pather.moveToExit(area.base, true)) throw new Error("Failed to move out of the temple"); + Precast.doPrecast((getTickCount() > precastTimeout)); + }); + + } catch (e) { + console.error(e); + } + }); + + return true; + }, + { + startArea: sdk.areas.KurastBazaar + } +); diff --git a/d2bs/kolbot/libs/scripts/MFHelper.js b/d2bs/kolbot/libs/scripts/MFHelper.js new file mode 100644 index 000000000..b88f89a7f --- /dev/null +++ b/d2bs/kolbot/libs/scripts/MFHelper.js @@ -0,0 +1,306 @@ +/** +* @filename MFHelper.js +* @author kolton, theBGuy +* @desc help another player kill bosses or clear areas +* +*/ + +const MFHelper = new Runnable( + function MFHelper () { + const startTime = getTickCount(); + /** + * @todo We should be able to handle Diablo scripts then resume MFHelper, not sure how yet but doesn't make sense to have + * helper just idle if leader does any of the a5 scripts before baal. I guess could re-order them in the configs but having + * it broken up by act flows better + */ + let player, playerAct, split; + let lastPrecast; + + /** @type {{ task: string, msg: string, at: number, area: number }[]} */ + const taskList = []; + const tasks = ["kill", "clearlevel", "clear", "quit", "cows", "council", "goto", "nextup"]; + + /** + * @param {string} name + * @param {string} msg + */ + function chatEvent (name, msg) { + if (!msg) return; + let msgShort = msg && msg.length ? msg.split(" ")[0] : ""; + if (!tasks.includes(msgShort)) return; + + if (!player) { + // anything else we need to consider here? + player = Misc.findPlayer(name); + } + + if (player && name === player.name) { + taskList.push({ task: msgShort, msg: msg, at: getTickCount(), area: player.area }); + } + } + + function breakClearLevel () { + if (!Config.MFHelper.BreakClearLevel) return false; + if (taskList.length) { + console.debug("Recieved new task, breaking clearLevel"); + + return true; + } + + return false; + } + + addEventListener("chatmsg", chatEvent); + Town.doChores(); + Town.move("portalspot"); + + if (Config.Leader) { + if (!Misc.poll(() => Misc.inMyParty(Config.Leader), 30e4, 1000)) { + throw new Error("MFHelper: Leader not partied"); + } + + player = Misc.findPlayer(Config.Leader); + } + + if (player) { + if (!Misc.poll(() => player.area, 120 * 60, 100 + me.ping)) { + throw new Error("Failed to wait for player area"); + } + + playerAct = Misc.getPlayerAct(Config.Leader); + + if (playerAct && playerAct !== me.act) { + Town.goToTown(playerAct); + Town.move("portalspot"); + } + } + + // START + while (true) { + if (me.dead) { + while (me.mode === sdk.player.mode.Death) { + delay(3); + } + + if (me.hardcore) { + D2Bot.printToConsole( + "(MFHelper) :: " + me.charname + " has died at level " + + me.charlvl + ". Shutting down profile...", + sdk.colors.D2Bot.Red + ); + D2Bot.stop(); + } + + while (!me.inTown) { + me.revive(); + delay(1000); + } + + Town.move("portalspot"); + console.log("revived!"); + } + + if (player) { + if (me.needHealing() && Town.heal()) { + Town.move("portalspot"); + } + + // Finish MFHelper script if leader is running Diablo or Baal AND we've been running longer than 30s + if ((getTickCount() - startTime > Time.seconds(30)) && [ + sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber + ].includes(player.area)) { + break; + } + + if (taskList.length) { + console.debug("Leader area :: " + player.area); + + if (taskList[0].task === "quit") return true; + // check if any message is telling us to quit + if (taskList.find(el => el.task === "quit")) return true; + + // check if any message is telling us that nextup is diablo/baal + if (taskList.some(el => { + if (el.task === "nextup") { + let script = el.msg.split("nextup ")[1]; + + if (script && ["Diablo", "Baal"].includes(script)) { + console.log("ÿc4MFHelperÿc0: Ending script"); + return true; + } + } + + return false; + })) return true; + + // handled pre-reqs, now perform normal checks + let { task, msg, at, area } = taskList.shift(); + + switch (task) { + case "goto": + try { + // lets see if the task list contains any other goto messages, in case we were late + { + let gt = taskList.findIndex(el => el.task === "goto"); + if (gt > -1) { + // alright there is another so lets see where we should actually be going + for (let i = 0; i < gt - 1; i++) { + // feels hacky but this should remove all elements up to the next goto message, while preserving the order of list + ({ task, msg, at, area } = taskList.shift()); + } + } + } + + split = msg.substr(6); + console.log("ÿc4MFHelperÿc0: Goto " + split); + + if (!!parseInt(split, 10)) { + split = parseInt(split, 10); + } + + Town.goToTown(split, true); + Town.move("portalspot"); + } catch (townerror) { + console.log(townerror); + } + + break; + case "nextup": + split = msg.split("nextup ")[1]; + console.log("ÿc4MFHelperÿc0: NextUp " + split); + + break; + case "cows": + console.log("ÿc4MFHelperÿc0: Clear Cows"); + + if (Misc.poll(() => { + Town.goToTown(1) && Pather.usePortal(sdk.areas.MooMooFarm); + return me.inArea(sdk.areas.MooMooFarm); + }, Time.minutes(1), 500 + me.ping)) { + include("core/Common/Cows.js"); + Precast.doPrecast(false); + Common.Cows.clearCowLevel(); + delay(1000); + } else { + console.warn("Failed to use portal. Currently in area: " + me.area); + } + + break; + case "council": + if (!me.inArea(sdk.areas.Travincal) && Town.goToTown(3)) { + Town.move("portalspot"); + Misc.poll(() => Pather.usePortal(sdk.areas.Travincal, player.name), Time.seconds(15), 500 + me.ping); + } + console.log("ÿc4MFHelperÿc0: Kill Council"); + Attack.clearClassids(sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3); + + break; + default: + // alright first lets check how long its been since the command was given + // this probably needs to be adjusted but for now 3 minutes on any of theses tasks is probably too long + if (getTickCount() - at > Time.minutes(3)) continue; + + /** + * @todo still think this section needs to be done better, we are using a snapshot of the player's area at the time + * of the message but sometimes the area hasn't been updated yet, causing us to do dumb things like attempt to kill + * while still in town. We can't just use the players area though because of towncheck/chicken. Feel like best solution + * would be adding area into leaders message and just always parsing it from there + */ + try { + split = msg.split(task + " ")[1]; + if (parseInt(split, 10)) { + split = parseInt(split, 10); + } + } catch (e) { + console.warn(e.message || "Failed to get id from message split"); + break; + } + + if (me.area !== area) { + !me.inTown && Town.goToTown(); + + if (me.act !== sdk.areas.actOf(area)) { + Town.goToTown(sdk.areas.actOf(area)); + Town.move("portalspot"); + } + + String.isEqual(task, "clearlevel") + ? (area = split) + : (player.area !== area && !player.inTown) && (area = player.area); + + try { + Misc.poll(() => Pather.usePortal(null, player.name), Time.seconds(15), 500 + me.ping); + } catch (e) { + console.warn(e.message || "Failed to take leader portal"); + continue; + } + } + + if (!me.inTown && me.area === area) { + let forceCast = false; + if (!lastPrecast || getTickCount() - lastPrecast > Time.minutes(2)) { + (forceCast = true) && (lastPrecast = getTickCount()); + } + Precast.doPrecast(forceCast); + } else if (!me.inTown && !me.inArea(player.area)) { + Town.goToTown(sdk.areas.actOf(player.area)); + continue; + } + + switch (task) { + case "kill": + console.log("ÿc4MFHelperÿc0: Kill " + split); + + try { + Attack.kill(split); + Pickit.pickItems(); + } catch (killerror) { + console.error(killerror); + } + + break; + case "clearlevel": + try { + console.log("ÿc4MFHelperÿc0: Clear Level " + getAreaName(split)); + + if (me.area !== split) { + Town.goToTown(sdk.areas.actOf(split)); + Town.move("portalspot"); + if (!Misc.poll(() => Pather.usePortal(split, player.name), Time.seconds(15), 500 + me.ping)) { + throw new Error("Failed to move to clearlevel area"); + } + } + Attack.clearLevel(Config.ClearType, breakClearLevel); + } catch (killerror2) { + console.error(killerror2); + } + + break; + case "clear": + console.log("ÿc4MFHelperÿc0: Clear " + split); + + try { + Attack.clear(15, 0, split); + } catch (killerror2) { + console.error(killerror2); + } + + break; + } + + if (!Pather.getPortal(sdk.areas.townOf(me.act)) || !Pather.usePortal(sdk.areas.townOf(me.act))) { + Town.goToTown(); + } + } + } + } + + delay(100); + } + + return true; + }, + { + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Mausoleum.js b/d2bs/kolbot/libs/scripts/Mausoleum.js new file mode 100644 index 000000000..c56703a7f --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Mausoleum.js @@ -0,0 +1,51 @@ +/** +* @filename Mausoleum.js +* @author kolton, theBGuy +* @desc clear Mausoleum - optionally kill Bishibosh and Bloodraven along the way. Also optionally clear crypt +* +*/ + +const Mausoleum = new Runnable( + function Mausoleum () { + Pather.useWaypoint(sdk.areas.ColdPlains); + Precast.doPrecast(true); + + if (Config.Mausoleum.KillBishibosh && !Attack.haveKilled(getLocaleString(sdk.locale.monsters.Bishibosh))) { + Pather.moveToPresetMonster(sdk.areas.ColdPlains, sdk.monsters.preset.Bishibosh); + Attack.kill(getLocaleString(sdk.locale.monsters.Bishibosh)); + Pickit.pickItems(); + } + + if (!Pather.moveToExit(sdk.areas.BurialGrounds, true)) throw new Error("Failed to move to Burial Grounds"); + + if (Config.Mausoleum.KillBloodRaven && !Attack.haveKilled(sdk.monsters.BloodRaven)) { + Pather.moveToPresetMonster(sdk.areas.BurialGrounds, sdk.monsters.preset.BloodRaven, { + minDist: me.sorceress && Pather.canTeleport() ? 30 : 5 + }); + Attack.kill(getLocaleString(sdk.locale.monsters.BloodRaven)); + Pickit.pickItems(); + } + + try { + Pather.moveToExit(sdk.areas.Mausoleum, true) && Attack.clearLevel(Config.ClearType); + } catch (e) { + console.error(e); + } + + if (Config.Mausoleum.ClearCrypt) { + // Crypt exit is... awkward + if (!(Pather.moveToExit(sdk.areas.BurialGrounds, true) + && Pather.moveToPreset(sdk.areas.BurialGrounds, sdk.unittype.Stairs, sdk.exits.preset.Crypt) + && Pather.moveToExit(sdk.areas.Crypt, true))) { + throw new Error("Failed to move to Crypt"); + } + + Attack.clearLevel(Config.ClearType); + } + + return true; + }, + { + startArea: sdk.areas.ColdPlains + } +); diff --git a/d2bs/kolbot/libs/scripts/Mephisto.js b/d2bs/kolbot/libs/scripts/Mephisto.js new file mode 100644 index 000000000..8f4d22ca1 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Mephisto.js @@ -0,0 +1,167 @@ +/** +* @filename Mephisto.js +* @author kolton, njomnjomnjom +* @desc kill Mephisto +* +*/ + +const Mephisto = new Runnable( + function Mephisto () { + // eslint-disable-next-line no-unused-vars + const killMephisto = function () { + let pos = {}; + let attackCount = 0; + let meph = Game.getMonster(sdk.monsters.Mephisto); + if (!meph) throw new Error("Mephisto not found!"); + + Config.MFLeader && Pather.makePortal() && say("kill " + meph.classid); + + while (attackCount < 300 && meph.attackable(meph)) { + if (meph.mode === sdk.monsters.mode.Attacking2) { + let angle = Math.round(Math.atan2(me.y - meph.y, me.x - meph.x) * 180 / Math.PI); + let angles = me.y > meph.y ? [-30, -60, -90] : [30, 60, 90]; + + for (let i = 0; i < angles.length; i += 1) { + pos.dist = 18; + pos.x = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * pos.dist + meph.x); + pos.y = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * pos.dist + meph.y); + + if (Attack.validSpot(pos.x, pos.y)) { + me.overhead("move, bitch!"); + Pather.moveTo(pos.x, pos.y); + + break; + } + } + } + + if (ClassAttack.doAttack(meph) < 2) { + break; + } + + attackCount += 1; + } + + return meph.dead; + }; + + const moat = function () { + /** + * @param {number} x + * @param {number} y + * @param {number} duration + * @returns {{ x: number, y: number, duration: number }} + */ + const _posObj = (x, y, duration) => ({ x: x, y: y, duration: duration }); + + delay(350); + Pather.moveTo(17563, 8072); + + let mephisto = Game.getMonster(sdk.monsters.Mephisto); + if (!mephisto) throw new Error("Mephisto not found."); + + delay(350); + [ + _posObj(17575, 8086, 350), _posObj(17584, 8091, 1200), + _posObj(17600, 8095, 550), _posObj(17610, 8094, 2500) + ].forEach(pos => Pather.moveTo(pos.x, pos.y) && delay(pos.duration)); + + Attack.clear(10); + Pather.moveTo(17610, 8094); + + const _lurePositions = [ + _posObj(17600, 8095, 150), _posObj(17584, 8091, 150), + _posObj(17575, 8086, 150), _posObj(17563, 8072, 350), + _posObj(17575, 8086, 350), _posObj(17584, 8091, 1200), + _posObj(17600, 8095, 550), _posObj(17610, 8094, 2500) + ]; + let count = 0; + let distance = getDistance(me, mephisto); + + while (distance > 27) { + count += 1; + + _lurePositions + .forEach(pos => Pather.moveTo(pos.x, pos.y) && delay(pos.duration)); + Attack.clear(10); + Pather.moveTo(17610, 8094); + + distance = getDistance(me, mephisto); + + if (count >= 5) { + throw new Error("Failed to lure Mephisto."); + } + } + + return true; + }; + + const killCouncil = function () { + const councilMembers = [sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3]; + const coords = [[17600, 8125], [17600, 8015], [17643, 8068]]; + + for (let [x, y] of coords) { + Pather.moveTo(x, y); + Attack.clearList(Attack.getMob(councilMembers, 0, 40)); + } + + return true; + }; + + Pather.useWaypoint(sdk.areas.DuranceofHateLvl2); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true)) { + throw new Error("Failed to move to Durance Level 3"); + } + + Config.Mephisto.KillCouncil && killCouncil(); + + if (Config.Mephisto.TakeRedPortal) { + Pather.moveTo(17590, 8068); + delay(400); // Activate the bridge tile + } else { + Pather.moveTo(17566, 8069); + } + + if (me.sorceress && Config.Mephisto.MoatTrick && Pather.canTeleport()) { + moat(); + Skill.usePvpRange = true; + Attack.kill(sdk.monsters.Mephisto); + Skill.usePvpRange = false; + } else { + Attack.kill(sdk.monsters.Mephisto); + } + + Pickit.pickItems(); + + if (Config.OpenChests.Enabled) { + Pather.moveTo(17572, 8011) && Attack.openChests(5); + Pather.moveTo(17572, 8125) && Attack.openChests(5); + Pather.moveTo(17515, 8061) && Attack.openChests(5); + } + + if (Config.Mephisto.TakeRedPortal) { + Pather.moveTo(17590, 8068); + let tick = getTickCount(), time = 0; + + // Wait until bridge is there + while (getCollision(me.area, 17601, 8070, 17590, 8068) !== 0 + && (time = getTickCount() - tick) < 2000) { + Pather.moveTo(17590, 8068); // Activate it + delay(3); + } + + // If bridge is there, and we can move to the location + if (time < 2000 && Pather.moveTo(17601, 8070)) { + Pather.usePortal(null); + } + } + + return true; + }, + { + startArea: sdk.areas.DuranceofHateLvl2, + bossid: sdk.monsters.Mephisto, + } +); diff --git a/d2bs/kolbot/libs/scripts/Nihlathak.js b/d2bs/kolbot/libs/scripts/Nihlathak.js new file mode 100644 index 000000000..c0a3e3a61 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Nihlathak.js @@ -0,0 +1,44 @@ +/** +* @filename Nihlathak.js +* @author kolton, theBGuy +* @desc kill Nihlathak +* +*/ + +const Nihlathak = new Runnable( + function Nihlathak () { + Town.goToTown(5); + + !Pather.initialized && Pather.useWaypoint(null, true); + + // UseWaypoint if set to or if we already have it + if (Config.Nihlathak.UseWaypoint || me.haveWaypoint(sdk.areas.HallsofPain)) { + Pather.useWaypoint(sdk.areas.HallsofPain); + } else { + if (Pather.journeyTo(sdk.areas.NihlathaksTemple)) { + Pather.moveToExit([sdk.areas.HallsofAnguish, sdk.areas.HallsofPain], true); + } + } + + Precast.doPrecast(false); + + if (!Pather.moveToExit(sdk.areas.HallsofVaught, true)) throw new Error("Failed to go to Nihlathak"); + + // faster detection of TombVipers + Pather.moveToPresetObject(me.area, sdk.objects.NihlathaksPlatform, { callback: () => { + if (Config.Nihlathak.ViperQuit && Game.getMonster(sdk.monsters.TombViper2)) { + console.log("Tomb Vipers found."); + throw new ScriptError("Tomb Vipers found."); + } + } }); + + Attack.kill(sdk.monsters.Nihlathak); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.Harrogath, + bossid: sdk.monsters.Nihlathak, + } +); diff --git a/d2bs/kolbot/libs/scripts/OrgTorch.js b/d2bs/kolbot/libs/scripts/OrgTorch.js new file mode 100644 index 000000000..0811a25b5 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/OrgTorch.js @@ -0,0 +1,597 @@ +/** +* @filename OrgTorch.js +* @author kolton, theBGuy +* @desc Convert keys to organs and organs to torches. It can work with TorchSystem to get keys from other characters +* @notes Search for the word "START" and follow the comments if you want to know what this script does and when. +* +*/ + +/** +* @todo: +* - override Town.buyPots, usually uber killers have only a little invo space so they fail to buy/drink all the pregame pots +* change method to buy/drink one pot at a time +* - add ability to team this, possible roles being: +* - taxi (just tele killer around) +* - helper (goes in tp and actuallys kills mob), maybe config for specifc areas like if we use salvation to kill meph +* but have a helper who comes in with max fanat or conviction +* - bo barb or war cry barb would make killing main boss easier with all the surrounding mobs being stunned +*/ + +const OrgTorch = new Runnable( + function OrgTorch () { + let currentGameInfo = null; + + const portalMode = { + MiniUbers: 0, + UberTristram: 1 + }; + + const OrgTorchData = { + filePath: "logs/OrgTorch-" + me.profile + ".json", + _default: { gamename: me.gamename, doneAreas: [] }, + + create: function () { + FileTools.writeText(this.filePath, JSON.stringify(this._default)); + return this._default; + }, + + read: function () { + let obj = {}; + try { + let string = FileTools.readText(this.filePath); + obj = JSON.parse(string); + } catch (e) { + return this._default; + } + + return obj; + }, + + update: function (newData) { + let data = this.read(); + Object.assign(data, newData); + FileTools.writeText(this.filePath, JSON.stringify(data)); + }, + + remove: function () { + return FileTools.remove(this.filePath); + } + }; + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + const getQuestItem = function (item) { + if (item) { + let id = item.classid; + let canFit = Storage.Inventory.CanFit(item); + if (!canFit && Pickit.canMakeRoom()) { + console.log("ÿc7Trying to make room for " + Item.color(item) + item.name); + Town.visitTown(); + !copyUnit(item).x && (item = Misc.poll(() => Game.getItem(id))); + } + } + return Pickit.pickItem(item); + }; + + // Identify & mule + const checkTorch = function () { + if (me.inArea(sdk.areas.UberTristram)) { + Pather.moveTo(25105, 5140); + Pather.usePortal(sdk.areas.Harrogath); + } + + Town.doChores(); + + if (!Config.OrgTorch.MakeTorch) return false; + + let torch = me.checkItem({ classid: sdk.items.LargeCharm, quality: sdk.items.quality.Unique }); + + if (torch.have && Pickit.checkItem(torch.item).result === Pickit.Result.WANTED) { + if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) { + scriptBroadcast("muleTorch"); + scriptBroadcast("quit"); + } + + return true; + } + + return false; + }; + + /** + * Try to lure a monster - wait until it's close enough + * @param {number} bossId + * @returns {boolean} + * @todo redo this + * - should, lure boss AWAY from the others and to us + * - create path to boss, move some -> wait to see if aggroed -> if yes - move back and make sure it follows until its safely away from other bosses + */ + const lure = function (bossId) { + let unit = Game.getMonster(bossId); + + if (unit) { + let tick = getTickCount(); + + while (getTickCount() - tick < 2000) { + if (unit.distance <= 10) { + return true; + } + + delay(50); + } + } + + return false; + }; + + /** + * Check if we have complete sets of organs + * @returns {boolean} + */ + const completeSetCheck = function () { + let [horns, brains, eyes] = [0, 0, 0]; + me.getItemsEx() + .filter(i => i.isInInventory && !Town.ignoreType(i.itemType) && i.quality === sdk.items.quality.Normal) + .forEach(i => { + switch (i.classid) { + case sdk.items.quest.DiablosHorn: + return (horns++); + case sdk.items.quest.MephistosBrain: + return (brains++); + case sdk.items.quest.BaalsEye: + return (eyes++); + default: return 0; + } + }); + + if (!horns || !brains || !eyes) { + return false; + } + + // We just need one set to make a torch + if (Config.OrgTorch.MakeTorch) { + return horns > 0 && brains > 0 && eyes > 0; + } + + return horns === brains && horns === eyes && brains === eyes; + }; + + /** + * Get fade in River of Flames - only works if we are wearing an item with ctc Fade + * @returns {boolean} + * @todo equipping an item from storage if we have it + */ + const getFade = function () { + if (Config.OrgTorch.GetFade && !me.getState(sdk.states.Fade) + && me.haveSome([ + { name: sdk.locale.items.Treachery, equipped: true }, + { name: sdk.locale.items.LastWish, equipped: true }, + { name: sdk.locale.items.SpiritWard, equipped: true } + ])) { + console.log(sdk.colors.Orange + "OrgTorch :: " + sdk.colors.White + "Getting Fade"); + // lets figure out what fade item we have before we leave town + let fadeItem = me.findFirst([ + { name: sdk.locale.items.Treachery, equipped: true }, + { name: sdk.locale.items.LastWish, equipped: true }, + { name: sdk.locale.items.SpiritWard, equipped: true } + ]); + + Pather.useWaypoint(sdk.areas.RiverofFlame); + Precast.doPrecast(true); + // check if item is on switch + let mainSlot; + + Pather.moveTo(7811, 5872); + + if (fadeItem.have && fadeItem.item.isOnSwap && me.weaponswitch !== sdk.player.slot.Secondary) { + mainSlot = me.weaponswitch; + me.switchWeapons(sdk.player.slot.Secondary); + } + + Skill.canUse(sdk.skills.Salvation) && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right); + + while (!me.getState(sdk.states.Fade)) { + delay(100); + } + + mainSlot !== undefined && me.weaponswitch !== mainSlot && me.switchWeapons(mainSlot); + + console.log(sdk.colors.Orange + "OrgTorch :: " + sdk.colors.Green + "Fade Achieved"); + } + + return true; + }; + + /** + * Open a red portal. Mode 0 = mini ubers, mode 1 = Tristram + * @param {number} mode + * @returns {ObjectUnit | false} + */ + const openPortal = function (mode) { + let item1 = mode === portalMode.MiniUbers + ? me.findItem("pk1", sdk.items.mode.inStorage) + : me.findItem("dhn", sdk.items.mode.inStorage); + let item2 = mode === portalMode.MiniUbers + ? me.findItem("pk2", sdk.items.mode.inStorage) + : me.findItem("bey", sdk.items.mode.inStorage); + let item3 = mode === portalMode.MiniUbers + ? me.findItem("pk3", sdk.items.mode.inStorage) + : me.findItem("mbr", sdk.items.mode.inStorage); + + Town.goToTown(5); + Town.doChores(); + + if (Town.openStash() && Cubing.emptyCube()) { + if (!Storage.Cube.MoveTo(item1) + || !Storage.Cube.MoveTo(item2) + || !Storage.Cube.MoveTo(item3) + || !Cubing.openCube()) { + return false; + } + + transmute(); + delay(1000); + + let portal = Game.getObject(sdk.objects.RedPortal); + + if (portal) { + do { + switch (mode) { + case portalMode.MiniUbers: + if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].includes(portal.objtype) + && currentGameInfo.doneAreas.indexOf(portal.objtype) === -1) { + return copyUnit(portal); + } + + break; + case portalMode.UberTristram: + if (portal.objtype === sdk.areas.UberTristram) { + return copyUnit(portal); + } + + break; + } + } while (portal.getNext()); + } + } + + return false; + }; + + const matronsDen = function () { + let dHorns = me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length; + + Precast.doPrecast(true); + Pather.moveToPreset(sdk.areas.MatronsDen, sdk.unittype.Object, sdk.objects.SmallSparklyChest, 2, 2); + Attack.kill(sdk.monsters.Lilith); + Pickit.pickItems(); + getQuestItem(Game.getItem(sdk.items.quest.DiablosHorn)); + Town.goToTown(); + + // we sucessfully picked up the horn + return (me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length > dHorns); + }; + + const forgottenSands = function () { + let bEyes = me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length; + + Precast.doPrecast(true); + + let nodes = [ + { x: 20196, y: 8694 }, + { x: 20308, y: 8588 }, + { x: 20187, y: 8639 }, + { x: 20100, y: 8550 }, + { x: 20103, y: 8688 }, + { x: 20144, y: 8709 }, + { x: 20263, y: 8811 }, + { x: 20247, y: 8665 }, + ]; + + try { + for (let i = 0; i < nodes.length; i++) { + Pather.moveTo(nodes[i].x, nodes[i].y); + delay(500); + + if (Game.getMonster(sdk.monsters.UberDuriel)) { + break; + } + + let eye = Game.getItem(sdk.items.quest.BaalsEye, sdk.items.mode.onGround); + + if (eye && Pickit.pickItem(eye)) { + throw new Error("Found an picked wanted organ"); + } + } + + Attack.kill(sdk.monsters.UberDuriel); + Pickit.pickItems(); + getQuestItem(Game.getItem(sdk.items.quest.BaalsEye)); + Town.goToTown(); + } catch (e) { + // + } + + // we sucessfully picked up the eye + return (me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length > bEyes); + }; + + const furnance = function () { + let mBrain = me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length; + + Precast.doPrecast(true); + Pather.moveToPreset(sdk.areas.FurnaceofPain, sdk.unittype.Object, sdk.objects.SmallSparklyChest, 2, 2); + Attack.kill(sdk.monsters.UberIzual); + Pickit.pickItems(); + getQuestItem(Game.getItem(sdk.items.quest.MephistosBrain)); + Town.goToTown(); + + // we sucessfully picked up the brain + return (me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length > mBrain); + }; + + /** + * @todo re-write this, lure doesn't always work and other classes can do ubers + */ + const uberTrist = function () { + let skillBackup; + let useSalvation = Config.OrgTorch.UseSalvation && Skill.canUse(sdk.skills.Salvation); + + Pather.moveTo(25068, 5078); + Precast.doPrecast(true); + + let nodes = [ + { x: 25040, y: 5101 }, + { x: 25040, y: 5166 }, + { x: 25122, y: 5170 }, + ]; + + for (let i = 0; i < nodes.length; i++) { + Pather.moveTo(nodes[i].x, nodes[i].y); + } + + useSalvation && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right); + lure(sdk.monsters.UberMephisto); + Pather.moveTo(25129, 5198); + useSalvation && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right); + lure(sdk.monsters.UberMephisto); + + if (!Game.getMonster(sdk.monsters.UberMephisto)) { + Pather.moveTo(25122, 5170); + } + + if (useSalvation) { + skillBackup = Config.AttackSkill[2]; + Config.AttackSkill[2] = sdk.skills.Salvation; + + Attack.init(); + } + + Attack.kill(sdk.monsters.UberMephisto); + + if (skillBackup && useSalvation) { + Config.AttackSkill[2] = skillBackup; + + Attack.init(); + } + + Pather.moveTo(25162, 5141); + delay(3250); + + if (!Game.getMonster(sdk.monsters.UberDiablo)) { + Pather.moveTo(25122, 5170); + } + + Attack.kill(sdk.monsters.UberDiablo); + + if (!Game.getMonster(sdk.monsters.UberBaal)) { + Pather.moveTo(25122, 5170); + } + + Attack.kill(sdk.monsters.UberBaal); + Pickit.pickItems(); + currentGameInfo.doneAreas.push(sdk.areas.UberTristram) && OrgTorchData.update(currentGameInfo); + checkTorch(); + }; + + /** + * Do mini ubers or Tristram based on area we're already in + * @param {number} portalId + */ + const pandemoniumRun = function (portalId) { + switch (me.area) { + case sdk.areas.MatronsDen: + if (matronsDen()) { + currentGameInfo.doneAreas.push(portalId) && OrgTorchData.update(currentGameInfo); + } + + break; + case sdk.areas.ForgottenSands: + if (forgottenSands()) { + currentGameInfo.doneAreas.push(portalId) && OrgTorchData.update(currentGameInfo); + } + + break; + case sdk.areas.FurnaceofPain: + if (furnance()) { + currentGameInfo.doneAreas.push(portalId) && OrgTorchData.update(currentGameInfo); + } + + break; + case sdk.areas.UberTristram: + uberTrist(); + + break; + } + }; + + /** + * @param {ObjectUnit} portal + */ + const runEvent = function (portal) { + if (portal) { + const { Antidote, Thawing } = Config.OrgTorch.PreGame; + if (Antidote.At.includes(portal.objtype) && Antidote.Drink > 0) { + Town.buyPots(Antidote.Drink, "Antidote", true, true); + } + if (Thawing.At.includes(portal.objtype) && Thawing.Drink > 0) { + Town.buyPots(Thawing.Drink, "Thawing", true, true); + } + Town.move("stash"); + console.log("taking portal: " + portal.objtype); + Pather.usePortal(null, null, portal); + pandemoniumRun(portal.objtype); + } + }; + + // ################# // + /* ##### START ##### */ + // ################# // + + // make sure we are picking the organs + Config.PickitFiles.length === 0 && NTIP.OpenFile("pickit/keyorg.nip", true); + + FileTools.exists(OrgTorchData.filePath) && (currentGameInfo = OrgTorchData.read()); + + if (!currentGameInfo || currentGameInfo.gamename !== me.gamename) { + currentGameInfo = OrgTorchData.create(); + } + + let portal; + let [tkeys, hkeys, dkeys] = [0, 0, 0]; + let [brains, eyes, horns] = [0, 0, 0]; + + me.getItemsEx() + .filter(i => i.isInStorage && !Town.ignoreType(i.itemType) && i.quality === sdk.items.quality.Normal) + .forEach(i => { + switch (i.classid) { + case sdk.items.quest.KeyofTerror: + return (tkeys++); + case sdk.items.quest.KeyofHate: + return (hkeys++); + case sdk.items.quest.KeyofDestruction: + return (dkeys++); + case sdk.items.quest.DiablosHorn: + return (horns++); + case sdk.items.quest.MephistosBrain: + return (brains++); + case sdk.items.quest.BaalsEye: + return (eyes++); + default: return 0; + } + }); + + // Do town chores and quit if MakeTorch is true and we have a torch. + checkTorch(); + + // Wait for other bots to drop off their keys. This works only if TorchSystem.js is configured properly. + Config.OrgTorch.WaitForKeys && TorchSystem.waitForKeys(); + + Town.goToTown(5); + Town.move("stash"); + + let redPortals = getUnits(sdk.unittype.Object, sdk.objects.RedPortal) + .filter(el => [ + sdk.areas.MatronsDen, sdk.areas.ForgottenSands, + sdk.areas.FurnaceofPain, sdk.areas.UberTristram + ].includes(el.objtype)); + let miniPortals = 0; + let keySetsReq = 3; + let tristOpen = false; + + if (redPortals.length > 0) { + redPortals.forEach(function (portal) { + if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].includes(portal.objtype)) { + miniPortals++; + keySetsReq--; + } else if (portal.objtype === sdk.areas.UberTristram) { + tristOpen = true; + } + }); + } else { + // possible same game name but different day and data file never got deleted + currentGameInfo.doneAreas.length > 0 && (currentGameInfo = OrgTorchData.create()); + } + + // End the script if we don't have enough keys nor organs + if ((tkeys < keySetsReq || hkeys < keySetsReq || dkeys < keySetsReq) + && (brains < 1 || eyes < 1 || horns < 1) && !tristOpen) { + console.log("Not enough keys or organs."); + OrgTorchData.remove(); + + return true; + } + + Config.UseMerc = false; + + // We have enough keys, do mini ubers + if (tkeys >= keySetsReq && hkeys >= keySetsReq && dkeys >= keySetsReq) { + getFade(); + Town.goToTown(5); + console.log("Making organs."); + D2Bot.printToConsole("OrgTorch: Making organs.", sdk.colors.D2Bot.DarkGold); + Town.move("stash"); + + // there are already open portals lets check our info on them + if (miniPortals > 0) { + for (let i = 0; i < miniPortals; i++) { + // mini-portal is up but its not in our done areas, probably chickend during it, lets try again + if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].includes(redPortals[i].objtype) + && !currentGameInfo.doneAreas.includes(redPortals[i].objtype)) { + portal = redPortals[i]; + runEvent(portal); + } + } + } + + for (let i = 0; i < keySetsReq; i += 1) { + // Abort if we have a complete set of organs + // If Config.OrgTorch.MakeTorch is false, check after at least one portal is made + if ((Config.OrgTorch.MakeTorch || i > 0) && completeSetCheck()) { + break; + } + + portal = openPortal(portalMode.MiniUbers); + runEvent(portal); + } + } + + // Don't make torches if not configured to OR if the char already has one + if (!Config.OrgTorch.MakeTorch || checkTorch()) { + OrgTorchData.remove(); + + return true; + } + + // Count organs + brains = me.findItems("mbr", sdk.items.mode.inStorage).length || 0; + eyes = me.findItems("bey", sdk.items.mode.inStorage).length || 0; + horns = me.findItems("dhn", sdk.items.mode.inStorage).length || 0; + + // We have enough organs, do Tristram - or trist is open we may have chickened and came back so check it + // if trist was already open when we joined should we run that first? + if ((brains && eyes && horns) || tristOpen) { + getFade(); + Town.goToTown(5); + Town.move("stash"); + + if (!tristOpen) { + console.log("Making torch"); + D2Bot.printToConsole("OrgTorch: Making torch.", sdk.colors.D2Bot.DarkGold); + portal = openPortal(portalMode.UberTristram); + } else { + portal = Pather.getPortal(sdk.areas.UberTristram); + } + + runEvent(portal); + OrgTorchData.remove(); + } + + return true; + }, + { + startArea: sdk.areas.Harrogath + } +); diff --git a/d2bs/kolbot/libs/scripts/OuterSteppes.js b/d2bs/kolbot/libs/scripts/OuterSteppes.js new file mode 100644 index 000000000..e29d55541 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/OuterSteppes.js @@ -0,0 +1,22 @@ +/** +* @filename OuterSteppes.js +* @author kolton +* @desc clear OuterSteppes +* +*/ + +const OuterSteppes = new Runnable( + function OuterSteppes() { + if (!Town.goToTown(4)) throw new Error("Failed to go to act 4"); + // force random precast because currently bugs if we precast as soon as we go from inTown to out of town + Precast.doRandomPrecast(true); + if (!Pather.journeyTo(sdk.areas.OuterSteppes)) throw new Error("Failed to move to Outer Steppes"); + + Attack.clearLevel(Config.ClearType); + + return true; + }, + { + startArea: sdk.areas.PandemoniumFortress + } +); diff --git a/d2bs/kolbot/libs/scripts/Pindleskin.js b/d2bs/kolbot/libs/scripts/Pindleskin.js new file mode 100644 index 000000000..147913f64 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Pindleskin.js @@ -0,0 +1,58 @@ +/** +* @filename Pindleskin.js +* @author kolton, theBGuy +* @desc kill Pindleskin and optionally Nihlathak +* +*/ + +const Pindleskin = new Runnable( + function Pindleskin () { + Town.goToTown((Config.Pindleskin.UseWaypoint ? undefined : 5)); + + if (!Attack.haveKilled(getLocaleString(sdk.locale.monsters.Pindleskin))) { + if (Config.Pindleskin.UseWaypoint) { + Pather.useWaypoint(sdk.areas.HallsofPain); + Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.HallsofAnguish, sdk.areas.NihlathaksTemple], true)) { + throw new Error("Failed to move to Nihlahak's Temple"); + } + } else { + if (!Pather.journeyTo(sdk.areas.NihlathaksTemple)) throw new Error("Failed to use portal."); + Precast.doPrecast(true); + } + + Pather.moveTo(10058, 13234); + + try { + Attack.kill(getLocaleString(sdk.locale.monsters.Pindleskin)); + } catch (e) { + console.error(e); + } + } + + if (Config.Pindleskin.KillNihlathak) { + if (!Pather.moveToExit([sdk.areas.HallsofAnguish, sdk.areas.HallsofPain, sdk.areas.HallsofVaught], true)) { + throw new Error("Failed to move to Halls of Vaught"); + } + + // faster detection of TombVipers + Pather.moveToPresetObject(me.area, sdk.objects.NihlathaksPlatform, { offX: 10, offY: 10, callback: () => { + if (Config.Pindleskin.ViperQuit && Game.getMonster(sdk.monsters.TombViper2)) { + console.log("Tomb Vipers found."); + throw new ScriptError("Tomb Vipers found."); + } + } }); + + Config.Pindleskin.ClearVipers && Attack.clearList(Attack.getMob(sdk.monsters.TombViper2, 0, 20)); + + Attack.kill(sdk.monsters.Nihlathak); + Pickit.pickItems(); + } + + return true; + }, + { + startArea: sdk.areas.Harrogath + } +); diff --git a/d2bs/kolbot/libs/scripts/Pit.js b/d2bs/kolbot/libs/scripts/Pit.js new file mode 100644 index 000000000..1140b8219 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Pit.js @@ -0,0 +1,28 @@ +/** +* @filename Pit.js +* @author kolton +* @desc clear Pit +* +*/ + +const Pit = new Runnable( + function Pit () { + Pather.useWaypoint(sdk.areas.BlackMarsh); + Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.TamoeHighland, sdk.areas.PitLvl1], true)) { + throw new Error("Failed to move to Pit level 1"); + } + Config.Pit.ClearPit1 && Attack.clearLevel(Config.ClearType); + + if (!Pather.moveToExit(sdk.areas.PitLvl2, true, Config.Pit.ClearPath)) { + throw new Error("Failed to move to Pit level 2"); + } + Attack.clearLevel(); + + return true; + }, + { + startArea: sdk.areas.BlackMarsh + } +); diff --git a/d2bs/kolbot/libs/scripts/Questing.js b/d2bs/kolbot/libs/scripts/Questing.js new file mode 100644 index 000000000..553ae9a6d --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Questing.js @@ -0,0 +1,420 @@ +/** +* @filename Questing.js +* @author kolton, theBGuy +* @desc Do simple quests, the ones that don't have a lot of pre-reqs for now +* +*/ + +// @notes: can't do duriel or meph because all the extra tasks. this is not meant to be autoplay or self rush + +const Questing = new Runnable( + function Questing () { + const log = (msg = "", errorMsg = false) => { + me.overhead(msg); + console.log("ÿc9(Questing) :: " + (errorMsg ? "ÿc1" : "ÿc0") + msg); + }; + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + const getQuestItem = (item) => { + if (item) { + let id = item.classid; + let canFit = Storage.Inventory.CanFit(item); + if (!canFit && Pickit.canMakeRoom()) { + console.log("ÿc7Trying to make room for " + Item.color(item) + item.name); + Town.visitTown(); + !copyUnit(item).x && (item = Misc.poll(() => Game.getItem(id))); + } + } + return Pickit.pickItem(item); + }; + + const den = function () { + log("starting den"); + + Town.doChores(); + if (!Pather.journeyTo(sdk.areas.DenofEvil)) throw new Error("den failed"); + Precast.doPrecast(true); + Attack.clearLevel(); + Town.goToTown() && Town.npcInteract("Akara"); + }; + + const smith = function () { + log("starting smith"); + include("core/Common/Smith.js"); + Common.Smith(); + }; + + const cain = function () { + include("core/Common/Cain.js"); + log("starting cain"); + + Town.doChores(); + Common.Cain.run(); + }; + + const andy = function () { + log("starting andy"); + + Town.doChores(); + if (!Pather.journeyTo(sdk.areas.CatacombsLvl4)) throw new Error("andy failed"); + Pather.moveTo(22582, 9612); + + let coords = [ + { x: 22572, y: 9635 }, { x: 22554, y: 9618 }, + { x: 22542, y: 9600 }, { x: 22572, y: 9582 }, + { x: 22554, y: 9566 } + ]; + + if (Pather.useTeleport()) { + Pather.moveTo(22571, 9590); + } else { + while (coords.length) { + let andy = Game.getMonster(sdk.monsters.Andariel); + + if (andy && andy.distance < 15) { + break; + } + + Pather.moveToUnit(coords[0]); + Attack.clearClassids(sdk.monsters.DarkShaman1); + coords.shift(); + } + } + + Attack.kill(sdk.monsters.Andariel); + Town.goToTown(); + Town.npcInteract("Warriv", false); + Misc.useMenu(sdk.menu.GoEast); + }; + + const radament = function () { + log("starting radament"); + + if (!Pather.journeyTo(sdk.areas.A2SewersLvl3)) { + throw new Error("radament failed"); + } + + Precast.doPrecast(true); + + if (!Pather.moveToPreset(sdk.areas.A2SewersLvl3, sdk.unittype.Object, sdk.quest.chest.HoradricScrollChest)) { + throw new Error("radament failed"); + } + + Attack.kill(sdk.monsters.Radament); + + let book = Game.getItem(sdk.quest.item.BookofSkill); + getQuestItem(book); + + Town.goToTown(); + Town.npcInteract("Atma"); + }; + + const lamEssen = function () { + log("starting lam essen"); + + if (!Pather.journeyTo(sdk.areas.RuinedTemple)) { + throw new Error("Lam Essen quest failed"); + } + + Precast.doPrecast(true); + + if (!Pather.moveToPreset(sdk.areas.RuinedTemple, sdk.unittype.Object, sdk.quest.chest.LamEsensTomeHolder)) { + throw new Error("Lam Essen quest failed"); + } + + Misc.openChest(sdk.quest.chest.LamEsensTomeHolder); + let book = Misc.poll(() => Game.getItem(sdk.quest.item.LamEsensTome), 1000, 100); + getQuestItem(book); + Town.goToTown(); + Town.npcInteract("Alkor"); + }; + + const izual = function () { + log("starting izual"); + if (!Loader.runScript("Izual")) throw new Error("izual failed"); + Town.goToTown(); + Town.npcInteract("Tyrael"); + }; + + const diablo = function () { + log("starting diablo"); + if (!Loader.runScript("Diablo")) throw new Error(); + Town.goToTown(4); + + Game.getObject(sdk.objects.RedPortalToAct5) + ? Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct5, sdk.areas.Harrogath) + : Town.npcInteract("Tyrael", false) && Misc.useMenu(sdk.menu.TravelToHarrogath); + }; + + const shenk = function () { + log("starting shenk"); + + if (!Pather.useWaypoint(sdk.areas.FrigidHighlands, true)) { + throw new Error("shenk failed"); + } + + Precast.doPrecast(true); + Pather.moveTo(3883, 5113); + Attack.kill(getLocaleString(sdk.locale.monsters.ShenktheOverseer)); + Town.goToTown(); + Town.npcInteract("Larzuk"); + }; + + const barbs = function () { + log("starting barb rescue"); + + if (!Pather.useWaypoint(sdk.areas.FrigidHighlands, true)) { + throw new Error("barbs failed"); + } + Precast.doPrecast(true); + + let barbs = (Game.getPresetObjects(me.area, sdk.quest.chest.BarbCage) || []); + if (!barbs.length) throw new Error("Couldn't find the barbs"); + + let coords = []; + + // Dark-f: x-3 + for (let cage = 0; cage < barbs.length; cage += 1) { + coords.push({ + x: barbs[cage].roomx * 5 + barbs[cage].x - 3, + y: barbs[cage].roomy * 5 + barbs[cage].y + }); + } + + for (let i = 0; i < coords.length; i += 1) { + log((i + 1) + "/" + coords.length); + Pather.moveToUnit(coords[i], 2, 0); + let door = Game.getMonster(sdk.monsters.PrisonDoor); + + if (door) { + Pather.moveToUnit(door, 1, 0); + Attack.kill(door); + } + + delay(1500 + me.ping); + } + + Town.npcInteract("qual_kehk"); + }; + + const anya = function () { + log("starting anya"); + + if (!Misc.checkQuest(sdk.quest.id.PrisonofIce, 8/** Recieved the scroll */)) { + if (!Pather.journeyTo(sdk.areas.FrozenRiver)) { + throw new Error("anya failed"); + } + + Precast.doPrecast(true); + + if (!Pather.moveToPreset(sdk.areas.FrozenRiver, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform)) { + throw new Error("Anya quest failed"); + } + + delay(1000); + + let frozenanya = Game.getObject(sdk.objects.FrozenAnya); + + /** + * Here we have issues sometimes + * Including a check for her unfreezing in case we already have malah's potion + * @todo + * - tele char can lure frozenstein away from anya as he can be hard to kill + * aggro the pack then move back until there isn't any monster around anya (note) we can only detect mobs around 40 yards of us + * then should use a static location behind anya as our destination to tele to + */ + if (frozenanya) { + if (me.sorceress && Skill.haveTK) { + Attack.getIntoPosition(frozenanya, 15, sdk.collision.LineOfSight, Pather.canTeleport(), true); + Packet.telekinesis(frozenanya); + } else { + Pather.moveToUnit(frozenanya); + Packet.entityInteract(frozenanya); + } + Misc.poll(() => getIsTalkingNPC() || frozenanya.mode, 2000, 50); + me.cancel() && me.cancel(); + } + + Town.npcInteract("malah"); + + /** + * Now this should prevent us from re-entering if we either failed to interact with anya in the first place + * or if we had malah's potion because this is our second attempt and we managed to unfreeze her + */ + if (me.getItem(sdk.quest.item.MalahsPotion)) { + console.log("Got potion, lets go unfreeze anya"); + + if (!Misc.poll(() => { + Pather.usePortal(sdk.areas.FrozenRiver, me.name); + return me.inArea(sdk.areas.FrozenRiver); + }, Time.seconds(30), 1000)) throw new Error("Anya quest failed - Failed to return to frozen river"); + + frozenanya = Game.getObject(sdk.objects.FrozenAnya); // Check again in case she's no longer there from first intereaction + + if (frozenanya) { + for (let i = 0; i < 3; i++) { + frozenanya.distance > 5 && Pather.moveToUnit(frozenanya, 1, 2); + Packet.entityInteract(frozenanya); + if (Misc.poll(() => frozenanya.mode, Time.seconds(2), 50)) { + me.cancel() && me.cancel(); + break; + } + if (getIsTalkingNPC()) { + // in case we failed to interact the first time this prevent us from crashing if her dialog is going + me.cancel() && me.cancel(); + } + } + } + } + } + + /** + * Now lets handle completing the quest as we have freed anya + */ + if (Misc.checkQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.ReqComplete)) { + /** + * Here we haven't talked to malah to recieve the scroll yet so lets do that + */ + if (!Misc.checkQuest(sdk.quest.id.PrisonofIce, 8/** Recieved the scroll */)) { + Town.npcInteract("malah"); + } + + /** + * Here we haven't talked to anya to open the red portal + */ + if (!Misc.checkQuest(sdk.quest.id.PrisonofIce, 9/** Talk to anya in town */)) { + Town.npcInteract("anya"); + } + + /** Handles using the scroll, no need to repeat the same code here */ + let scroll = me.scrollofresistance; + !!scroll && scroll.use(); + } + }; + + // @theBGuy + const ancients = function () { + include("core/Common/Ancients.js"); + log("starting ancients"); + Town.doChores(); + + if (!Pather.journeyTo(sdk.areas.ArreatSummit)) throw new Error("ancients failed"); + + // ancients prep + Town.doChores(); + [sdk.items.StaminaPotion, sdk.items.AntidotePotion, sdk.items.ThawingPotion] + .forEach(p => Town.buyPots(10, p, true)); + + let tempConfig = copyObj(Config); // save and update config settings + let townChicken = getScript("threads/townchicken.js"); + townChicken && townChicken.running && townChicken.stop(); + + Config.TownCheck = false; + Config.MercWatch = false; + Config.TownHP = 0; + Config.TownMP = 0; + Config.HPBuffer = 15; + Config.MPBuffer = 15; + Config.LifeChicken = 10; + + log("updated settings"); + + Town.buyPotions(); + // re-enter Arreat Summit + if (!Pather.usePortal(sdk.areas.ArreatSummit, me.name)) { + log("Failed to take portal back to Arreat Summit", true); + Pather.journeyTo(sdk.areas.ArreatSummit); + } + + Precast.doPrecast(true); + + // move to altar + if (!Pather.moveToPreset(sdk.areas.ArreatSummit, sdk.unittype.Object, sdk.quest.chest.AncientsAltar)) { + log("Failed to move to ancients' altar", true); + } + + Common.Ancients.touchAltar(); + Common.Ancients.startAncients(true); + + me.cancel(); + Config = tempConfig; + log("restored settings"); + Precast.doPrecast(true); + + // reload town chicken in case we are doing others scripts after this one finishes + let townChick = getScript("threads/TownChicken.js"); + if ((Config.TownHP > 0 || Config.TownMP > 0) && (townChick && !townChick.running || !townChick)) { + load("threads/TownChicken.js"); + } + + try { + if (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed)) { + Pather.moveToExit([sdk.areas.WorldstoneLvl1, sdk.areas.WorldstoneLvl2], true); + Pather.getWP(sdk.areas.WorldstoneLvl2); + } + } catch (err) { + log("Cleared Ancients. Failed to get WSK Waypoint", true); + } + }; + + const baal = function () { + log("starting baal"); + // just run baal script? I mean why re-invent the wheel here + Loader.runScript("Baal"); + Town.goToTown(5); + }; + + const tasks = (function () { + /** + * @constructor + * @param {function(): void} task + * @param {() => boolean} preReq + * @param {() => boolean} complete + */ + function Task (task, preReq, complete) { + this.run = task; + this.preReq = (preReq || (() => true)); + this.complete = (complete || (() => false)); + } + return [ + new Task(den, () => true, () => me.den), + new Task(smith, () => me.charlvl > 9, () => me.smith || me.imbue), + new Task(cain, () => true, () => me.cain), + new Task(andy, () => true, () => me.andariel), + new Task(radament, () => me.accessToAct(2), () => me.radament), + new Task(lamEssen, () => me.accessToAct(3), () => me.lamessen), + new Task(izual, () => me.accessToAct(4), () => me.izual), + new Task(diablo, () => me.accessToAct(4), () => me.diablo), + new Task(shenk, () => me.accessToAct(5), () => me.shenk || me.larzuk), + new Task(barbs, () => me.accessToAct(5), () => me.barbrescue), + new Task(anya, () => me.accessToAct(5), () => me.anya), + new Task(ancients, () => me.accessToAct(5) && me.charlvl > [20, 40, 60][me.diff], () => me.ancients), + new Task(baal, () => me.accessToAct(5) && me.ancients, () => me.baal), + ]; + })(); + + !me.inTown && Town.doChores(); + + for (let task of tasks) { + if (task.preReq() && !task.complete()) { + try { + task.run(); + } catch (e) { + console.error(e); + } + } + } + + if (Config.Questing.StopProfile || Loader.scriptList.length === 1) { + D2Bot.printToConsole("All quests done. Stopping profile.", sdk.colors.D2Bot.Green); + D2Bot.stop(); + } else { + log("ÿc9(Questing) :: ÿc2Complete"); + } + + return true; + } +); diff --git a/d2bs/kolbot/libs/scripts/Radament.js b/d2bs/kolbot/libs/scripts/Radament.js new file mode 100644 index 000000000..987330b86 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Radament.js @@ -0,0 +1,28 @@ +/** +* @filename Radament.js +* @author kolton +* @desc kill Radament +* +*/ + +const Radament = new Runnable( + function Radament () { + Pather.useWaypoint(sdk.areas.A2SewersLvl2); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.A2SewersLvl3, true) + || !Pather.moveToPresetObject(me.area, sdk.quest.chest.HoradricScrollChest)) { + throw new Error("Failed to move to Radament"); + } + + Attack.kill(sdk.monsters.Radament); + Pickit.pickItems(); + Attack.openChests(20); + + return true; + }, + { + startArea: sdk.areas.A2SewersLvl2, + bossid: sdk.monsters.Radament, + } +); diff --git a/d2bs/kolbot/libs/scripts/Rakanishu.js b/d2bs/kolbot/libs/scripts/Rakanishu.js new file mode 100644 index 000000000..36203d964 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rakanishu.js @@ -0,0 +1,34 @@ +/** +* @filename Rakanishu.js +* @author kolton, theBGuy +* @desc kill Rakanishu and optionally Griswold +* +*/ + +const Rakanishu = new Runnable( + function Rakanishu () { + Pather.useWaypoint(sdk.areas.StonyField); + Precast.doPrecast(true); + + if (!Attack.haveKilled(getLocaleString(sdk.locale.monsters.Rakanishu))) { + if (!Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 0, 0, false, true)) { + throw new Error("Failed to move to Rakanishu"); + } + Attack.kill(getLocaleString(sdk.locale.monsters.Rakanishu)); + } + + if (Config.Rakanishu.KillGriswold + && !Attack.haveKilled(sdk.monsters.Griswold) + && Pather.getPortal(sdk.areas.Tristram)) { + if (!Pather.usePortal(sdk.areas.Tristram)) throw new Error("Failed to move to Tristram"); + + Pather.moveTo(25149, 5180); + Attack.clear(20, 0xF, sdk.monsters.Griswold); + } + + return true; + }, + { + startArea: sdk.areas.StonyField + } +); diff --git a/d2bs/kolbot/libs/scripts/Rushee.js b/d2bs/kolbot/libs/scripts/Rushee.js new file mode 100644 index 000000000..cf371e53c --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rushee.js @@ -0,0 +1,1081 @@ +/* eslint-disable max-len */ +/** +* @filename Rushee.js +* @author kolton, theBGuy +* @desc Rushee script that works with Rusher +* +*/ + +let Overrides = require("../modules/Override"); + +new Overrides.Override(Town, Town.goToTown, function(orignal, act, wpmenu) { + try { + orignal(act, wpmenu); + + return true; + } catch (e) { + print(e); + + return Pather.useWaypoint(sdk.areas.townOf(me.area)); + } +}).apply(); + +new Overrides.Override(Pather, Pather.getWP, function(orignal, area, clearPath) { + if (area !== me.area) return false; + + for (let i = 0; i < sdk.waypoints.Ids.length; i++) { + let preset = Game.getPresetObject(me.area, sdk.waypoints.Ids[i]); + + if (preset) { + let x = (preset.roomx * 5 + preset.x); + let y = (preset.roomy * 5 + preset.y); + if (!me.inTown && [x, y].distance > 15) return false; + + Skill.haveTK + ? this.moveNearUnit(preset, 20, { clearSettings: { clearPath: clearPath } }) + : this.moveToUnit(preset, 0, 0, clearPath); + + let wp = Game.getObject("waypoint"); + + if (wp) { + for (let j = 0; j < 10; j++) { + if (!getUIFlag(sdk.uiflags.Waypoint)) { + if (wp.distance > 5 && Skill.useTK(wp) && j < 3) { + wp.distance > 21 && Attack.getIntoPosition(wp, 20, sdk.collision.Ranged); + Packet.telekinesis(wp); + } else if (wp.distance > 5 || !getUIFlag(sdk.uiflags.Waypoint)) { + this.moveToUnit(wp) && Misc.click(0, 0, wp); + } + } + + if (Misc.poll(() => me.gameReady && getUIFlag(sdk.uiflags.Waypoint), 1000, 150)) { + delay(500); + me.cancelUIFlags(); + + return true; + } + + // handle getUnit bug + if (!getUIFlag(sdk.uiflags.Waypoint) && me.inTown && wp.name.toLowerCase() === "dummy") { + Town.getDistance("waypoint") > 5 && Town.move("waypoint"); + Misc.click(0, 0, wp); + } + + delay(500); + } + } + } + } + + return false; +}).apply(); + +function Rushee() { + let act, leader, target, done = false; + let actions = []; + + this.log = function (msg = "", sayMsg = false) { + print(msg); + sayMsg && say(msg); + }; + + this.useScrollOfRes = function () { + let scroll = me.scrollofresistance; + if (scroll) { + clickItem(sdk.clicktypes.click.item.Right, scroll); + print("Using scroll of resistance"); + } + }; + + this.revive = function () { + while (me.mode === sdk.player.mode.Death) { + delay(40); + } + + if (me.mode === sdk.player.mode.Dead) { + me.revive(); + + while (!me.inTown) { + delay(40); + } + } + }; + + // todo - map the chest to classid so we only need to pass in one value + this.getQuestItem = function (classid, chestid) { + let tick = getTickCount(); + + if (me.getItem(classid)) { + this.log("Already have: " + classid); + return true; + } + + if (me.inTown) return false; + + let chest = Game.getObject(chestid); + + if (!chest) { + this.log("Couldn't find: " + chestid); + return false; + } + + for (let i = 0; i < 5; i++) { + if (Misc.openChest(chest)) { + break; + } + this.log("Failed to open chest: Attempt[" + (i + 1) + "]"); + let coord = CollMap.getRandCoordinate(chest.x, -4, 4, chest.y, -4, 4); + coord && Pather.moveTo(coord.x, coord.y); + } + + let item = Game.getItem(classid); + + if (!item) { + if (getTickCount() - tick < 500) { + delay(500); + } + + return false; + } + + return Pickit.pickItem(item) && delay(1000); + }; + + this.checkQuestMonster = function (classid) { + let monster = Game.getMonster(classid); + + if (monster) { + while (!monster.dead) { + delay(500); + } + + return true; + } + + return false; + }; + + this.tyraelTalk = function () { + let npc = Game.getNPC(NPC.Tyrael); + + if (!npc) return false; + + for (let i = 0; i < 3; i += 1) { + npc.distance > 3 && Pather.moveToUnit(npc); + npc.interact(); + delay(1000 + me.ping); + me.cancel(); + + if (Pather.getPortal(null)) { + me.cancel(); + + break; + } + } + + return Pather.usePortal(null) || Pather.usePortal(null, Config.Leader); + }; + + this.cubeStaff = function () { + let shaft = me.shaft; + let amulet = me.amulet; + + if (!shaft || !amulet) return false; + + Storage.Cube.MoveTo(amulet); + Storage.Cube.MoveTo(shaft); + Cubing.openCube(); + print("making staff"); + transmute(); + delay(750 + me.ping); + + let staff = me.completestaff; + + if (!staff) return false; + + Storage.Inventory.MoveTo(staff); + me.cancel(); + + return true; + }; + + this.placeStaff = function () { + let tick = getTickCount(); + let orifice = Game.getObject(sdk.quest.chest.HoradricStaffHolder); + if (!orifice) return false; + + Misc.openChest(orifice); + + let staff = me.completestaff; + + if (!staff) { + if (getTickCount() - tick < 500) { + delay(500); + } + + return false; + } + + staff.toCursor(); + submitItem(); + delay(750 + me.ping); + + // unbug cursor + let item = me.findItem(-1, sdk.items.mode.inStorage, sdk.storage.Inventory); + + if (item && item.toCursor()) { + Storage.Inventory.MoveTo(item); + } + + return true; + }; + + this.changeAct = function (act) { + let preArea = me.area; + + if (me.mode === sdk.player.mode.Dead) { + me.revive(); + + while (!me.inTown) { + delay(500); + } + } + + if (me.act === act || me.act > act) return true; + + try { + switch (act) { + case 2: + if (!Town.npcInteract("Warriv", false)) return false; + Misc.useMenu(sdk.menu.GoEast); + + break; + case 3: + // Non Quester needs to talk to Townsfolk to enable Harem TP + if (!Config.Rushee.Quester) { + // Talk to Atma + if (!Town.npcInteract("Atma")) { + break; + } + } + + Pather.usePortal(sdk.areas.HaremLvl1, Config.Leader); + Pather.moveToExit(sdk.areas.LutGholein, true); + + if (!Town.npcInteract("Jerhyn")) { + Pather.moveTo(5166, 5206); + + return false; + } + + me.cancel(); + Pather.moveToExit(sdk.areas.HaremLvl1, true); + Pather.usePortal(sdk.areas.LutGholein, Config.Leader); + + if (!Town.npcInteract("Meshif", false)) return false; + Misc.useMenu(sdk.menu.SailEast); + + break; + case 4: + if (me.inTown) { + Town.npcInteract("Cain"); + Pather.usePortal(sdk.areas.DuranceofHateLvl3, Config.Leader); + } else { + delay(1500); + } + + Pather.moveTo(17591, 8070); + Pather.usePortal(null); + + break; + case 5: + Town.npcInteract("Tyrael", false); + delay(me.ping + 1); + + if (Game.getObject(sdk.objects.RedPortalToAct5)) { + me.cancel(); + Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct5, sdk.areas.Harrogath); + } else { + Misc.useMenu(sdk.menu.TravelToHarrogath); + } + + break; + } + + delay(1000 + me.ping * 2); + + while (!me.area) { + delay(500); + } + + if (me.area === preArea) { + me.cancel(); + Town.move("portalspot"); + this.log("Act change failed.", Config.LocalChat.Enabled); + + return false; + } + + this.log("Act change done.", Config.LocalChat.Enabled); + } catch (e) { + return false; + } + + return true; + }; + + this.getQuestInfo = function (id) { + // note: end bosses double printed to match able to go to act flag + let quests = [ + ["cain", sdk.quest.id.TheSearchForCain], + ["andariel", sdk.quest.id.SistersToTheSlaughter], + ["andariel", sdk.quest.id.AbleToGotoActII], + ["radament", sdk.quest.id.RadamentsLair], + ["cube", sdk.quest.id.TheHoradricStaff], + ["amulet", sdk.quest.id.TheTaintedSun], + ["summoner", sdk.quest.id.TheArcaneSanctuary], + ["duriel", sdk.quest.id.TheSevenTombs], + ["duriel", sdk.quest.id.AbleToGotoActIII], + ["lamesen", sdk.quest.id.LamEsensTome], + ["travincal", sdk.quest.id.TheBlackenedTemple], + ["mephisto", sdk.quest.id.TheGuardian], + ["mephisto", sdk.quest.id.AbleToGotoActIV], + ["izual", sdk.quest.id.TheFallenAngel], + ["diablo", sdk.quest.id.TerrorsEnd], + ["diablo", sdk.quest.id.AbleToGotoActV], + ["shenk", sdk.quest.id.SiegeOnHarrogath], + ["anya", sdk.quest.id.PrisonofIce], + ["ancients", sdk.quest.id.RiteofPassage], + ["baal", sdk.quest.id.EyeofDestruction] + ]; + + let quest = quests.find(element => element[1] === id); + + return (!!quest ? quest[0] : ""); + }; + + this.nonQuesterNPCTalk = false; + + addEventListener("chatmsg", + function (who, msg) { + if (who === Config.Leader) { + actions.push(msg); + } + }); + + // START + Town.goToTown(me.highestAct); + me.inTown && Town.move("portalspot"); + + // if we can't find our leader after 5 minutes, I'm thinking they aren't showing up. Lets not wait around forever + leader = Misc.poll(() => Misc.findPlayer(Config.Leader), Time.minutes(5), 1000); + if (!leader) throw new Error("Failed to find my rusher"); + + Config.Rushee.Quester + ? this.log("Leader found", Config.LocalChat.Enabled) + : console.log("Leader Found: " + Config.Leader); + + // lets figure out if we either are the bumper or have a bumper so we know if we need to stop at the end of the rush + let bumperLevelReq = [20, 40, 60][me.diff]; + // ensure we are the right level to go to next difficulty if not on classic + let nextGame = (Config.Rushee.Bumper && (me.classic || me.charlvl >= bumperLevelReq)); + if (!nextGame) { + // we aren't the bumper, lets figure out if anyone else is a bumper + // hell is the end of a rush so always end profile after + if (Misc.getPlayerCount() > 2 && !me.hell) { + // there is more than just us and the rusher in game - so check party level + nextGame = Misc.checkPartyLevel(bumperLevelReq, leader.name); + } + } + console.debug("Is this our last run? " + (nextGame ? "No" : "Yes")); + + while (true) { + // todo - clean all this up so there is clear distinction between quester/non-quester and no repeat sequnces + try { + if (actions.length > 0) { + switch (actions[0]) { + case "all in": + switch (leader.area) { + case sdk.areas.A2SewersLvl3: + // Pick Book of Skill, use Book of Skill + Town.move("portalspot"); + Pather.usePortal(sdk.areas.A2SewersLvl3, Config.Leader); + delay(500); + + while (true) { + target = Game.getItem(sdk.quest.item.BookofSkill); + + if (!target) { + break; + } + + Pickit.pickItem(target); + delay(250); + target = me.getItem(sdk.quest.item.BookofSkill); + + if (target) { + print("Using book of skill"); + clickItem(sdk.clicktypes.click.item.Right, target); + + break; + } + } + + Pather.usePortal(sdk.areas.LutGholein, Config.Leader); + actions.shift(); + + break; + } + + actions.shift(); + + break; + case "questinfo": + if (!Config.Rushee.Quester) { + actions.shift(); + + break; + } + + say("highestquest " + this.getQuestInfo(me.highestQuestDone)); + actions.shift(); + + break; + case "wpinfo": + if (!Config.Rushee.Quester) { + actions.shift(); + + break; + } + + // go activate wp if we don't know our wps yet + !getWaypoint(1) && Pather.getWP(me.area); + + let myWps = Pather.nonTownWpAreas.slice(0).filter(function (area) { + if (area === sdk.areas.HallsofPain) return false; + if (me.classic && area >= sdk.areas.Harrogath) return false; + if (getWaypoint(Pather.wpAreas.indexOf(area))) return false; + return true; + }); + + say("mywps " + JSON.stringify(myWps)); + actions.shift(); + + break; + case "wp": + if (!me.inTown && !Town.goToTown()) { + this.log("I can't get to town :(", Config.LocalChat.Enabled); + + break; + } + + act = Misc.getPlayerAct(leader); + + if (me.act !== act) { + Town.goToTown(act); + Town.move("portalspot"); + } + + // make sure we talk to cain to access durance + leader.area === sdk.areas.DuranceofHateLvl2 && (!Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)) && Town.npcInteract("Cain"); + + // we aren't the quester but need to talk to npcs in order to be able to get wps from certain areas + (!Config.Rushee.Quester && !this.nonQuesterNPCTalk) && (this.nonQuesterNPCTalk = true); + + Town.getDistance("portalspot") > 10 && Town.move("portalspot"); + if (Pather.usePortal(null, Config.Leader) && Pather.getWP(me.area) && Pather.usePortal(sdk.areas.townOf(me.area), Config.Leader) && Town.move("portalspot")) { + me.inTown && Config.LocalChat.Enabled && say("gotwp"); + } else { + // check for bugged portal + let p = Game.getObject("portal"); + let preArea = me.area; + if (!!p && Misc.click(0, 0, p) && Misc.poll(() => me.area !== preArea, 1000, 100) + && Pather.getWP(me.area) && (Pather.usePortal(sdk.areas.townOf(me.area), Config.Leader) || Pather.useWaypoint(sdk.areas.townOf(me.area)))) { + me.inTown && Config.LocalChat.Enabled && say("gotwp"); + } else { + this.log("Failed to get wp", Config.LocalChat.Enabled); + !me.inTown && Town.goToTown(); + } + } + + actions.shift(); + + break; + case "1": + while (!leader.area) { + delay(500); + } + + act = Misc.getPlayerAct(leader); + + if (me.act !== act) { + Town.goToTown(act); + Town.move("portalspot"); + } + + // we need to talk to certain npcs in order to be able to grab waypoints as a non-quester + if (this.nonQuesterNPCTalk) { + console.debug("Leader Area: " + getAreaName(leader.area)); + + switch (leader.area) { + case sdk.areas.ClawViperTempleLvl2: + Misc.poll(() => !!(Misc.checkQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.ReqComplete) || Misc.checkQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.PartyMemberComplete), Time.seconds(20), 1000)); + if (Town.npcInteract("Drognan")) { + actions.shift(); + console.debug("drognan done"); + } + + break; + case sdk.areas.ArcaneSanctuary: + Misc.poll(() => !!(Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.ReqComplete) || Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.PartyMemberComplete), Time.seconds(20), 1000)); + if (Town.npcInteract("Atma")) { + actions.shift(); + console.debug("atma done"); + } + + break; + case sdk.areas.Travincal: + Misc.poll(() => !!(Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, 4) || Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.PartyMemberComplete) || Misc.checkQuest(sdk.quest.id.TheGuardian, 8), Time.seconds(20), 1000)); + if (Town.npcInteract("Cain")) { + actions.shift(); + console.debug("cain done"); + } + + break; + case sdk.areas.ArreatSummit: + Misc.poll(() => (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.ReqComplete) || Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.PartyMemberComplete), Time.seconds(20), 1000)); + if (Town.npcInteract("Malah")) { + actions.shift(); + console.debug("malah done"); + } + + break; + } + + me.inTown && Town.move("portalspot"); + } + + if (!Config.Rushee.Quester) { + actions.shift(); + + break; + } + + switch (leader.area) { + case sdk.areas.StonyField: + if (!Pather.usePortal(sdk.areas.StonyField, Config.Leader)) { + this.log("Failed to us portal to stony field", Config.LocalChat.Enabled); + break; + } + + let stones = [ + Game.getObject(sdk.quest.chest.StoneAlpha), + Game.getObject(sdk.quest.chest.StoneBeta), + Game.getObject(sdk.quest.chest.StoneGamma), + Game.getObject(sdk.quest.chest.StoneDelta), + Game.getObject(sdk.quest.chest.StoneLambda) + ]; + + while (stones.some((stone) => !stone.mode)) { + for (let i = 0; i < stones.length; i++) { + let stone = stones[i]; + + if (Misc.openChest(stone)) { + stones.splice(i, 1); + i--; + } + delay(10); + } + } + + let tick = getTickCount(); + // wait up to two minutes + while (getTickCount() - tick < Time.minutes(2)) { + if (Pather.getPortal(sdk.areas.Tristram)) { + Pather.usePortal(sdk.areas.RogueEncampment, Config.Leader); + + break; + } + } + Town.move("portalspot"); + actions.shift(); + + break; + case sdk.areas.DarkWood: + if (!Pather.usePortal(sdk.areas.DarkWood, Config.Leader)) { + this.log("Failed to use portal to dark wood", Config.LocalChat.Enabled); + break; + } + + this.getQuestItem(sdk.items.quest.ScrollofInifuss, sdk.quest.chest.InifussTree); + delay(500); + Pather.usePortal(sdk.areas.RogueEncampment, Config.Leader); + + if (Town.npcInteract("Akara")) { + this.log("Akara done", Config.LocalChat.Enabled); + } + + Town.move("portalspot"); + actions.shift(); + + break; + case sdk.areas.Tristram: + if (!Pather.usePortal(sdk.areas.Tristram, Config.Leader)) { + this.log("Failed to use portal to Tristram", Config.LocalChat.Enabled); + break; + } + + let gibbet = Game.getObject(sdk.quest.chest.CainsJail); + + if (gibbet && !gibbet.mode) { + Pather.moveTo(gibbet.x, gibbet.y); + if (Misc.poll(() => Misc.openChest(gibbet), 2000, 100)) { + Pather.usePortal(sdk.areas.RogueEncampment, Config.Leader); + Town.npcInteract("Akara") && this.log("Akara done", Config.LocalChat.Enabled); + } + } + Town.move("portalspot"); + actions.shift(); + + break; + case sdk.areas.CatacombsLvl4: + if (!Pather.usePortal(sdk.areas.CatacombsLvl4, Config.Leader)) { + this.log("Failed to use portal to catacombs", Config.LocalChat.Enabled); + break; + } + + target = Pather.getPortal(null, Config.Leader); + target && Pather.walkTo(target.x, target.y); + + actions.shift(); + + break; + case sdk.areas.A2SewersLvl3: + Town.move("portalspot"); + + if (Pather.usePortal(sdk.areas.A2SewersLvl3, Config.Leader)) { + actions.shift(); + } + + break; + case sdk.areas.HallsoftheDeadLvl3: + Pather.usePortal(sdk.areas.HallsoftheDeadLvl3, Config.Leader); + this.getQuestItem(sdk.quest.item.Cube, sdk.quest.chest.HoradricCubeChest); + Pather.usePortal(sdk.areas.LutGholein, Config.Leader); + + actions.shift(); + + break; + case sdk.areas.ClawViperTempleLvl2: + Pather.usePortal(sdk.areas.ClawViperTempleLvl2, Config.Leader); + this.getQuestItem(sdk.quest.item.ViperAmulet, sdk.quest.chest.ViperAmuletChest); + Pather.usePortal(sdk.areas.LutGholein, Config.Leader); + + if (Town.npcInteract("Drognan")) { + actions.shift(); + say("drognan done", Config.LocalChat.Enabled); + } + + Town.move("portalspot"); + + break; + case sdk.areas.MaggotLairLvl3: + Pather.usePortal(sdk.areas.MaggotLairLvl3, Config.Leader); + this.getQuestItem(sdk.quest.item.ShaftoftheHoradricStaff, sdk.quest.chest.ShaftoftheHoradricStaffChest); + delay(500); + Pather.usePortal(sdk.areas.LutGholein, Config.Leader); + this.cubeStaff(); + + actions.shift(); + + break; + case sdk.areas.ArcaneSanctuary: + if (!Pather.usePortal(sdk.areas.ArcaneSanctuary, Config.Leader)) { + break; + } + + actions.shift(); + + break; + case sdk.areas.TalRashasTomb1: + case sdk.areas.TalRashasTomb2: + case sdk.areas.TalRashasTomb3: + case sdk.areas.TalRashasTomb4: + case sdk.areas.TalRashasTomb5: + case sdk.areas.TalRashasTomb6: + case sdk.areas.TalRashasTomb7: + Pather.usePortal(null, Config.Leader); + this.placeStaff(); + Pather.usePortal(sdk.areas.LutGholein, Config.Leader); + actions.shift(); + + break; + case sdk.areas.DurielsLair: + Pather.usePortal(sdk.areas.DurielsLair, Config.Leader); + this.tyraelTalk(); + + actions.shift(); + + break; + case sdk.areas.Travincal: + if (!Pather.usePortal(sdk.areas.Travincal, Config.Leader)) { + me.cancel(); + + break; + } + + actions.shift(); + + break; + case sdk.areas.RuinedTemple: + if (!Pather.usePortal(sdk.areas.RuinedTemple, Config.Leader)) { + me.cancel(); + + break; + } + + this.getQuestItem(sdk.quest.item.LamEsensTome, sdk.quest.chest.LamEsensTomeHolder); + Pather.usePortal(sdk.areas.KurastDocktown, Config.Leader); + Town.npcInteract("Alkor"); + Town.move("portalspot"); + actions.shift(); + + + break; + case sdk.areas.DuranceofHateLvl3: + if (!Pather.usePortal(sdk.areas.DuranceofHateLvl3, Config.Leader)) { + me.cancel(); + + break; + } + + actions.shift(); + + break; + case sdk.areas.OuterSteppes: + case sdk.areas.PlainsofDespair: + if (Pather.usePortal(null, Config.Leader)) { + actions.shift(); + } + + break; + case sdk.areas.ChaosSanctuary: + Pather.usePortal(sdk.areas.ChaosSanctuary, Config.Leader); + Pather.moveTo(7762, 5268); + Packet.flash(me.gid); + delay(500); + Pather.walkTo(7763, 5267, 2); + + while (!Game.getMonster(sdk.monsters.Diablo)) { + delay(500); + } + + Pather.moveTo(7763, 5267); + actions.shift(); + + break; + case sdk.areas.BloodyFoothills: + Pather.usePortal(sdk.areas.BloodyFoothills, Config.Leader); + actions.shift(); + + break; + case sdk.areas.FrozenRiver: + Town.npcInteract("Malah"); + + Pather.usePortal(sdk.areas.FrozenRiver, Config.Leader); + delay(500); + + target = Game.getObject(sdk.objects.FrozenAnya); + + if (target) { + Pather.moveToUnit(target); + Misc.poll(() => { + Packet.entityInteract(target); + delay(100); + return !Game.getObject(sdk.objects.FrozenAnya); + }, 1000, 200); + delay(1000); + me.cancel(); + } + + actions.shift(); + + break; + default: // unsupported area + actions.shift(); + + break; + } + + break; + case "2": // Go back to town and check quest + if (!Config.Rushee.Quester) { + // Non-questers can piggyback off quester out messages + switch (leader.area) { + case sdk.areas.OuterSteppes: + case sdk.areas.PlainsofDespair: + me.act === 4 && Misc.checkQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.ReqComplete) && Town.npcInteract("Tyrael"); + + break; + case sdk.areas.BloodyFoothills: + me.act === 5 && Town.npcInteract("Larzuk"); + + break; + case sdk.areas.FrozenRiver: + if (me.act === 5) { + Town.npcInteract("Malah"); + this.useScrollOfRes(); + } + + break; + } + + actions.shift(); + + break; + } + + this.revive(); + + switch (me.area) { + case sdk.areas.CatacombsLvl4: + // Go to town if not there, break if procedure fails + if (!me.inTown && !Pather.usePortal(sdk.areas.RogueEncampment)) { + break; + } + + if (!Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, 4)) { + D2Bot.printToConsole("Andariel quest failed", sdk.colors.D2Bot.Red); + quit(); + } + + actions.shift(); + + break; + case sdk.areas.A2SewersLvl3: + if (!me.inTown && !Pather.usePortal(sdk.areas.LutGholein, Config.Leader)) { + break; + } + + actions.shift(); + + break; + case sdk.areas.ArcaneSanctuary: + if (!me.inTown && !Pather.usePortal(sdk.areas.LutGholein, Config.Leader)) { + break; + } + + Town.npcInteract("Atma"); + + if (!Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.Completed)) { + D2Bot.printToConsole("Summoner quest failed", sdk.colors.D2Bot.Red); + quit(); + } + + Town.move("portalspot"); + actions.shift(); + + break; + case sdk.areas.Travincal: + if (!me.inTown && !Pather.usePortal(sdk.areas.KurastDocktown, Config.Leader)) { + break; + } + + Town.npcInteract("Cain"); + + if (!Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)) { + D2Bot.printToConsole("Travincal quest failed", sdk.colors.D2Bot.Red); + quit(); + } + + Town.move("portalspot"); + actions.shift(); + + break; + case sdk.areas.DuranceofHateLvl3: + if (!Pather.usePortal(sdk.areas.KurastDocktown, Config.Leader)) { + break; + } + + actions.shift(); + + break; + case sdk.areas.OuterSteppes: + case sdk.areas.PlainsofDespair: + if (!me.inTown && !Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader)) { + break; + } + + if (Misc.checkQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.ReqComplete)) { + Town.npcInteract("Tyrael"); + Town.move("portalspot"); + } + + actions.shift(); + + break; + case sdk.areas.ChaosSanctuary: + me.classic && D2Bot.restart(); + + if (!me.inTown && !Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader)) { + break; + } + + actions.shift(); + + break; + case sdk.areas.BloodyFoothills: + if (!me.inTown && !Pather.usePortal(sdk.areas.Harrogath, Config.Leader)) { + break; + } + + Town.npcInteract("Larzuk"); + Town.move("portalspot"); + actions.shift(); + + break; + case sdk.areas.FrozenRiver: + if (!me.inTown && !Pather.usePortal(sdk.areas.FrozenRiver, Config.Leader)) { + break; + } + + Town.npcInteract("Malah"); + this.useScrollOfRes(); + Town.move("portalspot"); + + actions.shift(); + + break; + default: + Town.move("portalspot"); + actions.shift(); + + break; + } + + break; + case "3": // Bumper + if (!Config.Rushee.Bumper) { + actions.shift(); + + break; + } + + while (!leader.area) { + delay(500); + } + + act = Misc.getPlayerAct(leader); + + if (me.act !== act) { + Town.goToTown(act); + Town.move("portalspot"); + } + + switch (leader.area) { + case sdk.areas.ArreatSummit: + if (!Pather.usePortal(sdk.areas.ArreatSummit, Config.Leader)) { + break; + } + + // Wait until portal is gone + while (Pather.getPortal(sdk.areas.Harrogath, Config.Leader)) { + delay(500); + } + + // Wait until portal is up again + while (!Pather.getPortal(sdk.areas.Harrogath, Config.Leader)) { + delay(500); + } + + if (!Pather.usePortal(sdk.areas.Harrogath, Config.Leader)) { + break; + } + + actions.shift(); + + break; + case sdk.areas.WorldstoneChamber: + if (!Pather.usePortal(sdk.areas.WorldstoneChamber, Config.Leader)) { + break; + } + + actions.shift(); + + break; + } + + break; + case "quit": + done = true; + + break; + case "exit": + case "bye ~": + if (!nextGame) { + D2Bot.printToConsole("Rush Complete"); + D2Bot.stop(); + } else { + D2Bot.restart(); + } + + break; + case "a2": + case "a3": + case "a4": + case "a5": + act = actions[0].toString()[1]; + !!act && (act = (parseInt(act, 10) || me.act + 1)); + + if (!this.changeAct(act)) { + break; + } + + Town.move("portalspot"); + actions.shift(); + + break; + case me.name + " quest": + say("I am quester."); + Config.Rushee.Quester = true; + + actions.shift(); + + break; + case "leader": + console.log(Config.Leader + " is my leader in my config. " + leader.name + " is my leader right now"); + Config.LocalChat.Enabled && say(Config.Leader + " is my leader in my config. " + leader.name + " is my leader right now"); + actions.shift(); + + break; + default: // Invalid command + actions.shift(); + + break; + } + } + } catch (e) { + if (me.mode === sdk.player.mode.Dead) { + me.revive(); + + while (!me.inTown) { + delay(500); + } + } + } + + if (getUIFlag(sdk.uiflags.TradePrompt)) { + me.cancel(); + } + + if (done) { + break; + } + + delay(500); + } + + done && quit(); + + return true; +} diff --git a/d2bs/kolbot/libs/scripts/Rusher.js b/d2bs/kolbot/libs/scripts/Rusher.js new file mode 100644 index 000000000..20ba4817b --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rusher.js @@ -0,0 +1,258 @@ +/** +* @filename Rusher.js +* @author kolton, theBGuy +* @desc Rusher script. +* +* @Commands +* master - assigns player as master and listens to his commands +* release - resets master +* pause - pause the rusher +* resume - resume the rusher +* do sequence - stop current action and start the given sequence. +* supported sequences are: andariel, cube, amulet, staff, summoner, duriel, travincal, mephisto, diablo +* Example: do travincal +* +*/ + +function Rusher () { + load("threads/rushthread.js"); + delay(500); + + let i, command, master, commandSplit0; + let commands = []; + let sequence = [ + "cain", "andariel", "radament", "cube", "amulet", "staff", "summoner", "duriel", "lamesen", + "travincal", "mephisto", "izual", "diablo", "shenk", "anya", "ancients", "baal", "givewps" + ]; + const RushThread = { + /** @type {Script} */ + _thread: null, + path: "threads/rushthread.js", + + get: function () { + if (!this._thread) { + this._thread = getScript(this.path); + } + return this._thread; + }, + /** @param {String} msg */ + send: function (msg) { + // sign the msg so we can ignore other threads' messages + this.get().send("rush-" + msg); + }, + pause: function () { + say("Pausing"); + console.log("Pausing rush thread"); + this.get().pause(); + }, + resume: function () { + say("Resuming"); + console.log("Resuming rush thread"); + this.get().resume(); + }, + start: function () { + load(this.path); + delay(500); + + while (!this.get()) { + delay(500); + } + }, + stop: function () { + this.get().stop(); + }, + reload: function () { + this.stop(); + + while (this.get().running) { + delay(3); + } + this._thread = null; + this.start(); + }, + }; + + const getPartyAct = function () { + let party = getParty(); + let minArea = 999; + + do { + if (party.name !== me.name) { + while (!party.area) { + me.overhead("Waiting for party area info"); + delay(500); + } + + if (party.area < minArea) { + minArea = party.area; + } + } + } while (party.getNext()); + + return sdk.areas.actOf(minArea); + }; + + const chatEvent = function (nick, msg) { + if (nick !== me.name) { + if (typeof msg !== "string") return; + switch (msg) { + case "master": + if (!master) { + say(nick + " is my master."); + + master = nick; + } else { + say("I already have a master."); + } + + break; + case "release": + if (nick === master) { + say("I have no master now."); + + master = false; + } else { + say("I'm only accepting commands from my master."); + } + + break; + case "quit": + if (nick === master) { + say("bye ~"); + scriptBroadcast("quit"); + } else { + say("I'm only accepting commands from my master."); + } + + break; + default: + if (msg && msg.match(/^do \w|^clear \d|^pause$|^resume$/gi)) { + if (nick === master) { + commands.push(msg); + } else { + say("I'm only accepting commands from my master."); + } + } else if (msg && msg.includes("highestquest")) { + if (!!master && nick === master || !master) { + command = msg; + } else { + say("I'm only accepting commands from my master."); + } + } + + break; + } + } + }; + + addEventListener("chatmsg", chatEvent); + + while (Misc.getPartyCount() < Math.min(8, Config.Rusher.WaitPlayerCount)) { + me.overhead("Waiting for players to join"); + delay(500); + } + + // Skip to a higher act if all party members are there + switch (getPartyAct()) { + case 2: + say("Party is in act 2, starting from act 2"); + RushThread.send("skiptoact 2"); + + break; + case 3: + say("Party is in act 3, starting from act 3"); + RushThread.send("skiptoact 3"); + + break; + case 4: + say("Party is in act 4, starting from act 4"); + RushThread.send("skiptoact 4"); + + break; + case 5: + say("Party is in act 5, starting from act 5"); + RushThread.send("skiptoact 5"); + + break; + } + + // get info from master + let tick = getTickCount(); + let askAgain = 1; + say("questinfo"); + while (!command) { + // wait up to 3 minutes + if (getTickCount() - tick > Time.minutes(3)) { + break; + } + + if (getTickCount() - tick > Time.minutes(askAgain)) { + say("questinfo"); + askAgain++; + } + } + + if (command) { + commandSplit0 = command.split(" ")[1]; + if (!!commandSplit0 && sequence.some(el => el.toLowerCase() === commandSplit0)) { + RushThread.send(command.toLowerCase()); + } + } + + delay(200); + RushThread.send("go"); + + while (true) { + if (commands.length > 0) { + command = commands.shift(); + + switch (command) { + case "pause": + RushThread.pause(); + + break; + case "resume": + RushThread.resume(); + + break; + default: + if (typeof command === "string") { + commandSplit0 = command.split(" ")[0]; + + if (commandSplit0 === undefined) { + break; + } + + if (commandSplit0.toLowerCase() === "do") { + for (i = 0; i < sequence.length; i += 1) { + if (command.split(" ")[1] && sequence[i].match(command.split(" ")[1], "gi")) { + RushThread.reload(); + RushThread.send(command.split(" ")[1]); + + break; + } + } + + i === sequence.length && say("Invalid sequence"); + } else if (commandSplit0.toLowerCase() === "clear") { + if (!isNaN(parseInt(command.split(" ")[1], 10)) + && parseInt(command.split(" ")[1], 10) > 0 + && parseInt(command.split(" ")[1], 10) <= 132) { + RushThread.reload(); + RushThread.send(command); + } else { + say("Invalid area"); + } + } + } + + break; + } + } + + delay(100); + } + + // eslint-disable-next-line no-unreachable + return true; +} diff --git a/d2bs/kolbot/libs/scripts/SealLeecher.js b/d2bs/kolbot/libs/scripts/SealLeecher.js new file mode 100644 index 000000000..13e19a7cc --- /dev/null +++ b/d2bs/kolbot/libs/scripts/SealLeecher.js @@ -0,0 +1,102 @@ +/** +* @filename SealLeecher.js +* @author probably kolton, theBGuy +* @desc Leecher script. Works in conjuction with SealLeader script. +* +*/ + +const SealLeecher = new Runnable( + function SealLeecher() { + let commands = []; + Town.goToTown(4); + Town.move("portalspot"); + + if (!Config.Leader) { + D2Bot.printToConsole("You have to set Config.Leader"); + D2Bot.stop(); + + return false; + } + + let chatEvent = function (nick, msg) { + if (nick === Config.Leader) { + commands.push(msg); + } + }; + + try { + addEventListener("chatmsg", chatEvent); + + // Wait until leader is partied + while (!Misc.inMyParty(Config.Leader)) { + delay(1000); + } + + while (Misc.inMyParty(Config.Leader)) { + if (commands.length > 0) { + let command = commands.shift(); + + switch (command) { + case "in": + if (me.inTown) { + Pather.usePortal(sdk.areas.ChaosSanctuary, Config.Leader); + delay(250); + } + + if (getDistance(me, 7761, 5267) < 10) { + Pather.walkTo(7761, 5267, 2); + } + + break; + case "out": + if (!me.inTown) { + Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader); + } + + break; + case "done": + if (!me.inTown) { + Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader); + } + + return true; // End script + } + } + + if (me.dead) { + while (me.mode === sdk.player.mode.Death) { + delay(40); + } + + me.revive(); + + while (!me.inTown) { + delay(40); + } + } + + if (!me.inTown) { + let monster = Game.getMonster(); + + if (monster) { + do { + if (monster.attackable && monster.distance < 20) { + me.overhead("HOT"); + Pather.usePortal(sdk.areas.PandemoniumFortress, Config.Leader); + } + } while (monster.getNext()); + } + } + + delay(100); + } + } finally { + removeEventListener("chatmsg", chatEvent); + } + + return true; + }, + { + startArea: sdk.areas.PandemoniumFortress + } +); diff --git a/d2bs/kolbot/libs/scripts/SharpTooth.js b/d2bs/kolbot/libs/scripts/SharpTooth.js new file mode 100644 index 000000000..25863d621 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/SharpTooth.js @@ -0,0 +1,28 @@ +/** +* @filename Sharptooth.js +* @author loshmi +* @desc kill Thresh Socket +* +*/ + +const SharpTooth = new Runnable( + function SharpTooth () { + Pather.useWaypoint(sdk.areas.FrigidHighlands); + Precast.doPrecast(true); + + // FrigidHighlands returns invalid size with getBaseStat('leveldefs', 111, ['SizeX', 'SizeX(N)', 'SizeX(H)'][me.diff]); + // Could this be causing crashes here? + if (!Pather.moveToPresetMonster(sdk.areas.FrigidHighlands, sdk.monsters.preset.SharpToothSayer)) { + throw new Error("Failed to move to Sharptooth Slayer"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.SharpToothSayer)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.FrigidHighlands, + bossid: getLocaleString(sdk.locale.monsters.SharpToothSayer), + } +); diff --git a/d2bs/kolbot/libs/scripts/ShopBot.js b/d2bs/kolbot/libs/scripts/ShopBot.js new file mode 100644 index 000000000..0bcdd6801 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ShopBot.js @@ -0,0 +1,309 @@ +/** +* @filename ShopBot.js +* @author kolton, theBGuy +* @desc shop for items continually +* +*/ + +const ShopBot = new Runnable( + function ShopBot () { + const overlayText = { + title: new Text("kolbot shopbot", 50, 245, 2, 1), + cycles: new Text("Cycles in last minute: 0", 50, 260, 2, 1), + frequency: new Text("Valid item frequency: 0", 50, 275, 2, 1), + totalCycles: new Text("Total cycles: 0", 50, 290, 2, 1), + }; + + let tickCount; + let cycles = 0; + let validItems = 0; + let totalCycles = 0; + + /** @type {Array<[(item: ItemUnit) => boolean, (item: ItemUnit) => boolean, (item: ItemUnit) => boolean]>} */ + const pickEntries = []; + /** @type {Object} */ + const npcs = {}; + const wpPresets = { + 1: sdk.objects.A1Waypoint, + 2: sdk.objects.A2Waypoint, + 3: sdk.objects.A3Waypoint, + 4: sdk.objects.A4Waypoint, + 5: sdk.objects.A5Waypoint + }; + const outOfTownWps = { + 1: sdk.areas.CatacombsLvl2, + 2: sdk.areas.A2SewersLvl2, + 3: sdk.areas.DuranceofHateLvl2, + 4: sdk.areas.RiverofFlame, + 5: sdk.areas.CrystalizedPassage + }; + const shopableNPCS = new Map([ + // Act 1 + [NPC.Charsi, { town: sdk.areas.RogueEncampment, menuId: "Repair" }], + [NPC.Akara, { town: sdk.areas.RogueEncampment, menuId: "Shop" }], + [NPC.Gheed, { town: sdk.areas.RogueEncampment, menuId: "Shop" }], + // Act 2 + [NPC.Fara, { town: sdk.areas.LutGholein, menuId: "Repair" }], + [NPC.Elzix, { town: sdk.areas.LutGholein, menuId: "Shop" }], + [NPC.Drognan, { town: sdk.areas.LutGholein, menuId: "Shop" }], + // Act 3 + [NPC.Hratli, { town: sdk.areas.KurastDocktown, menuId: "Repair" }], + [NPC.Asheara, { town: sdk.areas.KurastDocktown, menuId: "Shop" }], + [NPC.Ormus, { town: sdk.areas.KurastDocktown, menuId: "Shop" }], + // Act 4 + [NPC.Halbu, { town: sdk.areas.PandemoniumFortress, menuId: "Repair" }], + [NPC.Jamella, { town: sdk.areas.PandemoniumFortress, menuId: "Shop" }], + // Act 5 + [NPC.Larzuk, { town: sdk.areas.Harrogath, menuId: "Repair" }], + [NPC.Malah, { town: sdk.areas.Harrogath, menuId: "Shop" }], + [NPC.Anya, { town: sdk.areas.Harrogath, menuId: "Shop" }], + [NPC.Nihlathak, { town: sdk.areas.Harrogath, menuId: "Shop" }] + ]); + + const buildPickList = function () { + let nipfile, filepath = "pickit/shopbot.nip"; + let filename = filepath.substring(filepath.lastIndexOf("/") + 1, filepath.length); + + if (!FileTools.exists(filepath)) { + Misc.errorReport("ÿc1NIP file doesn't exist: ÿc0" + filepath); + return false; + } + + try { + nipfile = File.open(filepath, 0); + } catch (fileError) { + Misc.errorReport("ÿc1Failed to load NIP: ÿc0" + filename); + } + + if (!nipfile) return false; + + let lines = nipfile.readAllLines(); + nipfile.close(); + + for (let i = 0; i < lines.length; i += 1) { + let info = { + line: i + 1, + file: filename, + string: lines[i] + }; + + let line = NTIP.ParseLineInt(lines[i], info); + line && pickEntries.push(line); + } + + return true; + }; + + /** + * Interact and open the menu of an NPC unit + * @param {NPCUnit} npc + * @returns {boolean} + */ + const openMenu = function (npc) { + if (!npc || npc.type !== sdk.unittype.NPC) throw new Error("Unit.openMenu: Must be used on NPCs."); + + let interactedNPC = getInteractedNPC(); + + if (interactedNPC && interactedNPC.name !== npc.name) { + Packet.cancelNPC(interactedNPC); + me.cancel(); + } + + if (getUIFlag(sdk.uiflags.NPCMenu)) return true; + + for (let i = 0; i < 10; i += 1) { + npc.distance > 5 && Pather.walkTo(npc.x, npc.y); + + if (!getUIFlag(sdk.uiflags.NPCMenu)) { + Packet.entityInteract(npc); + Packet.initNPC(npc); + } + + let tick = getTickCount(); + + while (getTickCount() - tick < Math.max(Math.round((i + 1) * 250 / (i / 3 + 1)), me.ping + 1)) { + if (getUIFlag(sdk.uiflags.NPCMenu)) { + return true; + } + + delay(10); + } + } + + me.cancel(); + + return false; + }; + + /** + * @param {NPCUnit} npc + * @param {number} menuId + * @returns {boolean} + */ + const shopItems = function (npc, menuId) { + if (!Storage.Inventory.CanFit({ sizex: 2, sizey: 4 }) && AutoMule.getMuleItems().length > 0) { + D2Bot.printToConsole("Mule triggered"); + scriptBroadcast("mule"); + scriptBroadcast("quit"); + return true; + } + + if (!npc) return false; + + for (let i = 0; i < 10; i += 1) { + delay(150); + + i % 2 === 0 && sendPacket(1, sdk.packets.send.EntityAction, 4, 1, 4, npc.gid, 4, 0); + + if (npc.itemcount > 0) { + break; + } + } + + let items = npc.getItemsEx().filter(function (item) { + return (Config.ShopBot.ScanIDs.includes(item.classid) || Config.ShopBot.ScanIDs.length === 0); + }); + if (!items.length) return false; + + me.overhead(npc.itemcount + " items, " + items.length + " valid"); + + let bought; + validItems += items.length; + overlayText.frequency.text = "Valid base items / cycle: " + ((validItems / totalCycles).toFixed(2).toString()); + + for (let i = 0; i < items.length; i += 1) { + if (Storage.Inventory.CanFit(items[i]) && Pickit.canPick(items[i]) && + me.gold >= items[i].getItemCost(sdk.items.cost.ToBuy) && + NTIP.CheckItem(items[i], pickEntries) + ) { + beep(); + D2Bot.printToConsole("Match found!", sdk.colors.D2Bot.DarkGold); + delay(1000); + + if (npc.startTrade(menuId)) { + Item.logItem("Shopped", items[i]); + items[i].buy(); + bought = true; + } + + Config.ShopBot.QuitOnMatch && scriptBroadcast("quit"); + } + } + + if (bought) { + me.cancelUIFlags(); + Town.stash(); + } + + return true; + }; + + /** + * @param {string} name + * @returns {boolean} + */ + const shopAtNPC = function (name) { + if (!shopableNPCS.has(name)) { + throw new Error("Invalid NPC"); + } + + const { town, menuId } = shopableNPCS.get(name); + + if (!me.inArea(town) && !Pather.useWaypoint(town)) return false; + + let npc = npcs[name] || Game.getNPC(name); + + if (!npc || npc.type !== sdk.unittype.NPC || npc.distance > 5) { + npc = Town.npcInteract(name); + } + + if (!npc) return false; + + !npcs[name] && (npcs[name] = copyUnit(npc)); + Config.ShopBot.CycleDelay && delay(Config.ShopBot.CycleDelay); + openMenu(npc) && shopItems(npc, menuId); + + return true; + }; + + // START + for (let i = 0; i < Config.ShopBot.ScanIDs.length; i += 1) { + if (isNaN(Config.ShopBot.ScanIDs[i])) { + if (NTIPAliasClassID.hasOwnProperty(Config.ShopBot.ScanIDs[i].replace(/\s+/g, "").toLowerCase())) { + Config.ShopBot.ScanIDs[i] = NTIPAliasClassID[Config.ShopBot.ScanIDs[i].replace(/\s+/g, "").toLowerCase()]; + } else { + Misc.errorReport("ÿc1Invalid ShopBot entry:ÿc0 " + Config.ShopBot.ScanIDs[i]); + Config.ShopBot.ScanIDs.splice(i, 1); + i -= 1; + } + } + } + + typeof Config.ShopBot.ShopNPC === "string" && (Config.ShopBot.ShopNPC = [Config.ShopBot.ShopNPC]); + + for (let i = 0; i < Config.ShopBot.ShopNPC.length; i += 1) { + Config.ShopBot.ShopNPC[i] = Config.ShopBot.ShopNPC[i].toLowerCase(); + } + + if (Config.ShopBot.MinGold && me.gold < Config.ShopBot.MinGold) return true; + + buildPickList(); + console.log("Shopbot: Pickit entries: " + pickEntries.length); + Town.doChores(); + + Pather.teleport = false; + tickCount = getTickCount(); + + while (!Config.ShopBot.Cycles || totalCycles < Config.ShopBot.Cycles) { + if (getTickCount() - tickCount >= 60 * 1000) { + overlayText.cycles.text = "Cycles in last minute: " + cycles.toString(); + overlayText.totalCycles.text = "Total cycles: " + totalCycles.toString(); + cycles = 0; + tickCount = getTickCount(); + } + + for (let i = 0; i < Config.ShopBot.ShopNPC.length; i += 1) { + shopAtNPC(Config.ShopBot.ShopNPC[i]); + } + + if (me.inTown) { + let area = getArea(); + const wp = Game.getPresetObject(me.area, wpPresets[me.act]).realCoords(); + const redPortal = (getUnits(sdk.unittype.Object, sdk.objects.RedPortal) + .sort((a, b) => a.distance - b.distance)) + .first(); + let exit = area.exits[0]; + + for (let i = 1; i < area.exits.length; i++) { + if (getDistance(me, exit) > getDistance(me, area.exits[i])) { + exit = area.exits[i]; + } + } + + if (!!redPortal && redPortal.distance < 20 && Pather.usePortal(null, null, redPortal)) { + delay(3000); + Pather.usePortal(sdk.areas.townOf(me.area)); + + if (totalCycles === 0) { + delay(10000); + } + + delay(1500); + } else if (getDistance(me, exit) < (getDistance(me, wp.x, wp.y) + 6)) { + Pather.moveToExit(me.area + 1, true); + Pather.moveToExit(me.area - 1, true); + } else { + Pather.useWaypoint(outOfTownWps[me.act]); + } + } + + cycles += 1; + totalCycles += 1; + } + + return true; + }, + { + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Smith.js b/d2bs/kolbot/libs/scripts/Smith.js new file mode 100644 index 000000000..98d36ffe7 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Smith.js @@ -0,0 +1,26 @@ +/** +* @filename Smith.js +* @author kolton +* @desc kill the Smith +* +*/ + +const Smith = new Runnable( + function Smith () { + Pather.useWaypoint(sdk.areas.OuterCloister); + Precast.doPrecast(true); + + if (!Pather.moveToPresetObject(sdk.areas.Barracks, sdk.quest.chest.MalusHolder)) { + throw new Error("Failed to move to the Smith"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.TheSmith)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.OuterCloister, + bossid: getLocaleString(sdk.locale.monsters.TheSmith), + } +); diff --git a/d2bs/kolbot/libs/scripts/Snapchip.js b/d2bs/kolbot/libs/scripts/Snapchip.js new file mode 100644 index 000000000..764016126 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Snapchip.js @@ -0,0 +1,26 @@ +/** +* @filename Snapchip.js +* @author kolton +* @desc kill Snapchip and optionally clear Icy Cellar +* +*/ + +const Snapchip = new Runnable( + function Snapchip () { + Pather.useWaypoint(sdk.areas.AncientsWay); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.IcyCellar, true) + || !Pather.moveToPresetObject(me.area, sdk.objects.SmallSparklyChest)) { + throw new Error("Failed to move to Snapchip Shatter"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.SnapchipShatter)); + Config.Snapchip.ClearIcyCellar && Attack.clearLevel(Config.ClearType); + + return true; + }, + { + startArea: sdk.areas.AncientsWay + } +); diff --git a/d2bs/kolbot/libs/scripts/Stormtree.js b/d2bs/kolbot/libs/scripts/Stormtree.js new file mode 100644 index 000000000..44be33c37 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Stormtree.js @@ -0,0 +1,25 @@ +/** +* @filename Stormtree.js +* @author kolton +* @desc kill Stormtree +* +*/ + +const Stormtree = new Runnable( + function Stormtree () { + Pather.useWaypoint(sdk.areas.LowerKurast); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.FlayerJungle, true)) { + throw new Error("Failed to move to Stormtree"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.Stormtree)); + + return true; + }, + { + startArea: sdk.areas.LowerKurast, + bossid: getLocaleString(sdk.locale.monsters.Stormtree), + } +); diff --git a/d2bs/kolbot/libs/scripts/Summoner.js b/d2bs/kolbot/libs/scripts/Summoner.js new file mode 100644 index 000000000..bb7c4856f --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Summoner.js @@ -0,0 +1,75 @@ +/** +* @filename Summoner.js +* @author kolton, theBGuy +* @desc kill the Summoner +* +*/ + +const Summoner = new Runnable( + function Summoner () { + Pather.useWaypoint(sdk.areas.ArcaneSanctuary); + Precast.doPrecast(true); + + if (Config.Summoner.FireEye && !Attack.haveKilled(getLocaleString(sdk.locale.monsters.FireEye))) { + try { + if (!Pather.usePortal(null)) throw new Error("Failed to move to Fire Eye"); + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.FireEye)); + } catch (e) { + console.error(e); + } + } + + if (me.inArea(sdk.areas.PalaceCellarLvl3) && !Pather.usePortal(null)) { + throw new Error("Failed to move back to arcane"); + } + + if (Attack.haveKilled(sdk.monsters.TheSummoner)) { + console.log("Summoner already dead"); + return true; + } + + if (!Pather.moveToPresetObject(me.area, sdk.quest.chest.Journal, { offX: -3, offY: -3 })) { + throw new Error("Failed to move to Summoner"); + } + + Attack.clear(15, 0, sdk.monsters.TheSummoner); + + // always take portal, faster access to wp + // first check if portal is already up + let portal = Game.getObject(sdk.objects.RedPortal); + + if (!portal || !Pather.usePortal(null, null, portal)) { + for (let i = 0; i < 5; i++) { + // couldn't find portal, attempt to interact with journal + let journal = Game.getObject(sdk.objects.Journal); + + // couldnt find journal? Move to it's preset + if (!journal) { + Pather.moveToPresetObject(me.area, sdk.objects.Journal); + continue; + } else if (journal && journal.distance > (18 - i)) { + Pather.moveNearUnit(journal, 13); + } + + Packet.entityInteract(journal); + Misc.poll(() => getIsTalkingNPC() || Game.getObject(sdk.objects.RedPortal), 2000, 200); + me.cancel() && me.cancel(); + + if (Pather.usePortal(sdk.areas.CanyonofMagic)) { + break; + } + } + } + + if (me.inArea(sdk.areas.CanyonofMagic)) { + Loader.scriptName(1) === "Duriel" + ? Loader.skipTown.push("Duriel") + : Pather.useWaypoint(sdk.areas.LutGholein); + } + + return true; + }, + { + startArea: sdk.areas.ArcaneSanctuary + } +); diff --git a/d2bs/kolbot/libs/scripts/Synch.js b/d2bs/kolbot/libs/scripts/Synch.js new file mode 100644 index 000000000..231266da0 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Synch.js @@ -0,0 +1,58 @@ +/** +* @filename Synch.js +* @author kolton +* @desc sync script? It's unused but works with Synch2.js +* +*/ + +let Synched = false; +let uRdyMsg = "I'm rdy, u?"; +let rdyMsg = "rdy"; + +function messageHandler(nick, msg) { + if (nick !== me.name) { + if (msg === uRdyMsg) { + say(rdyMsg); + Synched = true; + } else if (msg === rdyMsg) { + Synched = true; + } else if (msg === "Yo, I'm rdy, u?") { + say("No"); + quit(); + } + } +} + +function Synch() { + let i, party, j; + + addEventListener("chatmsg", messageHandler); + + delay(1000); + say(uRdyMsg); + + for (i = 0; i < 720 && !Synched; i += 1) { + delay(1000); + + for (j = 0; j < Config.Synch.WaitFor.length; j += 1) { + party = getParty(Config.Synch.WaitFor[j]); + if (!party) { + D2Bot.printToConsole("WaitFor not in game: " + + Config.Synch.WaitFor[j] + " so quitting."); + + removeEventListener("chatmsg", messageHandler); + quit(); + return false; + } + } + } + + if (!Synched) { + D2Bot.printToConsole("Failed to sync."); + quit(); + } + + removeEventListener("chatmsg", messageHandler); + + return true; +} diff --git a/d2bs/kolbot/libs/scripts/Synch2.js b/d2bs/kolbot/libs/scripts/Synch2.js new file mode 100644 index 000000000..020ebda64 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Synch2.js @@ -0,0 +1,62 @@ +/** +* @filename Synch2.js +* @author kolton +* @desc sync script? It's unused but works with Synch.js +* +*/ + +let Synched2 = false; +let uRdyMsg2 = "Yo, I'm rdy, u?"; +let rdyMsg2 = "Let's go"; + +function messageHandler2(nick, msg) { + if (nick !== me.name) { + if (msg === uRdyMsg2) { + say(rdyMsg2); + Synched2 = true; + } else if (msg === rdyMsg2) { + Synched2 = true; + } else if (msg === "I'm rdy, u?") { + say("No"); + quit(); + } + } +} + +function Synch2() { + let i, party, j; + + addEventListener("chatmsg", messageHandler2); + + delay(1000); + say(uRdyMsg2); + + delay(1000); + + for (i = 0; i < 720 && !Synched2; i += 1) { + for (j = 0; j < Config.Synch.WaitFor.length; j += 1) { + party = getParty(Config.Synch.WaitFor[j]); + if (!party) { + D2Bot.printToConsole("WaitFor not in game: " + + Config.Synch.WaitFor[j] + " so quitting."); + + removeEventListener("chatmsg", messageHandler2); + quit(); + return false; + } + } + + delay(1000); + } + + if (!Synched) { + D2Bot.printToConsole("Failed to sync."); + quit(); + } + + delay(1000); + + removeEventListener("chatmsg", messageHandler2); + + return true; +} diff --git a/d2bs/kolbot/libs/scripts/Test.js b/d2bs/kolbot/libs/scripts/Test.js new file mode 100644 index 000000000..daa6dde31 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Test.js @@ -0,0 +1,38 @@ +/** +* @filename Test.js +* @author kolton +* @desc Unsure? Just testing addEventListener it looks like +* +*/ + +function Test() { + print("ÿc8TESTING"); + + let c; + + function KeyDown(key) { + key === sdk.keys.Insert && (c = true); + } + + addEventListener("keydown", KeyDown); + + while (true) { + if (c) { + try { + doTest(); + } catch (qq) { + print("failed"); + print(qq + " " + qq.fileName + " " + qq.lineNumber); + } + + c = false; + } + + delay(10); + } +} + +function doTest() { + print("test"); + print("done"); +} diff --git a/d2bs/kolbot/libs/scripts/ThreshSocket.js b/d2bs/kolbot/libs/scripts/ThreshSocket.js new file mode 100644 index 000000000..65d10c465 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ThreshSocket.js @@ -0,0 +1,26 @@ +/** +* @filename ThreshSocket.js +* @author kolton +* @desc kill Thresh Socket +* +*/ + +const ThreshSocket = new Runnable( + function ThreshSocket () { + Pather.useWaypoint(sdk.areas.ArreatPlateau); + Precast.doPrecast(true); + + // ArreatPlateau returns invalid size with getBaseStat('leveldefs', 112, ['SizeX', 'SizeX(N)', 'SizeX(H)'][me.diff]); + // Could this be causing crashes here? Would it be better to go from crystal to Arreat instead? + if (!Pather.moveToExit(sdk.areas.CrystalizedPassage, false)) throw new Error("Failed to move to Thresh Socket"); + + Attack.kill(getLocaleString(sdk.locale.monsters.ThreshSocket)); + Pickit.pickItems(); + + return true; + }, + { + startArea: sdk.areas.ArreatPlateau, + bossid: getLocaleString(sdk.locale.monsters.ThreshSocket), + } +); diff --git a/d2bs/kolbot/libs/scripts/Tombs.js b/d2bs/kolbot/libs/scripts/Tombs.js new file mode 100644 index 000000000..7cde54ed6 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Tombs.js @@ -0,0 +1,36 @@ +/** +* @filename Tombs.js +* @author kolton, theBGuy +* @desc clear Tal Rasha's Tombs, optionally kill duriel as well +* +*/ + +const Tombs = new Runnable( + function Tombs() { + Pather.useWaypoint(sdk.areas.CanyonofMagic); + Precast.doPrecast(true); + const correctTomb = getRoom().correcttomb; + + for (let i = sdk.areas.TalRashasTomb1; i <= sdk.areas.TalRashasTomb7; i++) { + try { + if (!Pather.journeyTo(i, true)) throw new Error("Failed to move to tomb"); + + Attack.clearLevel(Config.ClearType); + + if (Config.Tombs.KillDuriel && me.inArea(correctTomb)) { + Pather.journeyTo(sdk.areas.DurielsLair) && Attack.kill(sdk.monsters.Duriel); + Pather.journeyTo(sdk.areas.CanyonofMagic); + } + + if (!Pather.moveToExit(sdk.areas.CanyonofMagic, true)) throw new Error("Failed to move to Canyon"); + } catch (e) { + console.error(e); + } + } + + return true; + }, + { + startArea: sdk.areas.CanyonofMagic + } +); diff --git a/d2bs/kolbot/libs/scripts/Travincal.js b/d2bs/kolbot/libs/scripts/Travincal.js new file mode 100644 index 000000000..3388bca06 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Travincal.js @@ -0,0 +1,74 @@ +/** +* @filename Travincal.js +* @author kolton +* @desc kill Council members in Travincal +* +*/ + +const Travincal = new Runnable( + function Travincal () { + Pather.useWaypoint(sdk.areas.Travincal); + Precast.doPrecast(true); + + const [orgX, orgY] = [me.x, me.y]; + + /** @param {Monster} mon */ + const councilMember = (mon) => ( + [sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3].includes(mon.classid) + ); + + if (Config.Travincal.PortalLeech) { + Pather.moveTo(orgX + 85, orgY - 139); + Attack.securePosition(orgX + 70, orgY - 139, 25, 2000); + Attack.securePosition(orgX + 100, orgY - 139, 25, 2000); + Attack.securePosition(orgX + 85, orgY - 139, 25, 5000); + Pather.moveTo(orgX + 85, orgY - 139); + Pather.makePortal(); + delay(1000); + Precast.doPrecast(true); + } + + if (Skill.canUse(sdk.skills.LeapAttack) && !Pather.canTeleport()) { + const coords = [[60, -53], [64, -72], [78, -72], [74, -88]]; + + for (let i = 0; i < coords.length; i++) { + let [x, y] = coords[i]; + + if (i % 2 === 0) { + Pather.moveTo(orgX + x, orgY + y); + } else { + Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, orgX + x, orgY + y); + Attack.clearList( + Attack.buildMonsterList( + /** @param {Monster} mon */ + (mon) => councilMember(mon) && !checkCollision(me, mon, sdk.collision.BlockWall) + ) + ); + } + } + + Attack.clearList(Attack.buildMonsterList(councilMember)); + } else { + Pather.moveTo(orgX + 101, orgY - 56); + + // Stack Merc + if (me.barbarian && !Pather.canTeleport() && me.expansion) { + Pather.moveToExit([sdk.areas.DuranceofHateLvl1, sdk.areas.Travincal], true); + } + + if (Config.MFLeader) { + Pather.makePortal(); + say("council " + me.area); + } + + Attack.clearList(Attack.buildMonsterList(councilMember)); + } + + Config.MFLeader && Config.PublicMode && say("travdone"); + + return true; + }, + { + startArea: sdk.areas.Travincal + } +); diff --git a/d2bs/kolbot/libs/scripts/TravincalLeech.js b/d2bs/kolbot/libs/scripts/TravincalLeech.js new file mode 100644 index 000000000..e0c5b706c --- /dev/null +++ b/d2bs/kolbot/libs/scripts/TravincalLeech.js @@ -0,0 +1,91 @@ +/** +* @filename TravincalLeech.js +* @author ToS/XxXGoD/YGM/azero, theBGuy +* @desc Travincal Leech +* +*/ + +/** +* @todo: +* - add help option +* - keep within 40 of leader for just leeching +* - long range help for helper? +* - add dodge if position is too hot (hydras can kill a low level quickly) +*/ + +const TravincalLeech = new Runnable( + function TravincalLeech () { + include("core/Common/Leecher.js"); + let leader; + let done = false; + + const chatEvent = function (nick, msg) { + if (nick === leader && msg.toLowerCase() === "travdone") { + done = true; + } + }; + + Town.goToTown(3); + Town.doChores(); + Town.move("portalspot"); + + if (Config.Leader) { + leader = Config.Leader; + if (!Misc.poll(() => Misc.inMyParty(leader), Time.minutes(2), 1000)) { + throw new Error("TristramLeech: Leader not partied"); + } + } + + !leader && (leader = Misc.autoLeaderDetect({ + destination: sdk.areas.Travincal, + quitIf: (area) => Common.Leecher.nextScriptAreas.includes(area), + timeout: Time.minutes(5) + })); + + if (leader) { + try { + const Worker = require("../modules/Worker"); + addEventListener("chatmsg", chatEvent); + + Common.Leecher.killLeaderTracker = false; + Common.Leecher.leader = leader; + Common.Leecher.currentScript = Loader.scriptName(); + Worker.runInBackground.leaderTracker = Common.Leecher.leaderTracker; + + while (Misc.inMyParty(Common.Leecher.leader)) { + if (done) return true; + + if (me.inTown && Pather.getPortal(sdk.areas.Travincal, Common.Leecher.leader)) { + Pather.usePortal(sdk.areas.Travincal, Common.Leecher.leader); + Town.getCorpse(); + } + + if (me.mode === sdk.player.mode.Dead) { + me.revive(); + + while (!me.inTown) { + delay(100); + } + + Town.move("portalspot"); + } + + delay(100); + } + } catch (e) { + console.error(e); + } finally { + removeEventListener("chatmsg", chatEvent); + Common.Leecher.killLeaderTracker = true; + } + } else { + console.warn("No leader found"); + } + + return true; + }, + { + startArea: sdk.areas.KurastDocktown, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Treehead.js b/d2bs/kolbot/libs/scripts/Treehead.js new file mode 100644 index 000000000..04992cb4a --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Treehead.js @@ -0,0 +1,25 @@ +/** +* @filename Treehead.js +* @author kolton +* @desc kill Treehead WoodFist +* +*/ + +const Treehead = new Runnable( + function Treehead () { + Pather.useWaypoint(sdk.areas.DarkWood); + Precast.doPrecast(true); + + if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) { + throw new Error("Failed to move to Treehead"); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.TreeheadWoodFist)); + + return true; + }, + { + startArea: sdk.areas.DarkWood, + bossid: getLocaleString(sdk.locale.monsters.TreeheadWoodFist), + } +); diff --git a/d2bs/kolbot/libs/scripts/Tristram.js b/d2bs/kolbot/libs/scripts/Tristram.js new file mode 100644 index 000000000..d3b986c3a --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Tristram.js @@ -0,0 +1,75 @@ +/** +* @filename Tristram.js +* @author kolton, cuss, theBGuy +* @desc clear Tristram +* +*/ + +const Tristram = new Runnable( + function Tristram () { + Pather._teleport = Pather.teleport; + + // complete quest if its not complete + if (!me.getQuest(sdk.quest.id.TheSearchForCain, 4) + && !me.getQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed)) { + include("core/Common/Cain.js"); + Common.Cain.run(); + } + + MainLoop: + while (true) { + switch (true) { + case me.inTown: + Town.doChores(); + Pather.useWaypoint(sdk.areas.StonyField); + Precast.doPrecast(true); + + break; + case me.inArea(sdk.areas.StonyField): + if (!Pather.moveToPreset( + sdk.areas.StonyField, + sdk.unittype.Monster, + sdk.monsters.preset.Rakanishu, + 0, 0, + false, + true) + ) { + throw new Error("Failed to move to Rakanishu"); + } + + if (!Attack.haveKilled(getLocaleString(sdk.locale.monsters.Rakanishu))) { + Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Rakanishu)); + } + + while (!Pather.usePortal(sdk.areas.Tristram)) { + Attack.securePosition(me.x, me.y, 10, 1000); + } + + break; + case me.inArea(sdk.areas.Tristram): + let redPortal = Game.getObject(sdk.objects.RedPortal); + !!redPortal && Pather.moveTo(redPortal.x, redPortal.y + 6); + + if (Config.Tristram.PortalLeech) { + Pather.makePortal(); + delay(1000); + Pather.teleport = !Config.Tristram.WalkClear && Pather._teleport; + } + + Config.Tristram.PortalLeech ? Attack.clearLevel(0) : Attack.clearLevel(Config.ClearType); + + break MainLoop; + default: + break MainLoop; + } + } + + Config.MFLeader && Config.PublicMode && say("tristdone"); + Pather.teleport = Pather._teleport; + + return true; + }, + { + startArea: sdk.areas.StonyField + } +); diff --git a/d2bs/kolbot/libs/scripts/TristramLeech.js b/d2bs/kolbot/libs/scripts/TristramLeech.js new file mode 100644 index 000000000..c46c6fbbd --- /dev/null +++ b/d2bs/kolbot/libs/scripts/TristramLeech.js @@ -0,0 +1,129 @@ +/** +* @filename TristramLeech.js +* @author ToS/XxXGoD/YGM, theBGuy +* @desc Tristram Leech (Helper) +* +*/ + +const TristramLeech = new Runnable( + function TristramLeech () { + include("core/Common/Leecher.js"); + let done = false; + let whereisleader, leader; + + const chatEvent = function (nick, msg) { + if (nick === leader && msg.toLowerCase() === "tristdone") { + done = true; + } + }; + + Town.goToTown(1); + Town.move("portalspot"); + + if (Config.Leader) { + leader = (Config.Leader || Config.TristramLeech.Leader); + if (!Misc.poll(() => Misc.inMyParty(leader), Time.seconds(30), 1000)) { + throw new Error("TristramLeech: Leader not partied"); + } + } + + !leader && (leader = Misc.autoLeaderDetect({ + destination: sdk.areas.Tristram, + quitIf: (area) => Common.Leecher.nextScriptAreas.includes(area), + timeout: Time.minutes(5) + })); + + if (leader) { + try { + const Worker = require("../modules/Worker"); + addEventListener("chatmsg", chatEvent); + + Common.Leecher.leader = leader; + Common.Leecher.currentScript = Loader.scriptName(); + Common.Leecher.killLeaderTracker = false; + Worker.runInBackground.leaderTracker = Common.Leecher.leaderTracker; + + if (!Misc.poll(() => { + if (done) return true; + if (Pather.getPortal(sdk.areas.Tristram, Config.Leader || null) + && Pather.usePortal(sdk.areas.Tristram, Config.Leader || null)) { + return true; + } + + return false; + }, Time.minutes(5), 1000)) { + throw new Error("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); + } + + Precast.doPrecast(true); + delay(3000); + + whereisleader = Misc.poll(() => { + let lead = getParty(leader); + + if (lead.area === sdk.areas.Tristram) { + return lead; + } + + return false; + }, Time.minutes(3), 1000); + + while (true) { + if (done) return true; + + whereisleader = getParty(leader); + let leaderUnit = Misc.getPlayerUnit(leader); + + if (whereisleader.area !== sdk.areas.Tristram && !Misc.poll(() => { + let lead = getParty(leader); + + if (lead.area === sdk.areas.Tristram) { + return true; + } + + return false; + }, Time.minutes(3), 1000)) { + console.log("Leader wasn't in tristram for longer than 3 minutes, End script"); + + break; + } + + if (whereisleader.area === me.area) { + try { + if (copyUnit(leaderUnit).x) { + if (Config.TristramLeech.Helper && leaderUnit.distance > 4) { + Pather.moveToUnit(leaderUnit) && Attack.clear(10); + } + !Config.TristramLeech.Helper && leaderUnit.distance > 20 && Pather.moveNearUnit(leaderUnit, 15); + } else { + if (Config.TristramLeech.Helper) { + Pather.moveTo(copyUnit(leaderUnit).x, copyUnit(leaderUnit).y) && Attack.clear(10); + } + !Config.TristramLeech.Helper && Pather.moveNear(copyUnit(leaderUnit).x, copyUnit(leaderUnit).y, 15); + } + } catch (err) { + if (whereisleader.area === me.area) { + Config.TristramLeech.Helper && Pather.moveTo(whereisleader.x, whereisleader.y) && Attack.clear(10); + !Config.TristramLeech.Helper && Pather.moveNear(whereisleader.x, whereisleader.y, 15); + } + } + } + + delay(100); + } + } catch (e) { + console.error(e); + } finally { + removeEventListener("chatmsg", chatEvent); + Common.Leecher.killLeaderTracker = true; + } + } + + if (!me.inTown && Town.goToTown()) throw new Error("Failed to get back to town"); + + return true; + }, + { + startArea: sdk.areas.RogueEncampment + } +); diff --git a/d2bs/kolbot/libs/scripts/UndergroundPassage.js b/d2bs/kolbot/libs/scripts/UndergroundPassage.js new file mode 100644 index 000000000..3b471227e --- /dev/null +++ b/d2bs/kolbot/libs/scripts/UndergroundPassage.js @@ -0,0 +1,24 @@ +/** +* @filename UndergroundPassage.js +* @author loshmi +* @desc Move and clear Underground passage level 2 +* +*/ + +const UndergroundPassage = new Runnable( + function UndergroundPassage() { + Pather.useWaypoint(sdk.areas.StonyField); + Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.UndergroundPassageLvl1, sdk.areas.UndergroundPassageLvl2], true)) { + throw new Error("Failed to move to Underground passage level 2"); + } + + Attack.clearLevel(); + + return true; + }, + { + startArea: sdk.areas.StonyField + } +); diff --git a/d2bs/kolbot/libs/scripts/UserAddon.js b/d2bs/kolbot/libs/scripts/UserAddon.js new file mode 100644 index 000000000..20fd05e4b --- /dev/null +++ b/d2bs/kolbot/libs/scripts/UserAddon.js @@ -0,0 +1,114 @@ +/** +* @filename UserAddon.js +* @author kolton, theBGuy +* @desc Allows you to see more information about items, NPCs and players by placing the cursor over them. +* Shows item level, items in sockets, classid, code and magic item prefix/suffix numbers. +* Shows monster's classid, HP percent and resistances. +* Shows other players' gear. +* +*/ + +const UserAddon = new Runnable( + function UserAddon () { + let i, title, dummy, command = ""; + let showInfo = true; + const UnitInfo = new (require("../modules/UnitInfo")); + const className = sdk.player.class.nameOf(me.classid); + const flags = [ + sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, + sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow, sdk.uiflags.ChatBox, + sdk.uiflags.Quest, sdk.uiflags.Msgs, sdk.uiflags.Stash, + sdk.uiflags.Shop, sdk.uiflags.EscMenu, sdk.uiflags.Cube + ]; + + const keyEvent = function (key) { + switch (key) { + case sdk.keys.Spacebar: + FileTools.copy("libs/config/" + className + ".js", "libs/config/" + className + "." + me.name + ".js"); + D2Bot.printToConsole("libs/config/" + className + "." + me.name + ".js has been created."); + D2Bot.printToConsole("Please configure your bot and start it again."); + D2Bot.stop(); + + break; + } + }; + + /** + * @param {string} speaker + * @param {string} msg + * @returns {boolean} + */ + const onChatInput = function (speaker, msg) { + if (msg.length && msg[0] === ".") { + command = msg.split(" ")[0].split(".")[1]; + return true; + } + + return false; + }; + + try { + // Make sure the item event is loaded - why though? + !Config.FastPick && addEventListener("itemaction", Pickit.itemEvent); + addEventListener("chatinputblocker", onChatInput); + + if (!FileTools.exists("libs/config/" + className + "." + me.name + ".js")) { + console.log("ÿc4UserAddonÿc0: Press HOME and then press SPACE if you want to create character config."); + addEventListener("keyup", keyEvent); + showConsole(); + } + + while (true) { + for (i = 0; i < flags.length; i += 1) { + if (getUIFlag(flags[i])) { + if (title) { + title.remove(); + dummy.remove(); + + title = false; + dummy = false; + } + + break; + } + } + + if (i === flags.length && !title) { + title = new Text(":: kolbot user addon ::", 400, 525, 4, 0, 2); + dummy = new Text("`", 1, 1); // Prevents crash + } + + UnitInfo.check(); + + if (command) { + console.debug(command); + if (command.toLowerCase() === "done") { + return true; + } else if (command.toLowerCase() === "info") { + showInfo = !showInfo; + } + command = ""; + } + + Pickit.fastPick(); + if (showInfo) { + UnitInfo.createInfo(Game.getSelectedUnit()); + } + + delay(20); + } + } finally { + console.log("ÿc4UserAddon ÿc1ended"); + removeEventListener("keyup", keyEvent); + removeEventListener("itemaction", Pickit.itemEvent); + removeEventListener("chatinputblocker", onChatInput); + // ensure hooks are properly disposed of + !!title && title.remove(); + dummy && dummy.remove(); + UnitInfo.remove(); + } + }, + { + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/WPGetter.js b/d2bs/kolbot/libs/scripts/WPGetter.js new file mode 100644 index 000000000..65c9a0360 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/WPGetter.js @@ -0,0 +1,30 @@ +/** +* @filename WPGetter.js +* @author kolton, theBGuy +* @desc Get wps we don't have +* +*/ + +const WPGetter = new Runnable( + function WPGetter () { + Town.goToTown(); + Pather.getWP(me.area); + + const highestAct = me.highestAct; + let lastWP = sdk.areas.townOfAct((highestAct === 5 ? highestAct : highestAct + 1)); + lastWP === sdk.areas.Harrogath && (lastWP = me.baal ? sdk.areas.WorldstoneLvl2 : sdk.areas.AncientsWay); + let wpsToGet = Pather.nonTownWpAreas + .filter((wp) => wp < lastWP && wp !== sdk.areas.HallsofPain && !me.haveWaypoint(wp)); + + console.debug(wpsToGet); + + for (let wp of wpsToGet) { + Pather.getWP(wp); + if (me.checkScrolls(sdk.items.TomeofTownPortal) < 10) { + Town.doChores(); + } + } + + return true; + } +); diff --git a/d2bs/kolbot/libs/scripts/Wakka.js b/d2bs/kolbot/libs/scripts/Wakka.js new file mode 100644 index 000000000..95c138e6c --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Wakka.js @@ -0,0 +1,463 @@ +/** +* @filename Wakka.js +* @author kolton, theBGuy +* @desc walking Chaos Sanctuary leecher +* +*/ + +const Wakka = new Runnable( + function Wakka () { + include("core/Common/Diablo.js"); + const timeout = Config.Wakka.Wait; + const [minDist, maxDist] = [50, 80]; + const internals = { + died: false, + safeTP: false, + coordsInit: false, + vizCoords: [], + seisCoords: [], + infCoords: [], + vizClear: false, + seisClear: false, + infClear: false, + }; + + let tick; + let leader = ""; + let [leaderUnit, leaderPartyUnit] = [null, null]; + + const checkMonsters = function (range = 15, dodge = false) { + let monList = []; + let monster = Game.getMonster(); + + if (monster) { + do { + if (monster.y < 5565 && monster.attackable && monster.distance <= range) { + if (!dodge) return true; + monList.push(copyUnit(monster)); + } + } while (monster.getNext()); + } + + if (!monList.length) return false; + + monList.sort(Sort.units); + + if (monList[0].distance < 25 && !checkCollision(me, monList[0], sdk.collision.Ranged)) { + Attack.deploy(monList[0], 25, 5, 15); + } + + return true; + }; + + const getCoords = function () { + if (!internals.coordsInit) { + Common.Diablo.initLayout(); + internals.vizCoords = Common.Diablo.vizLayout === 1 + ? [7707, 5274] + : [7708, 5298]; + internals.seisCoords = Common.Diablo.seisLayout === 1 + ? [7812, 5223] + : [7809, 5193]; + internals.infCoords = Common.Diablo.infLayout === 1 + ? [7868, 5294] + : [7882, 5306]; + internals.coordsInit = true; + } + }; + + /** @param {string} name */ + const checkBoss = function (name) { + let glow = Game.getObject(sdk.objects.SealGlow); + if (!glow) return false; + + for (let i = 0; i < 10; i += 1) { + let boss = Game.getMonster(name); + + if (boss) { + while (!boss.dead) { + delay(500); + } + + return true; + } + + delay(500); + } + + return true; + }; + + const revive = function () { + if (me.mode === sdk.player.mode.Death) { + while (me.mode !== sdk.player.mode.Dead) { + delay(3); + } + } else if (me.dead) { + if (Config.LifeChicken <= 0) { + console.log("I Died...reviving"); + internals.died = true; + me.revive(); + } else { + scriptBroadcast("quit"); + } + } + }; + + const getCorpse = function () { + revive(); + + let rval = false; + let corpse = Game.getPlayer(me.name, sdk.player.mode.Dead); + + if (corpse) { + do { + if (corpse.distance <= 15) { + Pather.moveToUnit(corpse); + corpse.interact(); + delay(500); + + rval = true; + } + } while (corpse.getNext()); + } + + return rval; + }; + + /** @param {[number, number]} dest */ + const followPath = function (dest) { + let path = getPath(me.area, me.x, me.y, dest[0], dest[1], 0, 10); + if (!path) throw new Error("Failed go get path"); + + while (path.length > 0) { + if (me.mode === sdk.player.mode.Dead || me.inTown) return false; + if (!leaderUnit || !copyUnit(leaderUnit).x) { + leaderUnit = Game.getPlayer(leader); + } + + if (leaderUnit) { + // monsters nearby - don't move + if (checkMonsters(45, true) && leaderUnit.distance <= maxDist) { + path = getPath(me.area, me.x, me.y, dest[0], dest[1], 0, 15); + delay(200); + + continue; + } + + // leader within minDist range - don't move + if (leaderUnit.distance <= minDist) { + delay(200); + + continue; + } + + // make sure distance to next node isn't too hot + if ([path[0].x, path[0].y].mobCount({ range: 15 }) !== 0) { + console.log("Mobs at next node"); + // mobs, stay where we are + delay(200); + + continue; + } + } else { + // leaderUnit out of getUnit range but leader is still within reasonable distance - check party unit's coords! + leaderPartyUnit = getParty(leader); + + if (leaderPartyUnit) { + // leader went to town - don't move + if (leaderPartyUnit.area !== me.area) { + delay(200); + + continue; + } + + // if there's monsters between the leecher and leader, wait until monsters are dead or leader is out of maxDist range + if (checkMonsters(45, true) && getDistance(me, leaderPartyUnit.x, leaderPartyUnit.y) <= maxDist) { + path = getPath(me.area, me.x, me.y, dest[0], dest[1], 0, 15); + + delay(200); + + continue; + } + } + } + + Pather.moveTo(path[0].x, path[0].y) && path.shift(); + // no mobs around us, so it's safe to pick + !me.checkForMobs({ + range: 10, + coll: (sdk.collision.BlockWall | sdk.collision.Objects | sdk.collision.ClosedDoor) + }) && Pickit.pickItems(5); + getCorpse(); + } + + return true; + }; + + /** @returns {number} */ + const getLeaderUnitArea = function () { + if (!leaderUnit || !copyUnit(leaderUnit).x) { + leaderUnit = Game.getPlayer(leader); + } + if (leaderUnit && leaderUnit.area !== 0) return leaderUnit.area; + let pLeader = getParty(leader); + if (pLeader && pLeader.area !== 0) return pLeader.area; + return 0; + }; + + const log = function (msg = "") { + me.overhead(msg); + console.log(msg); + }; + + // START + Town.goToTown(4); + Town.move("portalspot"); + + if (Config.Leader) { + leader = Config.Leader; + if (!Misc.poll(() => Misc.inMyParty(leader), 30e3, 1000)) { + console.warn("Wakka: Leader not partied. Using autodetect"); + leader = ""; + } + } + + !leader && (leader = Misc.autoLeaderDetect({ + destination: sdk.areas.ChaosSanctuary, + quitIf: (area) => [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(area), + timeout: timeout * 60e3 + })); + Town.doChores(); + if (!leader) throw new ScriptError("Wakka: Leader not found"); + + addEventListener("gamepacket", Common.Diablo.diabloLightsEvent); + const Worker = require("../modules/Worker"); + + try { + if (Config.Wakka.SkipIfBaal) { + let leadTick = getTickCount(); + let killLeaderTracker = false; + + Worker.runInBackground.leaderTracker = function () { + if (Common.Diablo.done || killLeaderTracker) return false; + // check every 3 seconds + if (getTickCount() - leadTick < 3000) return true; + leadTick = getTickCount(); + + // check again in another 3 seconds if game wasn't ready + if (!me.gameReady) return true; + if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); + + // Player is in Throne of Destruction or Worldstone Chamber + if ([sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(getLeaderUnitArea())) { + if (Loader.scriptName() === "Wakka") { + killLeaderTracker = true; + Common.Diablo.done = true; + throw new Error("Party leader is running baal"); + } else { + // kill process + return false; + } + } + + return true; + }; + } + + let levelTick = getTickCount(); + let killLevelTracker = false; + + Worker.runInBackground.levelTracker = function () { + if (Common.Diablo.done || killLevelTracker) return false; + // check every 3 seconds + if (getTickCount() - levelTick < 3000) return true; + levelTick = getTickCount(); + + // check again in another 3 seconds if game wasn't ready + if (!me.gameReady) return true; + + if (me.charlvl >= Config.Wakka.StopAtLevel) { + Config.Wakka.StopProfile && D2Bot.stop(); + + if (Loader.scriptName() === "Wakka") { + killLevelTracker = true; + throw new Error("Reached wanted level"); + } else { + // kill process + return false; + } + } + + return true; + }; + + let diaTick = getTickCount(); + + Worker.runInBackground.diaSpawned = function () { + if (Common.Diablo.done || (internals.vizClear && internals.seisClear && internals.infClear)) { + return false; + } + // check every 1/4 second + if (getTickCount() - diaTick < 250) return true; + diaTick = getTickCount(); + + if (Common.Diablo.diabloSpawned) { + internals.vizClear = true; + internals.seisClear = true; + internals.infClear = true; + throw new Error("Diablo spawned"); + } + + return true; + }; + + while (Misc.inMyParty(leader)) { + try { + if (Common.Diablo.done) { + console.log("Diablo is done"); + break; + } + + if (me.inArea(sdk.areas.PandemoniumFortress)) { + let portal = Pather.getPortal(sdk.areas.ChaosSanctuary, null); + + if (portal) { + !internals.safeTP && delay(5000); + Pather.usePortal(sdk.areas.ChaosSanctuary, null); + Precast.doPrecast(true); + } + } else if (me.inArea(sdk.areas.ChaosSanctuary)) { + try { + if (!internals.safeTP) { + if (checkMonsters(25, false)) { + log("hot tp"); + // go back through portal if it's still there + if (Pather.usePortal(sdk.areas.PandemoniumFortress, null)) { + continue; + } + // if the portal isn't there try to make our own + if (me.canTpToTown() && Town.goToTown()) { + continue; + } + // dodge monsters otherwise - find closest monster + let _closeMon = Attack.getNearestMonster(25); + Attack.deploy(_closeMon, 25, 5, 15); + } else { + getCoords(); + internals.safeTP = true; + } + } + + if (!internals.vizClear) { + if (followPath(internals.vizCoords)) { + if (checkBoss(getLocaleString(sdk.locale.monsters.GrandVizierofChaos))) { + log("vizier dead"); + internals.vizClear = true; + Precast.doPrecast(true); + tick = getTickCount(); + + while (getTickCount() - tick >= 5000) { + delay(100); + } + } + } else { + console.debug("Failed to move to viz"); + } + } + + if (internals.vizClear && !internals.seisClear) { + if (followPath(internals.seisCoords)) { + if (checkBoss(getLocaleString(sdk.locale.monsters.LordDeSeis))) { + log("seis dead"); + internals.seisClear = true; + Precast.doPrecast(true); + tick = getTickCount(); + + while (getTickCount() - tick >= 7000) { + delay(100); + } + } + } else { + console.debug("Failed to move to seis"); + } + } + + if (internals.vizClear && internals.seisClear && !internals.infClear) { + if (followPath(internals.infCoords)) { + if (checkBoss(getLocaleString(sdk.locale.monsters.InfectorofSouls))) { + log("infector dead"); + internals.infClear = true; + Precast.doPrecast(true); + tick = getTickCount(); + + while (getTickCount() - tick >= 2000) { + delay(100); + } + } + } else { + console.debug("Failed to move to infector"); + } + } + + if (internals.vizClear && internals.seisClear && internals.infClear) { + Pather.moveTo(7767, 5263); + Misc.poll(function () { + if (Common.Diablo.diabloSpawned) return true; + if (Game.getMonster(sdk.monsters.Diablo)) return true; + return false; + }, Time.minutes(2), 500); + } + } catch (e) { + console.error((e.message ? e.message : e)); + } + + if (internals.vizClear && internals.seisClear && internals.infClear) { + Pather.moveTo(7767, 5263); + + let diablo = Misc.poll(function () { + return Game.getMonster(sdk.monsters.Diablo); + }, Time.minutes(3), 500); + + if (diablo) { + while (!diablo.dead) { + delay(100); + } + log("Diablo is dead"); + + if (!me.canTpToTown() || !Town.goToTown()) { + Pather.usePortal(sdk.areas.PandemoniumFortress); + } + + return true; + } else { + log("Couldn't find diablo"); + } + } + } + + revive(); + + delay(200); + } catch (e) { + console.error(e); + + return true; + } + } + } catch (e) { + // console.error(e); + } finally { + Common.Diablo.done; + removeEventListener("gamepacket", Common.Diablo.diabloLightsEvent); + } + + log("Wakka complete"); + + return true; + }, + { + startArea: sdk.areas.PandemoniumFortress, + preAction: null + } +); diff --git a/d2bs/kolbot/libs/scripts/Worldstone.js b/d2bs/kolbot/libs/scripts/Worldstone.js new file mode 100644 index 000000000..14be2b358 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Worldstone.js @@ -0,0 +1,43 @@ +/** +* @filename Worldstone.js +* @author kolton, theBGuy +* @desc Clear Worldstone levels +* +*/ + +const Worldstone = new Runnable( + function Worldstone() { + Pather.useWaypoint(sdk.areas.WorldstoneLvl2); + Precast.doPrecast(true); + /** + * Calc distances so we know whether to tp to town or not after clearing WSK1 + * - WP -> WSK3 + * - WSK1 -> WSK3 + * @todo Take into account walking vs tele and adjust distance check accordingly + */ + + /** @type {Exit[]} */ + let exits = getArea().exits; + let WS1 = exits.find(t => t.target === sdk.areas.WorldstoneLvl1); + let WS3 = exits.find(t => t.target === sdk.areas.WorldstoneLvl3); + let wpToWS3 = WS3.distance; + let ws1ToWS3 = getDistance(WS1, WS3); + + Attack.clearLevel(Config.ClearType); + Pather.moveToExit(sdk.areas.WorldstoneLvl1, true) && Attack.clearLevel(Config.ClearType); + if (wpToWS3 < ws1ToWS3 + Pather.getDistanceToExit(me.area, sdk.areas.WorldstoneLvl2)) { + console.log("Going to town to start from WSK2 waypoint."); + Town.goToTown(); + Pather.useWaypoint(sdk.areas.WorldstoneLvl2); + } else { + Pather.moveToExit(sdk.areas.WorldstoneLvl2, true); + } + + Pather.moveToExit(sdk.areas.WorldstoneLvl3, true) && Attack.clearLevel(Config.ClearType); + + return true; + }, + { + startArea: sdk.areas.WorldstoneLvl2 + } +); diff --git a/d2bs/kolbot/libs/starter/AdvancedConfig.js b/d2bs/kolbot/libs/starter/AdvancedConfig.js new file mode 100644 index 000000000..8e8c23a19 --- /dev/null +++ b/d2bs/kolbot/libs/starter/AdvancedConfig.js @@ -0,0 +1,46 @@ +/** +* @filename AdvancedConfig.js +* @author theBGuy +* @desc Profile specific settings for entry scripts. +* @note For general and global settings @see StarterConfig.js +* +*/ + +(function (module) { + module.exports = { + /* Features: + Override channel for each profile, Override join delay for each profile + Override default values for JoinChannel, FirstJoinMessage, AnnounceGames and AfterGameMessage per profile + + * Format *: + "Profile Name": {JoinDelay: number_of_seconds} + or + "Profile Name": {JoinChannel: "channel name"} + or + "Profile Name": {JoinChannel: "channel name", JoinDelay: number_of_seconds} + + * Example * (don't edit this - it's just an example): + + "MyProfile1": {JoinDelay: 3}, + "MyProfile2": {JoinChannel: "some channel"}, + "MyProfile3": {JoinChannel: "some other channel", JoinDelay: 11} + "MyProfile4": {AnnounceGames: true, AnnounceMessage: "Joining game"} // announce game you are joining + + "Profile Name": { + JoinChannel: "channel name", + FirstJoinMessage: "first message", -OR- ["join msg 1", "join msg 2"], + AnnounceGames: true, + AfterGameMessage: "message after a finished run" -OR- ["msg 1", msg 2"] + } + */ + + // Put your lines under this one. Multiple entries are separated by commas. No comma after the last one. + + "Test": { + JoinChannel: "op nnqry", + JoinDelay: 3, + AnnounceGames: true, + AnnounceMessage: "Joining game" // output: Joining game Baals-23 + }, + }; +})(module); diff --git a/d2bs/kolbot/libs/starter/StarterConfig.js b/d2bs/kolbot/libs/starter/StarterConfig.js new file mode 100644 index 000000000..02081b3be --- /dev/null +++ b/d2bs/kolbot/libs/starter/StarterConfig.js @@ -0,0 +1,44 @@ +/** +* @filename StarterConfig.js +* @author theBGuy +* @desc Global settings for entry scripts +* @note For profile specific settings and overrides @see AdvancedConfig.js +* +*/ + +(function (module) { + module.exports = { + MinGameTime: 360, // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby + PingQuitDelay: 30, // Time in seconds to wait in lobby after quitting due to high ping + CreateGameDelay: rand(5, 15), // Seconds to wait before creating a new game + ResetCount: 999, // Reset game count back to 1 every X games. + CharacterDifference: 99, // Character level difference. Set to false to disable character difference. + MaxPlayerCount: 8, // Max amount of players in game between 1 and 8 + GameDescription: "", // Game description when creating a game + StopOnDeadHardcore: true, // Stop profile character has died on hardcore mode + + // ChannelConfig can override these options for individual profiles. + JoinChannel: "", // Default channel. + FirstJoinMessage: "", // Default join message. Can be an array of messages + ChatActionsDelay: 2, // Seconds to wait in lobby before entering a channel + AnnounceGames: false, // Default value + AnnounceMessage: "", // Message to announce before making game + AfterGameMessage: "", // Default message after a finished game. Can be an array of messages + + InvalidPasswordDelay: 10, // Minutes to wait after getting Invalid Password message + VersionErrorDelay: rand(5, 30), // Seconds to wait after 'unable to identify version' message + SwitchKeyDelay: 5, // Seconds to wait before switching a used/banned key or after realm down + CrashDelay: rand(120, 150), // Seconds to wait after a d2 window crash + FTJDelay: 120, // Seconds to wait after failing to create a game + RealmDownDelay: 3, // Minutes to wait after getting Realm Down message + UnableToConnectDelay: 5, // Minutes to wait after Unable To Connect message + TCPIPNoHostDelay: 5, // Seconds to wait after Cannot Connect To Server message + CDKeyInUseDelay: 5, // Minutes to wait before connecting again if CD-Key is in use. + ConnectingTimeout: 60, // Seconds to wait before cancelling the 'Connecting...' screen + PleaseWaitTimeout: 60, // Seconds to wait before cancelling the 'Please Wait...' screen + WaitInLineTimeout: 3600, // Seconds to wait before cancelling the 'Waiting in Line...' screen + WaitOutQueueRestriction: true, // Wait out queue if we are restricted, queue time > 10000 + WaitOutQueueExitToMenu: false, // Wait out queue restriction at D2 Splash screen if true, else wait out in lobby + GameDoesNotExistTimeout: 30, // Seconds to wait before cancelling the 'Game does not exist.' screen + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/automule/AutoMule.js b/d2bs/kolbot/libs/systems/automule/AutoMule.js new file mode 100644 index 000000000..6108aa460 --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/AutoMule.js @@ -0,0 +1,669 @@ +/* eslint-disable max-len */ +/** +* @filename AutoMule.js +* @author kolton, theBGuy +* @desc Main driver for Mule system +* For Mules setup @see MuleConfig.js +* For TorchAnniMules setup @see TorchAnniMules.js +* +*/ + +const AutoMule = { + /** @type {Object.} */ + Mules: Object.assign({}, + require("./config/MuleConfig", null, false) + ), + + /** @type {Object.} */ + TorchAnniMules: Object.assign({}, + require("./config/TorchAnniMules", null, false) + ), + + inGame: false, + check: false, + torchAnniCheck: false, + gids: new Set(), + baseGids: new Set(), + + // ################################## // + /* ##### Master/Muler Functions ##### */ + // ################################## // + + /** + * Get mule and torchanni mule info if it exists + * @returns {muleObj | {}} + */ + getInfo: function () { + let info; + + for (let i in this.Mules) { + if (this.Mules.hasOwnProperty(i)) { + for (let profile of this.Mules[i].enabledProfiles) { + if (String.isEqual(profile, "all") || String.isEqual(profile, me.profile)) { + !info && (info = {}); + info.muleInfo = this.Mules[i]; + + break; + } + } + } + } + + for (let i in this.TorchAnniMules) { + if (this.TorchAnniMules.hasOwnProperty(i)) { + for (let profile of this.TorchAnniMules[i].enabledProfiles) { + if (String.isEqual(profile, "all") || String.isEqual(profile, me.profile)) { + !info && (info = {}); + info.torchMuleInfo = this.TorchAnniMules[i]; + + break; + } + } + } + } + + return info; + }, + + muleCheck: function () { + let info = this.getInfo(); + + if (info && info.hasOwnProperty("muleInfo")) { + let items = this.getMuleItems(); + + if (info.muleInfo.hasOwnProperty("usedStashTrigger") && info.muleInfo.hasOwnProperty("usedInventoryTrigger") + && Storage.Inventory.UsedSpacePercent() >= info.muleInfo.usedInventoryTrigger + && Storage.Stash.UsedSpacePercent() >= info.muleInfo.usedStashTrigger && items.length > 0) { + D2Bot.printToConsole("MuleCheck triggered!", sdk.colors.D2Bot.DarkGold); + + return true; + } + + for (let i = 0; i < items.length; i += 1) { + if (this.matchItem(items[i], Config.AutoMule.Trigger)) { + D2Bot.printToConsole("MuleCheck triggered!", sdk.colors.D2Bot.DarkGold); + return true; + } + } + } + + return false; + }, + + /** + * Find a mule that matches our wanted check + * @returns {muleObj | false} + */ + getMule: function () { + let info = this.getInfo(); + + if (info) { + if (this.check && info.hasOwnProperty("muleInfo")) return info.muleInfo; + if (this.torchAnniCheck && info.hasOwnProperty("torchMuleInfo")) return info.torchMuleInfo; + } + + return false; + }, + + outOfGameCheck: function () { + if (!this.check && !this.torchAnniCheck) return false; + + let muleObj = this.getMule(); + if (!muleObj) return false; + + function muleCheckEvent (mode, msg) { + mode === 10 && (muleInfo = JSON.parse(msg)); + } + + let stopCheck = false; + let once = false; + let muleInfo = { status: "" }; + let failCount = 0; + let Controls = require("../../modules/Control"); + + if (!muleObj.continuousMule || !muleObj.skipMuleResponse) { + addEventListener("copydata", muleCheckEvent); + } + + if (muleObj.continuousMule) { + D2Bot.printToConsole("Starting mule.", sdk.colors.D2Bot.DarkGold); + D2Bot.start(muleObj.muleProfile); + } else { + D2Bot.printToConsole( + "Starting " + (this.torchAnniCheck === 2 ? "anni " : this.torchAnniCheck === 1 ? "torch " : "") + + "mule profile: " + muleObj.muleProfile, + sdk.colors.D2Bot.DarkGold + ); + } + + const mulePayload = JSON.stringify({ profile: me.profile, mode: this.torchAnniCheck || 0 }); + + MainLoop: + while (true) { + // Set status to ready if using continuous mule with no response check + if (muleObj.continuousMule && muleObj.skipMuleResponse) { + muleInfo.status = "ready"; + + // If nothing received our copy data start the mule profile + } else if (!sendCopyData(null, muleObj.muleProfile, 10, mulePayload) && !muleObj.continuousMule) { + // if the mule profile isn't already running and there is a profile to be stopped, stop it before starting the mule profile + if (!stopCheck && muleObj.stopProfile && !String.isEqual(me.profile, muleObj.stopProfile)) { + D2Bot.stop(muleObj.stopProfile, muleObj.stopProfileKeyRelease); + stopCheck = true; + delay(2000); // prevents cd-key in use error if using -skiptobnet on mule profile + } + + D2Bot.start(muleObj.muleProfile); + } + + delay(1000); + + switch (muleInfo.status) { + case "loading": + if (!muleObj.continuousMule && !stopCheck && muleObj.stopProfile + && !String.isEqual(me.profile, muleObj.stopProfile)) { + D2Bot.stop(muleObj.stopProfile, muleObj.stopProfileKeyRelease); + + stopCheck = true; + } + + failCount += 1; + + break; + case "busy": + case "begin": + D2Bot.printToConsole("Mule profile is busy.", sdk.colors.D2Bot.Red); + + break MainLoop; + case "ready": + Starter.LocationEvents.openJoinGameWindow(); + + delay(2000); + + AutoMule.inGame = true; + me.blockMouse = true; + + try { + joinGame(muleObj.muleGameName[0], muleObj.muleGameName[1]); + } catch (joinError) { + delay(100); + } + + me.blockMouse = false; + + // Untested change 11.Feb.14. + for (let i = 0; i < 8; i += 1) { + delay(1000); + + if (me.ingame && me.gameReady) { + break MainLoop; + } + } + + if (!once && getLocation() === sdk.game.locations.GameIsFull) { + Controls.CreateGameWindow.click(); + Starter.LocationEvents.openJoinGameWindow(); + // how long should we wait? + once = true; + let date = new Date(); + let dateString = "[" + new Date( + date.getTime() + Time.minutes(3) - (date.getTimezoneOffset() * 60000) + ).toISOString().slice(0, -5).replace(/-/g, "/").replace("T", " ") + "]"; + console.log("Game is full so lets hangout for a bit before we try again. Next attempt at " + dateString); + } + + if (muleObj.continuousMule && muleObj.skipMuleResponse && !me.ingame) { + D2Bot.printToConsole("Unable to join mule game", sdk.colors.D2Bot.Red); + + break MainLoop; + } + + break; + default: + failCount += 1; + + break; + } + + if (failCount >= 260) { + D2Bot.printToConsole("No response from mule profile.", sdk.colors.D2Bot.Red); + + break; + } + } + + removeEventListener("copydata", muleCheckEvent); + + while (me.ingame) { + delay(1000); + } + + AutoMule.inGame = false; + AutoMule.check = false; + AutoMule.torchAnniCheck = false; + + // No response - stop mule profile + if (!muleObj.continuousMule) { + if (failCount >= 60) { + D2Bot.stop(muleObj.muleProfile, true); + delay(1000); + } + + if (stopCheck && muleObj.stopProfile) { + D2Bot.start(muleObj.stopProfile); + } + } + + return true; + }, + + inGameCheck: function () { + let muleObj, tick; + let begin = false; + let timeout = Time.minutes(4); // Ingame mule timeout + let status = "muling"; + + // Single player + if (!me.gamename) return false; + + let info = this.getInfo(); + + // Profile is not a part of AutoMule + if (!info) return false; + + // Profile is not in mule or torch mule game + if (!((info.hasOwnProperty("muleInfo") && String.isEqual(me.gamename, info.muleInfo.muleGameName[0])) + || (info.hasOwnProperty("torchMuleInfo") && String.isEqual(me.gamename, info.torchMuleInfo.muleGameName[0])))) { + return false; + } + + function dropStatusEvent (mode, msg) { + if (mode === 10) { + switch (JSON.parse(msg).status) { + case "report": // reply to status request + sendCopyData(null, muleObj.muleProfile, 12, JSON.stringify({ status: status })); + + break; + case "quit": // quit command + status = "quit"; + + break; + } + } + } + + function muleModeEvent (msg) { + switch (msg) { + case "2": + case "1": + AutoMule.torchAnniCheck = Number(msg); + + break; + case "0": + AutoMule.check = true; + } + } + + try { + addEventListener("scriptmsg", muleModeEvent); + scriptBroadcast("getMuleMode"); + delay(500); + + if (!this.check && !this.torchAnniCheck) { + throw new Error("Error - Unable to determine mule mode"); + } + + muleObj = this.getMule(); + me.maxgametime = 0; + + !muleObj.continuousMule && addEventListener("copydata", dropStatusEvent); + + if (!Town.goToTown(1)) { + throw new Error("Error - Failed to go to Act 1"); + } + + Town.move("stash"); + + if (muleObj.continuousMule) { + console.log("ÿc4AutoMuleÿc0: Looking for valid mule"); + tick = getTickCount(); + + while (getTickCount() - tick < timeout) { + if (this.verifyMulePrefix(muleObj.charPrefix)) { + console.log("ÿc4AutoMuleÿc0: Found valid mule"); + begin = true; + + break; + } + + delay(2000); + } + + if (!begin) { + throw new Error("Error - Unable to find mule character"); + } + } else { + console.debug("MuleProfile :: " + muleObj.muleProfile); + sendCopyData(null, muleObj.muleProfile, 11, "begin"); + } + + let gameType = this.torchAnniCheck === 2 + ? " anni" + : this.torchAnniCheck === 1 + ? " torch" + : ""; + console.log("ÿc4AutoMuleÿc0: In" + gameType + " mule game."); + D2Bot.updateStatus("AutoMule: In" + gameType + " mule game."); + + if (this.torchAnniCheck === 2) { + this.dropCharm(true); + } else if (this.torchAnniCheck === 1) { + this.dropCharm(false); + } else { + this.dropStuff(); + } + + status = "done"; + tick = getTickCount(); + + while (true) { + if (muleObj.continuousMule) { + if (this.isFinished()) { + D2Bot.printToConsole("Done muling.", sdk.colors.D2Bot.DarkGold); + status = "quit"; + } else { + delay(5000); + } + } + + if (status === "quit") { + break; + } + + if (getTickCount() - tick > timeout) { + if (Misc.getPlayerCount() > 1) { + // we aren't alone currently so chill for a bit longer + tick = getTickCount(); + Packet.questRefresh(); // to prevent disconnect from idleing + } else { + D2Bot.printToConsole("Mule didn't rejoin. Picking up items.", sdk.colors.D2Bot.Red); + Misc.useItemLog = false; // Don't log items picked back up in town. + Pickit.pickItems(); + + break; + } + } + + delay(500); + } + + return true; + } catch (e) { + console.error(e); + + return false; + } finally { + removeEventListener("scriptmsg", muleModeEvent); + removeEventListener("copydata", dropStatusEvent); + + if (muleObj && !muleObj.continuousMule) { + D2Bot.stop(muleObj.muleProfile, true); + delay(1000); + muleObj.stopProfile && D2Bot.start(muleObj.stopProfile); + } + + delay(2000); + quit(); + } + }, + + /** + * finished if no items are on ground + */ + isFinished: function () { + let item = Game.getItem(); + + if (item) { + do { + // check if the items we dropped are on the ground still + if (getDistance(me, item) < 20 + && item.onGroundOrDropping + && AutoMule.gids.has(item.gid)) { + return false; + } + } while (item.getNext()); + } + + // we are finished so reset gid list + AutoMule.gids.clear(); + + return true; + }, + + /** + * make sure mule character is in game + * @param {string} mulePrefix + */ + verifyMulePrefix: function (mulePrefix) { + try { + let player = getParty(); + + if (player) { + let regex = new RegExp(mulePrefix, "i"); + + do { + // case insensitive matching + if (player.name.match(regex)) { + return true; + } + } while (player.getNext()); + } + } catch (e) { + delay(100); + } + + return false; + }, + + /** + * Transfer items to waiting mule + * @returns {boolean} + */ + dropStuff: function () { + if (!Town.openStash()) return false; + + let items = (this.getMuleItems() || []); + if (items.length === 0) return false; + items.forEach(item => AutoMule.gids.add(item.gid)); + + D2Bot.printToConsole("AutoMule: Transfering " + items.length + " items.", sdk.colors.D2Bot.DarkGold); + D2Bot.printToConsole("AutoMule: " + JSON.stringify(items.map(i => i.prettyPrint)), sdk.colors.D2Bot.DarkGold); + + items.forEach(item => item.drop()); + delay(1000); + me.cancel(); + + // clean up stash + Storage.Stash.SortItems(); + me.cancelUIFlags(); + + return true; + }, + + /** + * @param {ItemUnit} item + * @param {(number | string | ((item: ItemUnit) => boolean))[]} list + * @returns {boolean} + */ + matchItem: function (item, list) { + const parsedPickit = []; + const classIDs = []; + + for (let check of list) { + let info = { + file: "Character Config", + line: check + }; + + // classids + if (typeof check === "number") { + classIDs.push(check); + } else if (typeof check === "string") { + // pickit entries + let parsedLine = NTIP.ParseLineInt(check, info); + parsedLine && parsedPickit.push(parsedLine); + } else if (typeof check === "function") { + if (check(item)) { + return true; + } + } + } + + return (classIDs.includes(item.classid) || NTIP.CheckItem(item, parsedPickit)); + }, + + /** + * get a list of items to mule + * @returns {ItemUnit[] | false} + */ + getMuleItems: function () { + let info = this.getInfo(); + if (!info || !info.hasOwnProperty("muleInfo")) return false; + + const muleOrphans = !!(info.muleInfo.hasOwnProperty("muleOrphans") && info.muleInfo.muleOrphans); + + /** @param {ItemUnit} item */ + const isAKey = function (item) { + return [ + sdk.items.quest.KeyofTerror, + sdk.items.quest.KeyofHate, + sdk.items.quest.KeyofDestruction + ].includes(item.classid); + }; + + /** + * check if wanted by any of the systems + * @param {ItemUnit} item + * @returns {boolean} if item is wanted by various systems + */ + const isWanted = function (item) { + return (AutoMule.cubingIngredient(item) + || AutoMule.runewordIngredient(item) + || AutoMule.utilityIngredient(item) + ); + }; + + let items = me.getItemsEx() + .filter(function (item) { + // we don't mule items that are equipped or are junk + if (!item.isInStorage || Town.ignoreType(item.itemType)) return false; + // don't mule excluded items + if (AutoMule.matchItem(item, Config.AutoMule.Exclude)) return false; + // Don't mule cube/torch/annihilus + if (item.isAnni || item.isTorch || item.classid === sdk.quest.item.Cube) return false; + // don't mule items in locked spots + if (item.isInInventory && Storage.Inventory.IsLocked(item, Config.Inventory)) return false; + // don't mule items wanted by one of the various systems - checks that it's not on the force mule list + if (isWanted(item) && !AutoMule.matchItem(item, Config.AutoMule.Force.concat(Config.AutoMule.Trigger))) { + return false; + } + // don't mule keys if part of torchsystem + if (isAKey(item) && TorchSystem.getFarmers() && TorchSystem.isFarmer()) return false; + // we've gotten this far, mule items that are on the force list + if (AutoMule.matchItem(item, Config.AutoMule.Force.concat(Config.AutoMule.Trigger))) return true; + // alright that handles the basics -- now normal pickit check + let pResult = Pickit.checkItem(item).result; + // if it's a junk item, we don't want it + if ([Pickit.Result.UNID, Pickit.Result.UNWANTED, Pickit.Result.TRASH].includes(pResult)) { + return (item.isInStash && muleOrphans); + } + // we've made it this far, we want it + return true; + }); + + return items; + }, + + /** + * Wanted by CraftingSystem + * @param {ItemUnit} item + * @returns {boolean} + */ + utilityIngredient: function (item) { + if (!item) return false; + return CraftingSystem.validGids.includes(item.gid); + }, + + /** + * check if an item is a cubing ingredient + * @param {ItemUnit} item + * @returns {boolean} + */ + cubingIngredient: function (item) { + if (!item) return false; + + return Cubing.validIngredients.some(function (ingred) { + return (item.gid === ingred.gid); + }); + }, + + /** + * check if an item is a runeword ingredient - rune, empty base or bad rolled base + * @param {ItemUnit} item + * @returns {boolean} + */ + runewordIngredient: function (item) { + if (!item) return false; + if (Runewords.validGids.includes(item.gid)) return true; + + if (!this.baseGids.size) { + for (let i = 0; i < Config.Runewords.length; i += 1) { + const [runeword, base, ethFlag] = Config.Runewords[i]; + let baseItem = (Runewords.getBase(runeword, base, (ethFlag || 0)) + || Runewords.getBase(runeword, base, (ethFlag || 0), true) + ); + baseItem && this.baseGids.add(baseItem.gid); + } + } + + return this.baseGids.has(item.gid); + }, + + /** + * Drop Anni or Torch + * @param {boolean} dropAnni + * @returns {boolean} + */ + dropCharm: function (dropAnni) { + if (!Town.openStash()) return false; + + let item; + let items = me.getItemsEx() + .filter(function (item) { + return item.isInStorage && item.isCharm && item.unique; + }); + if (!items.length) return false; + + if (dropAnni) { + item = items.find(function (item) { + return item.isAnni && !Storage.Inventory.IsLocked(item, Config.Inventory); + }); + if (!item) return false; + + D2Bot.printToConsole("AutoMule: Transfering Anni.", sdk.colors.D2Bot.DarkGold); + } else { + item = items.find(function (item) { + return item.isTorch; + }); + if (!item) return false; + + D2Bot.printToConsole("AutoMule: Transfering Torch.", sdk.colors.D2Bot.DarkGold); + } + + item.drop(); + delay(1000); + me.cancel() && me.cancel(); + + return true; + }, +}; diff --git a/d2bs/kolbot/libs/systems/automule/Mule.js b/d2bs/kolbot/libs/systems/automule/Mule.js new file mode 100644 index 000000000..d58897480 --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/Mule.js @@ -0,0 +1,497 @@ +/** +* @filename Mule.js +* @author theBGuy +* @desc Main lib for the Mule +* +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/mulelogger/MuleLogger")} +*/ + +/** + * Mule Data object manipulates external mule datafile + */ +const MuleData = { + _default: { + account: "", + accNum: 0, + character: "", + charNum: 0, + fullChars: [], + torchChars: [] + }, + fileName: "", + // create a new mule datafile + create: function () { + let string = JSON.stringify(this._default); + FileTools.writeText(this.fileName, string); + }, + + // read data from the mule datafile and return the data object + read: function () { + try { + let string = FileTools.readText(this.fileName); + let obj = JSON.parse(string); + + return obj; + } catch (e) { + console.error(e); + this.create(); + + return this._default; + } + }, + + // write a data object to the mule datafile + write: function (obj) { + let string = JSON.stringify(obj); + FileTools.writeText(this.fileName, string); + }, + + // set next account - increase account number in mule datafile + nextAccount: function () { + Starter.makeAccount = true; + let obj = MuleData.read(); + + obj.accNum += 1; + obj.account = Mule.obj.accountPrefix + obj.accNum; + + MuleData.write(Object.assign(this._default, { accNum: obj.accNum, account: obj.account })); + + return obj.account; + }, + + nextChar: function () { + console.trace(); + let charSuffix = ""; + const charNumbers = "abcdefghijklmnopqrstuvwxyz"; + const obj = MuleData.read(); + + // dirty + obj.charNum > 25 && (obj.charNum = 0); + let num = obj.accNum.toString(); + + for (let i = 0; i < num.length; i++) { + charSuffix += charNumbers[parseInt(num[i], 10)]; + } + + charSuffix += charNumbers[obj.charNum]; + obj.charNum = obj.charNum + 1; + obj.character = Mule.obj.charPrefix + charSuffix; + + MuleData.write(obj); + + return obj.character; + }, +}; + +const Mule = { + /** @type {muleObj} */ + obj: null, + minGameTime: 0, + maxGameTime: 0, + continuous: false, + makeNext: false, + next: false, + refresh: false, + master: "", + mode: -1, + fileName: "", + startTick: 0, + status: "loading", + statusString: "", + masterStatus: { status: "" }, + droppedGids: new Set(), + + waitForMaster: function () { + console.log("Waiting for muler"); + // forever alone check? + Misc.poll(() => Mule.status === "begin", Time.minutes(3), 100); + + if (Mule.status !== "begin") { + if (Mule.foreverAlone() && !getUnits(sdk.unittype.Item).filter(i => i.onGroundOrDropping).length) { + D2Bot.printToConsole("Nobody joined - stopping.", sdk.colors.D2Bot.Red); + D2Bot.stop(me.profile, true); + } else { + console.debug("No response from master, but items on ground. Setting status to begin."); + Mule.status = "begin"; + } + } + + me.overhead("begin"); + console.debug("begin"); + }, + /** + * @todo check if there are any other profiles that need to mule while we are already in game? + */ + done: function () { + !Mule.obj.onlyLogWhenFull && MuleLogger.logChar(); + + let obj = MuleData.read(); + + if (Mule.checkAnniTorch() && obj.torchChars.indexOf(me.name) === -1) { + obj.torchChars.push(me.name); + } + + MuleData.write(obj); + D2Bot.printToConsole("Done muling.", sdk.colors.D2Bot.DarkGold); + console.log("Done muling"); + sendCopyData(null, Mule.master, 10, JSON.stringify({ status: "quit" })); + D2Bot.stop(me.profile, true); + }, + + nextChar: function () { + MuleLogger.logChar(); + delay(500); + + // Mule.makeNext = true; + let obj = MuleData.read(); + + if (Mule.checkAnniTorch() && obj.torchChars.indexOf(me.name) === -1) { + obj.torchChars.push(me.name); + } + + if (obj.fullChars.indexOf(me.name) === -1) { + obj.fullChars.push(me.name); + MuleData.write(obj); + } + let nextMule = MuleData.nextChar(); + D2Bot.printToConsole("Mule full, getting next character (" + nextMule + " )", sdk.colors.D2Bot.DarkGold); + + if (Mule.minGameTime && getTickCount() - Mule.startTick < Mule.minGameTime * 1000) { + while (getTickCount() - Mule.startTick < Mule.minGameTime * 1000) { + me.overhead( + "Stalling for " + Math.round(((Mule.startTick + (Mule.minGameTime * 1000)) - getTickCount()) / 1000) + + " Seconds" + ); + delay(1000); + } + } + + Mule.quit(); + }, + + quit: function () { + Mule.cursorCheck(); + console.log("ÿc8Mule game duration ÿc2" + (Time.format(getTickCount() - me.gamestarttime))); + + try { + quit(); + } finally { + while (me.ingame) { + delay(100); + } + } + + return true; + }, + foreverAlone: function () { + let party = getParty(); + + if (party) { + do { + if (party.name !== me.name) return false; + } while (party.getNext()); + } + + return true; + }, + + checkAnniTorch: function () { + while (!me.gameReady) { + delay(500); + } + + return me.getItemsEx() + .some(i => i.isInStorage && (i.isAnni || i.isTorch)); + }, + + stashItems: function () { + me.getItemsEx() + .filter(item => item.isInInventory) + .sort((a, b) => (b.sizex * b.sizey - a.sizex * a.sizey)) + .forEach(item => { + Storage.Stash.CanFit(item) && Storage.Stash.MoveTo(item); + }); + + return true; + }, + + cursorCheck: function () { + let cursorItem = Game.getCursorUnit(); + + if (cursorItem) { + if (!Storage.Inventory.CanFit(cursorItem) || !Storage.Inventory.MoveTo(cursorItem)) { + console.warn("Can't place " + cursorItem.prettyPrint + " in inventory"); + cursorItem.drop(); + } + } + + return true; + }, + + getGroundItems: function () { + return getUnits(sdk.unittype.Item) + .filter(i => i.distance < 20 && i.onGroundOrDropping && !Town.ignoreType(i.itemType)); + }, + + pickItems: function () { + /** @type {ItemUnit[]} */ + let list = []; + let waitTick = getTickCount(); + let rval = "ready"; + let override = false; + let pickedUniqueCharm = false; + + if (!Mule.clearedJunk) { + me.getItemsEx() + .filter(function (item) { + return (item.isInInventory + && Town.ignoreType(item.itemType) + && (Mule.mode === 0 || item.classid !== sdk.items.ScrollofIdentify) + ); + }) + .forEach(function (item) { + try { + item.drop(); + } catch (e) { + console.warn("Failed to drop an item."); + } + }); + Mule.clearedJunk = true; // only do this once + } + + while (me.gameReady) { + if (Mule.masterStatus.status === "done" || Mule.continuous || override) { + override = false; + let item = Game.getItem(); + + if (item) { + list = Mule.getGroundItems(); + Mule.droppedGids.forEach(function (gid) { + if (gid > 0 && !list.some(i => i.gid === gid)) { + item = Game.getItem(-1, -1, gid); + if (item && !Town.ignoreType(item.itemType)) { + list.push(item); + } + } + }); + Mule.droppedGids.clear(); + } + + // If and only if there is nothing left are we "done" + if (list.length === 0) { + rval = Mule.continuous ? "ready" : "done"; + + break; + } + + /** + * pick large items first by sorting items by size in descending order + * and move gheed's charm to the end of the list + */ + list.sort(function (a, b) { + if (a.isGheeds && !Pickit.canPick(a)) return 1; + if (b.isGheeds && !Pickit.canPick(b)) return -1; + + return (b.sizex * b.sizey - a.sizex * a.sizey); + }); + + while (list.length > 0) { + item = list.shift(); + let canFit = Storage.Inventory.CanFit(item); + + // Torch and Anni handling + if (Mule.mode > 0 && (item.isAnni || item.isTorch) && !Pickit.canPick(item)) { + let msg = item.classid === sdk.items.LargeCharm + ? "Mule already has a Torch." + : "Mule already has a Anni."; + D2Bot.printToConsole(msg, sdk.colors.D2Bot.DarkGold); + rval = "next"; + } + + // Gheed's Fortune handling + if (item.isGheeds && !Pickit.canPick(item)) { + D2Bot.printToConsole("Mule already has Gheed's.", sdk.colors.D2Bot.DarkGold); + rval = "next"; + } + + if (!canFit && Mule.stashItems()) { + canFit = Storage.Inventory.CanFit(item); + } + + canFit + ? Pickit.pickItem(item) + : (rval = "next"); + + // torch and anni handling + if (Mule.mode > 0 && Mule.continuous && (item.isAnni || item.isTorch) && item.isInStorage) { + // we picked up a torch or anni so move to next mule once we are done + pickedUniqueCharm = true; + } + } + + if (rval === "next") { + break; + } + } else { + if (!Mule.continuous) { + sendCopyData(null, Mule.master, 10, JSON.stringify({ status: "report" })); + Misc.poll(() => Mule.masterStatus.status === "done", Time.seconds(5), 50); + } else { + if (getTickCount() - waitTick > Time.minutes(10)) { + break; + } + } + // safety check + if (getTickCount() - waitTick > Time.minutes(3) && Mule.getGroundItems().length) { + override = true; + } + } + + delay(500); + } + + if (pickedUniqueCharm) { + return "next"; + } + + return rval; + }, + + /** + * @param {number} time + */ + ingameTimeout: function (time) { + let tick = getTickCount(); + + while (getTickCount() - tick < time) { + if (me.ingame && me.gameReady && !!me.area) break; + + // game doesn't exist, might need more locs + if (getLocation() === sdk.game.locations.GameDoesNotExist) { + break; + } + + delay(100); + } + + return (me.ingame && me.gameReady && !!me.area); + }, + + /** + * @param {{ profile: string, mode: number }} info + * @returns {{ profile: string, mode: number }} master info + */ + getMaster: function (info) { + const muleObj = info.mode === 1 + ? AutoMule.TorchAnniMules + : AutoMule.Mules; + + for (let i in muleObj) { + if (muleObj.hasOwnProperty(i)) { + const { enabledProfiles } = muleObj[i]; + if (!enabledProfiles.length) continue; + for (let profile of enabledProfiles) { + if (String.isEqual(profile, info.profile)) { + return { + profile: profile, + mode: info.mode + }; + } else if (profile === "all") { + return { + profile: info.profile, // set whoever is asking as master + mode: info.mode + }; + } + } + } + } + + return false; + }, + + /** + * @param {number} mode + * @param {string} master + * @param {boolean} continuous + * @returns {string} + */ + getMuleFilename: function (mode, master, continuous = false) { + mode = mode || 0; + let file; + const mule = mode > 0 ? AutoMule.TorchAnniMules : AutoMule.Mules; + + // Iterate through mule object + for (let i in mule) { + if (mule.hasOwnProperty(i)) { + const { + muleProfile, + enabledProfiles, + accountPrefix, + continuousMule, + } = mule[i]; + // Mule profile matches config + if (muleProfile && String.isEqual(muleProfile, me.profile) + && (continuous || enabledProfiles.includes(master) || enabledProfiles.includes("all"))) { + if (continuous && !continuousMule) continue; + file = mode === 0 + ? "logs/AutoMule." + i + ".json" + : "logs/TorchMule." + i + ".json"; + + // If file exists check for valid info + if (FileTools.exists(file)) { + try { + let jsonStr = FileTools.readText(file); + let jsonObj = JSON.parse(jsonStr); + + // Return filename containing correct mule info + if (accountPrefix && jsonObj.account && jsonObj.account.match(accountPrefix)) { + return file; + } + } catch (e) { + console.error(e); + } + } else { + return file; + } + } + } + } + + // File exists but doesn't contain valid info - remake + FileTools.remove(file); + + return file; + }, + + /** @returns {{ mode: number, obj: muleObj }[]} */ + getMuleInfo: function () { + const data = []; + /** + * @param {muleObj} muleObj + * @param {number} mode + */ + const checkObj = function (muleObj, mode) { + let { muleProfile } = muleObj; + if (muleProfile && String.isEqual(muleProfile, me.profile)) { + data.push({ + mode: mode, + obj: muleObj, + }); + } + }; + + for (let i in AutoMule.Mules) { + if (AutoMule.Mules.hasOwnProperty(i)) { + checkObj(AutoMule.Mules[i], 0); + } + } + + for (let i in AutoMule.TorchAnniMules) { + if (AutoMule.TorchAnniMules.hasOwnProperty(i)) { + checkObj(AutoMule.TorchAnniMules[i], 1); + } + } + return data; + }, +}; diff --git a/d2bs/kolbot/libs/systems/automule/config/MuleConfig.js b/d2bs/kolbot/libs/systems/automule/config/MuleConfig.js new file mode 100644 index 000000000..7d2fdcaca --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/config/MuleConfig.js @@ -0,0 +1,43 @@ +/** +* @filename MuleConfig.js +* @author theBGuy +* @desc Configuration profiles for AutoMule system, for TorchAnni specific @see TorchAnniMules.js +* +*/ + +(function (module) { + module.exports = { + "Mule1": { + muleProfile: "", // The name of mule profile in d2bot#. It will be started and stopped when needed. + accountPrefix: "", // Account prefix. Numbers added automatically when making accounts. + accountPassword: "", // Account password. + charPrefix: "", // Character prefix. Suffix added automatically when making characters. + realm: "", // Available options: "useast", "uswest", "europe", "asia" + expansion: true, + ladder: true, + hardcore: false, + charsPerAcc: 18, // Maximum number of mules to create per account (between 1 to 18) + + // Game name and password of the mule game. Never use the same game name as for mule logger. + muleGameName: ["", ""], // ["gamename", "password"] + + // List of profiles that will mule items. Example: enabledProfiles: ["profile 1", "profile 2"], + enabledProfiles: [""], + + // Stop a profile prior to muling. Useful when running 8 bots without proxies. + stopProfile: "", + stopProfileKeyRelease: false, // true = stopProfile key will get released on stop. useful when using 100% of your keys for botting. + + // Trigger muling at the end of a game if used space in stash and inventory is equal to or more than given percent. + usedStashTrigger: 80, + usedInventoryTrigger: 80, + + // Mule items that have been stashed at some point but are no longer in pickit. + muleOrphans: true, + // Continuous Mule settings + continuousMule: false, // Mule stays in game for continuous muling. muleProfile must be dedicated and started manually. + skipMuleResponse: false, // Skip mule response check and attempt to join mule game. Useful if mule is shared and/or ran on different system. + onlyLogWhenFull: false // Only log character when full, solves an issue with droppers attempting to use characters who are already in game + }, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/automule/config/TorchAnniMules.js b/d2bs/kolbot/libs/systems/automule/config/TorchAnniMules.js new file mode 100644 index 000000000..b2c117b33 --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/config/TorchAnniMules.js @@ -0,0 +1,45 @@ +/** +* @filename TorchAnniMules.js +* @author theBGuy +* @desc Torch/Anni specific mule profiles for AutoMule system +* +*/ + +/** + * @description Torch/Anni mules: + * - Torch is muled in OrgTorch script after finishing uber Tristram successfully or when starting OrgTorch script with a Torch already on the character. + * - Anni is muled after successfully killing Diablo in Palace Cellar level 3 using Config.KillDclone option or KillDClone script. + * - If a profile is listed in Torch/Anni mule's enabledProfiles list, it will also do a check to mule Anni at the end of each game. + * @note Anni that is in locked inventory slot will not be muled. + * @note Each mule will hold either a Torch or an Anni, but not both. As soon as the current mule has either one, a new one will be created. + */ +(function (module) { + module.exports = { + "Mule1": { + muleProfile: "", // The name of mule profile in d2bot#. It will be started and stopped when needed. + accountPrefix: "", // Account prefix. Numbers added automatically when making accounts. + accountPassword: "", // Account password. + charPrefix: "", // Character prefix. Suffix added automatically when making characters. + realm: "", // Available options: "useast", "uswest", "europe", "asia" + expansion: true, + ladder: true, + hardcore: false, + charsPerAcc: 8, // Maximum number of mules to create per account (between 1 to 18) + + // Game name and password of the mule game. Never use the same game name as for mule logger. + muleGameName: ["", ""], // ["gamename", "password"] + + // List of profiles that will mule items. Example: enabledProfiles: ["profile 1", "profile 2"], + enabledProfiles: [""], + + // Stop a profile prior to muling. Useful when running 8 bots without proxies. + stopProfile: "", + stopProfileKeyRelease: false, // true = stopProfile key will get released on stop. useful when using 100% of your keys for botting. + + // Continuous Mule settings + continuousMule: true, // Mule stays in game for continuous muling. muleProfile must be dedicated and started manually. + skipMuleResponse: true, // Skip mule response check and attempt to join mule game. Useful if mule is shared and/or ran on different system. + onlyLogWhenFull: true // Only log character when full, solves an issue with droppers attempting to use characters who are already in game + }, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/automule/main.js b/d2bs/kolbot/libs/systems/automule/main.js new file mode 100644 index 000000000..6d0f012fd --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/main.js @@ -0,0 +1,215 @@ +/** +* @filename main.js +* @author theBGuy +* @desc Executed upon game join, main thread for mule +* +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/mulelogger/MuleLogger")} +*/ + +js_strict(true); +include("critical.js"); // required + +// globals needed for core gameplay +includeCoreLibs(); + +// system libs +includeSystemLibs(); +include("systems/automule/Mule.js"); +include("systems/mulelogger/MuleLogger.js"); + +function main () { + D2Bot.init(); // Get D2Bot# handle + D2Bot.ingame(); + + while (!me.gameReady) { + delay(50); + } + + // load heartbeat if it isn't already running + let _heartbeat = getScript("threads/heartbeat.js"); + if (!_heartbeat || !_heartbeat.running) { + load("threads/heartbeat.js"); + } + + const Worker = require("../../modules/Worker"); + const Delta = new (require("../../modules/Deltas")); + + Worker.runInBackground.areaWatcher = (function () { + let areaTick = 0; + return function () { + // run area check every half second + if (getTickCount() - areaTick < 500) return true; + areaTick = getTickCount(); + // check that we are actually in game and that we've been there longer than a minute + if (getLocation() !== null || getTickCount() - me.gamestarttime < Time.minutes(1)) return true; + + if (me.ingame && me.gameReady && me.area > 0) { + if (me.area !== sdk.areas.RogueEncampment) { + console.warn("Preventing Suicide Walk! Current Area: " + me.area); + console.trace(); + + Mule.quit(); + } + } + + return true; + }; + })(); + + Worker.runInBackground.antiIdle = (function () { + let idleTick = 0; + return function () { + if (!me.ingame || getTickCount() - me.gamestarttime < Time.minutes(1) || !me.gameReady) return true; + if (idleTick === 0) { + idleTick = getTickCount() + Time.seconds(rand(1200, 1500)); + console.log("Anti-idle refresh in: (" + Time.format(idleTick - getTickCount()) + ")"); + } + if (me.gameReady) { + if (getTickCount() - idleTick > 0) { + Packet.questRefresh(); + idleTick += Time.seconds(rand(1200, 1500)); + console.log("Sent anti-idle packet, next refresh in: (" + Time.format(idleTick - getTickCount()) + ")"); + } + } else if (getLocation() !== null) { + idleTick = 0; + } + + return true; + }; + })(); + + // START + const EntryScript = getScript("D2BotMule.dbj"); + + Delta.track(() => Mule.status, () => EntryScript.send({ status: Mule.status })); + + addEventListener("itemaction", function (gid, mode, code) { + if (gid > 0 && mode === 2) { + console.log("gid: " + gid, " mode: " + mode + " code: " + code); + Mule.droppedGids.add(gid); + // Mule.status = "begin"; + } + }); + addEventListener("scriptmsg", function (msg) { + if (typeof msg === "object") { + if (msg.hasOwnProperty("obj")) { + // Object.assign(Mule, msg); + Mule.obj = msg.obj; + Mule.mode = msg.mode; + Mule.master = msg.master; + Mule.next = msg.next; + Mule.minGameTime = msg.minGameTime; + Mule.maxGameTime = msg.maxGameTime; + Mule.continuous = msg.obj.continuousMule; + MuleData.fileName = msg.fileName; + } + } + }); + addEventListener("copydata", function (mode, msg) { + switch (mode) { + case 10: // mule request + let obj = JSON.parse(msg); + + if (Mule.continuous) { + sendCopyData(null, obj.profile, 10, JSON.stringify({ status: "ready" })); + } else { + if (!Mule.master) { + let masterInfo = Mule.getMaster(obj); + + if (masterInfo) { + Mule.master = masterInfo.profile; + Mule.mode = masterInfo.mode; + } + } else { + // come back to this to allow multiple mulers + if (obj.profile === Mule.master) { + sendCopyData(null, Mule.master, 10, JSON.stringify({ status: Mule.status })); + } else { + sendCopyData(null, obj.profile, 10, JSON.stringify({ status: "busy" })); + } + } + } + + break; + case 11: // begin item pickup + Mule.status = "begin"; + + break; + case 12: // get master's status + Mule.masterStatus = JSON.parse(msg); + + break; + } + }); + EntryScript.send("mule_init"); + Misc.poll(() => Mule.obj !== null, Time.seconds(30), 100); + Mule.startTick = getTickCount(); + + if (Mule.next) { + // we had to make a new mule, if we are here then items need to be picked up + Mule.status = "begin"; + Mule.masterStatus = { status: "done" }; + } + + Mule.status !== "begin" && (Mule.status = "ready"); + Mule.recheckTick = getTickCount(); + + Town.goToTown(1); + Town.move("stash"); + + Storage.Init(); + + if (Mule.continuous) { + !Mule.obj.onlyLogWhenFull && MuleLogger.logChar(); + } + console.log("~~~Mule init complete~~~"); + // check the ground for items + if (Mule.getGroundItems().length > 0) { + Mule.status = "begin"; + } + + if (!Mule.continuous) { + Mule.waitForMaster(); + } + + while (me.ingame) { + if (Mule.status === "begin") { + Mule.status = Mule.pickItems(); + + switch (Mule.status) { + // done picking, tell the master to leave game and kill mule profile + case "done": + Mule.done(); + + return true; + // can't fit more items, get to next character or account + case "next": + EntryScript.send("next"); + Mule.nextChar(); + + return true; + } + } else if (Mule.droppedGids.size > 0 && Mule.status === "ready") { + Mule.status = "begin"; + } + + if (Town.getDistance("stash") > 10) { + Town.move("stash"); + } + + if (Mule.continuous) { + if (Mule.maxGameTime > 0 + && getTickCount() - me.gamestarttime > Time.minutes(Mule.maxGameTime) + && Mule.foreverAlone()) { + console.log("~~~MaxGameTime Reached~~~"); + EntryScript.send("refresh"); + Mule.quit(); + + break; + } + } + delay(1000); + } + return true; +} diff --git a/d2bs/kolbot/libs/systems/autorush/AutoRush.js b/d2bs/kolbot/libs/systems/autorush/AutoRush.js new file mode 100644 index 000000000..5fc0cef1c --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/AutoRush.js @@ -0,0 +1,1382 @@ +/** +* @filename AutoRush.js +* @author theBGuy +* @desc Scripts for the AutoRush system +* +*/ + +(function (module) { + const { + AutoRush, + RushModes, + RushConfig, + } = require("./RushConfig"); + + /** + * @param {string} msg + * @param {boolean} sayMsg + */ + const log = function (msg = "", sayMsg = true) { + console.log(msg); + sayMsg && say(msg); + }; + + /** + * @param {number} area + * @param {string} [nick] + * @returns {boolean} + */ + const playerIn = function (area, nick) { + !area && (area = me.area); + + let party = getParty(); + + if (party) { + do { + if (party.name !== me.name + && (!nick || String.isEqual(party.name, nick)) + && party.area === area) { + return true; + } + } while (party.getNext()); + } + + return false; + }; + + const bumperLvlReq = function () { + return [20, 40, 60][me.diff]; + }; + + /** + * @param {string} nick + * @returns {boolean} + */ + const bumperCheck = function (nick) { + return nick + ? Misc.findPlayer(nick).level >= bumperLvlReq() + : Misc.checkPartyLevel(bumperLvlReq()); + }; + + /** @param {Act} act */ + const playersInAct = function (act) { + !act && (act = me.act); + + let area = sdk.areas.townOfAct(act); + let party = getParty(); + + if (party) { + const myPartyId = party.partyid; + do { + if (party.partyid !== myPartyId) continue; + if (party.name !== me.name && party.area !== area) { + return false; + } + } while (party.getNext()); + } + + return true; + }; + + const cain = function () { + log("starting cain"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.DarkWood, true) && Precast.doPrecast(true); + + if (!Pather.moveToPreset(sdk.areas.DarkWood, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) { + throw new Error("Failed to move to Tree of Inifuss"); + } + + let tree = Game.getObject(sdk.quest.chest.InifussTree); + !!tree && tree.distance > 5 && Pather.moveToUnit(tree); + Attack.securePosition(me.x, me.y, 40, 3000, true); + !!tree && tree.distance > 5 && Pather.moveToUnit(tree); + Pather.makePortal(); + log(AutoRush.playersIn); + let tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(2)) { + if (tree.mode) { + break; + } + Attack.securePosition(me.x, me.y, 25, 1000); + } + + Pather.usePortal(1) || Town.goToTown(); + Pather.useWaypoint(sdk.areas.StonyField, true); + Precast.doPrecast(true); + Pather.moveToPresetMonster(sdk.areas.StonyField, sdk.monsters.preset.Rakanishu, { + offX: 10, offY: 10, pop: true + }); + const alphaPreset = Game.getPresetObject(sdk.areas.StonyField, sdk.quest.chest.StoneAlpha); + const StoneAlpha = alphaPreset.realCoords(); + Attack.securePosition(StoneAlpha.x, StoneAlpha.y, 40, 3000, true); + StoneAlpha.distance > 5 && Pather.moveToUnit(StoneAlpha); + Pather.makePortal(); + log(AutoRush.playersIn); + + tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(2)) { + if (Pather.getPortal(sdk.areas.Tristram) && Pather.usePortal(sdk.areas.Tristram)) { + break; + } + Attack.securePosition(StoneAlpha.x, StoneAlpha.y, 35, 1000); + } + + if (me.inArea(sdk.areas.Tristram)) { + Pather.moveTo(me.x, me.y + 6); + let gibbet = Game.getObject(sdk.quest.chest.CainsJail); + + if (gibbet && !gibbet.mode) { + if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.CainsJail, 0, 0, true, true)) { + throw new Error("Failed to move to Cain's Jail"); + } + + Attack.securePosition(gibbet.x, gibbet.y, 25, 3000); + Pather.makePortal(); + log(AutoRush.playersIn); + + tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(2)) { + if (gibbet.mode) { + break; + } + Attack.securePosition(me.x, me.y, 15, 1000); + } + } + } + + return true; + }; + + /** @param {string} [nick] */ + const andariel = function (nick) { + log("starting andariel"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.CatacombsLvl2, true) && Precast.doPrecast(true); + const safeNode = new PathNode(22582, 9612); + + if (!Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true) + || !Pather.move(safeNode)) { + throw new Error("andy failed"); + } + + Attack.securePosition(safeNode.x, safeNode.y, 40, 3000, true); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + if (playerIn(me.area, nick)) { + return true; + } + Pather.move(safeNode); + return false; + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Attack.kill(sdk.monsters.Andariel); + log(AutoRush.playersOut); + Pather.move(safeNode); + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(250); + } + + Pather.usePortal(null, me.name); + Town.goToTown(2); + for (let i = 0; i < 3; i++) { + log("a2"); + + let playersMoved = Misc.poll(function () { + return !playersInAct(2); + }, Time.seconds(30), Time.seconds(1)); + + if (playersMoved) { + break; + } + } + } + + return true; + }; + + /** @param {string} [nick] */ + const bloodraven = function (nick) { + log("starting bloodraven"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.ColdPlains, true) && Precast.doPrecast(true); + + if (!Pather.moveToPreset(sdk.areas.BurialGrounds, sdk.unittype.Monster, sdk.monsters.preset.BloodRaven, 30, 30)) { + throw new Error("bloodraven failed"); + } + + Attack.securePosition(me.x, me.y, 10, 1000); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + if (playerIn(me.area, nick)) { + return true; + } + Pather.moveTo(22582, 9612); + return false; + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Attack.kill(sdk.monsters.BloodRaven); + log(AutoRush.playersOut); + Pather.moveTo(22582, 9612); + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(250); + } + + Pather.usePortal(null, me.name); + Town.goToTown(2); + } + + return true; + }; + + /** @param {string} [nick] */ + const smith = function (nick) { + log("starting smith"); + if (Misc.findPlayer(nick).level < 8) { + log(nick + " you are not eligible for smith. You need to be at least level 8"); + + return false; + } + + Town.doChores(); + Pather.useWaypoint(sdk.areas.OuterCloister, true) && Precast.doPrecast(true); + if (!Pather.moveToPreset(sdk.areas.Barracks, sdk.unittype.Object, sdk.quest.chest.MalusHolder)) { + throw new Error("smith failed"); + } + Attack.securePosition(me.x, me.y, 30, 3000, true); + Pather.makePortal(); + log(AutoRush.playersIn); + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + Pather.usePortal(null, me.name); + return true; + }; + + /** @param {string} [nick] */ + const radament = function (nick) { + log("starting radament"); + + const moveIntoPos = function (unit, range) { + let coords = []; + let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); + const angles = [ + 0, 15, -15, 30, -30, 45, -45, 60, -60, + 75, -75, 90, -90, 105, -105, 120, -120, + 135, -135, 150, -150, 180 + ]; + + for (let i = 0; i < angles.length; i += 1) { + let coordx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * range + unit.x); + let coordy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * range + unit.y); + + try { + if (!(getCollision(unit.area, coordx, coordy) & 0x1)) { + coords.push({ + x: coordx, + y: coordy + }); + } + } catch (e) { + continue; + } + } + + if (coords.length > 0) { + coords.sort(Sort.units); + + return Pather.moveToUnit(coords[0]); + } + + return false; + }; + + Pather.useWaypoint(sdk.areas.A2SewersLvl2, true) && Precast.doPrecast(false); + Pather.moveToExit(sdk.areas.A2SewersLvl3, true); + + const radaPreset = Game.getPresetObject(sdk.areas.A2SewersLvl3, sdk.quest.chest.HoradricScrollChest); + const radaCoords = radaPreset.realCoords(); + + moveIntoPos(radaCoords, 50); + const rada = Misc.poll(function () { + return Game.getMonster(sdk.monsters.Radament); + }, 1500, 500); + + rada ? moveIntoPos(rada, 60) : console.log("radament unit not found"); + Attack.securePosition(me.x, me.y, 35, 3000); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Attack.kill(sdk.monsters.Radament); + + let book = Game.getItem(sdk.quest.item.BookofSkill); + const returnSpot = { + x: (book ? book.x : me.x), + y: (book ? book.y : me.y) + }; + + log(AutoRush.playersOut); + Pickit.pickItems(); + Attack.securePosition(returnSpot.x, returnSpot.y, 30, 3000); + + if (!Misc.poll(function () { + return !playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Pather.moveToUnit(returnSpot); + Pather.makePortal(); + log(AutoRush.allIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + Misc.poll(function () { + return !Game.getItem(sdk.quest.item.BookofSkill); + }, 30000, 1000); + + while (playerIn(me.area, nick)) { + delay(200); + } + } + + Pather.usePortal(null, null); + + return true; + }; + /** @param {string} [nick] */ + const cube = function (nick) { + if (me.normal) { + log("starting cube"); + Pather.useWaypoint(sdk.areas.HallsoftheDeadLvl2, true); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.HallsoftheDeadLvl3, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest)) { + throw new Error("cube failed"); + } + Attack.securePosition(me.x, me.y, 30, 3000, true); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + Pather.usePortal(null, me.name); + } + + return true; + }; + /** @param {string} [nick] */ + const amulet = function (nick) { + const exits = [sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2]; + log("starting amulet"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.LostCity, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit(exits, true) + || !Pather.moveTo(15044, 14045)) { + throw new Error("amulet failed"); + } + + Attack.securePosition(15044, 14045, 25, 3000, me.hell, me.hell); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + Pather.usePortal(null, me.name); + + return true; + }; + /** @param {string} [nick] */ + const staff = function (nick) { + log("starting staff"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.FarOasis, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3], true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest)) { + throw new Error("staff failed"); + } + + Attack.securePosition(me.x, me.y, 30, 3000, true); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + Pather.usePortal(null, me.name); + + return true; + }; + /** @param {string} [nick] */ + const summoner = function (nick) { + // right up 25449 5081 (25431, 5011) + // left up 25081 5446 (25011, 5446) + // right down 25830 5447 (25866, 5431) + // left down 25447 5822 (25431, 5861) + + log("starting summoner"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.ArcaneSanctuary, true) && Precast.doPrecast(true); + + const preset = Game.getPresetObject(sdk.areas.ArcaneSanctuary, sdk.quest.chest.Journal).realCoords(); + /** @type {PathNode} */ + let spot = {}; + + switch (preset.x) { + case 25011: + spot = { x: 25081, y: 5446 }; + break; + case 25866: + spot = { x: 25830, y: 5447 }; + break; + case 25431: + switch (preset.y) { + case 5011: + spot = { x: 25449, y: 5081 }; + break; + case 5861: + spot = { x: 25447, y: 5822 }; + break; + } + + break; + } + + if (!Pather.moveToUnit(spot)) { + throw new Error("summoner failed"); + } + + Attack.securePosition(spot.x, spot.y, 25, 3000); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + if (playerIn(me.area, nick)) { + return true; + } + Pather.moveToUnit(spot); + Attack.securePosition(me.x, me.y, 25, 500); + return false; + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal); + Attack.kill(sdk.monsters.Summoner); + log(AutoRush.playersOut); + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + Pickit.pickItems(); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal); + + let redPortal = Game.getObject(sdk.objects.RedPortal); + + if (!redPortal || !Pather.usePortal(null, null, redPortal)) { + if (!Misc.poll(function () { + let journal = Game.getObject(sdk.quest.chest.Journal); + + if (journal && journal.interact()) { + delay(1000); + me.cancel(); + } + + redPortal = Pather.getPortal(sdk.areas.CanyonofMagic); + + return (redPortal && Pather.usePortal(null, null, redPortal)); + })) throw new Error("summoner failed"); + } + Pather.useWaypoint(sdk.areas.LutGholein); + + return true; + }; + /** @param {string} [nick] */ + const duriel = function (nick) { + log("starting duriel"); + + if (me.inTown) { + Town.doChores(); + Pather.useWaypoint(sdk.areas.CanyonofMagic, true); + } else { + giveWP(); + } + + Precast.doPrecast(true); + + if (!Pather.moveToExit(getRoom().correcttomb, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder)) { + throw new Error("duriel failed"); + } + + Attack.securePosition(me.x, me.y, 30, 3000, true, me.hell); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + AutoRush.rushMode !== RushModes.chanter + ? log(AutoRush.playersOut) + : log("Place staff in orifice then wait in town"); + + if (!Misc.poll(function () { + return !playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (!Misc.poll(function () { + return Game.getObject(sdk.objects.PortaltoDurielsLair); + }, AutoRush.playerWaitTimeout, 1000)) { + log("Duriel portal not found"); + return false; + } + + Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair); + Attack.kill(sdk.monsters.Duriel); + Pickit.pickItems(); + + Pather.teleport = false; + + Pather.moveTo(22579, 15706); + + Pather.teleport = true; + + Pather.moveTo(22577, 15649, 10); + Pather.moveTo(22577, 15609, 10); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (!Pather.usePortal(null, me.name)) { + Town.goToTown(); + } + + Pather.useWaypoint(sdk.areas.PalaceCellarLvl1); + Pather.moveToExit([sdk.areas.HaremLvl2, sdk.areas.HaremLvl1], true); + Pather.moveTo(10022, 5047); + + if (AutoRush.rushMode !== RushModes.chanter) { + for (let i = 0; i < 3; i++) { + log("a3"); + + let playersMoved = Misc.poll(function () { + return !playersInAct(3); + }, Time.seconds(30), Time.seconds(1)); + + if (playersMoved) { + break; + } + } + Town.goToTown(3); + Town.doChores(); + } else if (AutoRush.rushMode === RushModes.chanter) { + Pather.moveToExit([sdk.areas.HaremLvl1, sdk.areas.LutGholein], true); + Pather.useWaypoint(sdk.areas.RogueEncampment); + } + + return true; + }; + /** @param {string} [nick] */ + const gidbinn = function (nick) { + log("starting gidbinn"); + + Town.doChores(); + Pather.useWaypoint(sdk.areas.FlayerJungle, true) && Precast.doPrecast(true); + if (!Pather.moveToPreset(sdk.areas.FlayerJungle, sdk.unittype.Object, sdk.quest.chest.GidbinnAltar)) { + throw new Error("gidbinn failed"); + } + const altar = Game.getObject(sdk.quest.chest.GidbinnAltar); + if (!altar) { + throw new Error("gidbinn failed - couldn't find altar"); + } + Misc.poll(function () { + altar.interact(); + return altar.mode === sdk.objects.mode.Active; + }, Time.minutes(1), Time.seconds(1)); + Attack.securePosition(me.x, me.y, 30, 3000, true); + Pather.makePortal(); + log(AutoRush.playersIn); + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + Pather.usePortal(null, me.name); + return true; + }; + /** @param {string} [nick] */ + const lamesen = function (nick) { + log("starting lamesen"); + + if (!Town.goToTown() || !Pather.useWaypoint(sdk.areas.KurastBazaar, true)) { + throw new Error("Lam Essen quest failed"); + } + + Precast.doPrecast(false); + + if (!Pather.moveToExit(sdk.areas.RuinedTemple, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.LamEsensTomeHolder)) { + throw new Error("Lam Essen quest failed"); + } + + Attack.securePosition(me.x, me.y, 30, 2000); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + if (!Misc.poll(function () { + return !playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + } + + Pather.usePortal(null, null); + + return true; + }; + /** @param {string} [nick] */ + const brain = function (nick) { + const exits = [ + sdk.areas.FlayerDungeonLvl1, + sdk.areas.FlayerDungeonLvl2, + sdk.areas.FlayerDungeonLvl3 + ]; + log("starting brain"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.FlayerJungle, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit(exits, true) + || !Pather.moveToPresetObject(me.area, sdk.quest.chest.KhalimsBrainChest)) { + throw new Error("brain failed"); + } + + Attack.securePosition(me.x, me.y, 30, 3000, me.hell, me.hell); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + Pather.usePortal(null, me.name); + + return true; + }; + /** @param {string} [nick] */ + const eye = function (nick) { + log("starting eye"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.SpiderForest, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.SpiderCavern, true) + || !Pather.moveToPresetObject(me.area, sdk.quest.chest.KhalimsEyeChest)) { + throw new Error("eye failed"); + } + + Attack.securePosition(me.x, me.y, 30, 3000, me.hell, me.hell); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + Pather.usePortal(null, me.name); + + return true; + }; + /** @param {string} [nick] */ + const heart = function (nick) { + log("starting heart"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.KurastBazaar, true) && Precast.doPrecast(true); + + if (!Pather.journeyTo(sdk.areas.A3SewersLvl2, true) + || !Pather.moveToPresetObject(me.area, sdk.quest.chest.KhalimsHeartChest)) { + throw new Error("heart failed"); + } + + Attack.securePosition(me.x, me.y, 30, 3000, me.hell, me.hell); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + Pather.usePortal(null, me.name); + + return true; + }; + // re-write to prevent fail to complete quest due to killing council from to far away + /** @param {string} [nick] */ + const travincal = function (nick) { + log("starting travincal"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.Travincal, true) && Precast.doPrecast(true); + + const wpCoords = new PathNode(me.x, me.y); + const portalSpot = new PathNode(wpCoords.x + 23, wpCoords.y - 102); + + [ + new PathNode(4742, 2179), + new PathNode(4738, 2133), + new PathNode(4768, 2150), + new PathNode(4762, 2106), + ].forEach((node) => { + Pather.move(node); + Attack.securePosition(node.x, node.y, 25, 2500); + }); + + Pather.move(portalSpot); + console.debug("Mob Count? " + me.mobCount({ range: 40 })); + Attack.securePosition(portalSpot.x, portalSpot.y, 40, 4000); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Pather.moveTo(wpCoords.x + 30, wpCoords.y - 134); + Pather.moveTo(wpCoords.x + 86, wpCoords.y - 130); + Pather.moveTo(wpCoords.x + 71, wpCoords.y - 94); + Attack.securePosition(me.x, me.y, 40, 4000); + Attack.kill(sdk.locale.monsters.IsmailVilehand); + + Pather.move(portalSpot); + Pather.makePortal(); + log(AutoRush.playersOut); + Pather.usePortal(null, me.name); + + return true; + }; + /** @param {string} [nick] */ + const mephisto = function (nick) { + log("starting mephisto"); + + Town.doChores(); + Pather.useWaypoint(sdk.areas.DuranceofHateLvl2, true) && Precast.doPrecast(true); + if (Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true) && Pather.moveTo(17692, 8023)) { + Pather.makePortal(); + } + delay(2000); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Pather.moveTo(17591, 8070); + Attack.kill(sdk.monsters.Mephisto); + Pickit.pickItems(); + Pather.moveTo(17692, 8023) && Pather.makePortal(); + log(AutoRush.playersOut); + + while (playerIn(me.area, nick)) { + delay(250); + } + + Pather.moveTo(17591, 8070) && Attack.securePosition(me.x, me.y, 40, 3000); + + let hydra = Game.getMonster(getLocaleString(sdk.locale.monsters.Hydra)); + + if (hydra) { + do { + while (!hydra.dead && hydra.hp > 0) { + delay(500); + } + } while (hydra.getNext()); + } + + Pather.makePortal(); + Pather.moveTo(17581, 8070); + log(AutoRush.playersIn); + + while (!playerIn(me.area, nick)) { + delay(250); + } + + if (AutoRush.rushMode !== RushModes.chanter) { + // allow 3 attempts + for (let i = 0; i < 3; i++) { + log("a4"); + + let playersMoved = Misc.poll(function () { + return !playersInAct(4); + }, Time.seconds(30), Time.seconds(1)); + + if (playersMoved) { + break; + } + } + } + + delay(2000); + Pather.usePortal(null); + + return true; + }; + /** @param {string} [nick] */ + const izual = function (nick) { + log("starting izual"); + + const moveIntoPos = function (unit, range) { + let coords = []; + let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); + const angles = [ + 0, 15, -15, 30, -30, 45, -45, 60, -60, + 75, -75, 90, -90, 105, -105, 120, -120, + 135, -135, 150, -150, 180 + ]; + + for (let i = 0; i < angles.length; i += 1) { + let coordx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * range + unit.x); + let coordy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * range + unit.y); + + try { + if (!(getCollision(unit.area, coordx, coordy) & 0x1)) { + coords.push({ + x: coordx, + y: coordy + }); + } + } catch (e) { + continue; + } + } + + if (coords.length > 0) { + coords.sort(Sort.units); + + return Pather.moveToUnit(coords[0]); + } + + return false; + }; + + Pather.useWaypoint(sdk.areas.CityoftheDamned, true) && Precast.doPrecast(false); + Pather.moveToExit(sdk.areas.PlainsofDespair, true); + + const izualPreset = Game.getPresetMonster(sdk.areas.PlainsofDespair, sdk.monsters.Izual).realCoords(); + + moveIntoPos(izualPreset, 50); + let izual = Misc.poll(function () { + return Game.getMonster(sdk.monsters.Izual); + }, 1500, 500); + + izual ? moveIntoPos(izual, 60) : console.log("izual unit not found"); + + let returnSpot = { + x: me.x, + y: me.y + }; + + Attack.securePosition(me.x, me.y, 30, 3000); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Attack.kill(sdk.monsters.Izual); + Pickit.pickItems(); + log(AutoRush.playersOut); + Pather.moveToUnit(returnSpot); + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(200); + } + } + + Pather.usePortal(null, null); + + return true; + }; + /** @param {string} [nick] */ + const diablo = function (nick) { + include("core/Common/Diablo.js"); + log("starting diablo"); + + function inviteIn () { + Pather.moveTo(7763, 5267) && Pather.makePortal(); + // change this spot so we don't bring diablo closer to rushees + Pather.moveTo(7727, 5267); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + return true; + } + + Town.doChores(); + Pather.useWaypoint(sdk.areas.RiverofFlame); + Precast.doPrecast(true); + if (!Pather.moveToExit(sdk.areas.ChaosSanctuary, true) && !Pather.moveTo(7790, 5544)) { + throw new Error("Failed to move to Chaos Sanctuary"); + } + + Common.Diablo.initLayout(); + Config.Diablo.Fast = true; + Config.Diablo.SealLeader = false; + + try { + Common.Diablo.runSeals(Config.Diablo.SealOrder); + console.log("Attempting to find Diablo"); + inviteIn() && Common.Diablo.diabloPrep(); + } catch (error) { + console.log("Diablo wasn't found. Checking seals."); + Common.Diablo.runSeals(Config.Diablo.SealOrder); + inviteIn() && Common.Diablo.diabloPrep(); + } + + Attack.kill(sdk.monsters.Diablo); + log(AutoRush.playersOut); + + if (me.expansion && AutoRush.rushMode !== RushModes.chanter) { + // allow 3 attempts + for (let i = 0; i < 3; i++) { + log("a5"); + + let playersMoved = Misc.poll(function () { + return !playersInAct(5); + }, Time.seconds(30), Time.seconds(1)); + + if (playersMoved) { + break; + } + } + } + + Pickit.pickItems(); + !Pather.usePortal(null, me.name) && Town.goToTown(); + + return true; + }; + /** @param {string} [nick] */ + const shenk = function (nick) { + log("starting shenk"); + + Pather.useWaypoint(sdk.areas.FrigidHighlands, true) && Precast.doPrecast(false); + Pather.moveTo(3846, 5120); + Attack.securePosition(me.x, me.y, 30, 3000); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Attack.kill(getLocaleString(sdk.locale.monsters.ShenktheOverseer)); + Pickit.pickItems(); + Pather.moveTo(3846, 5120); + log(AutoRush.playersOut); + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(200); + } + } + + Pather.usePortal(null, null); + + return true; + }; + /** @param {string} [nick] */ + const anya = function (nick) { + !me.inTown && Town.goToTown(); + + log("starting anya"); + + if (!Pather.useWaypoint(sdk.areas.CrystalizedPassage, true)) { + throw new Error("Anya quest failed"); + } + + Precast.doPrecast(false); + + if (!Pather.moveToExit(sdk.areas.FrozenRiver, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform)) { + throw new Error("Anya quest failed"); + } + + Attack.securePosition(me.x, me.y, 30, 2000); + + let anya = Game.getObject(sdk.objects.FrozenAnya); + + if (anya) { + Pather.moveToUnit(anya); + // Rusher should be able to interact so quester can get the potion without entering + Packet.entityInteract(anya); + delay(1000 + me.ping); + me.cancel(); + } + + if (AutoRush.rushMode === RushModes.chanter) { + log("Talk to Malah to get potion then come in"); + } + Pather.makePortal(); + if (AutoRush.rushMode !== RushModes.chanter) { + log(AutoRush.playersIn); + } + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + Misc.poll(function () { + return !Game.getObject(sdk.objects.FrozenAnya); + }, 30000, 1000); + log(AutoRush.playersOut); // Mainly for non-questers to know when to get the scroll of resistance + while (playerIn(me.area, nick)) { + delay(200); + } + } + + Pather.usePortal(null, null); + + return true; + }; + /** @param {string} [nick] */ + const ancients = function (nick) { + if (AutoRush.rushMode !== RushModes.chanter) { + if (!RushConfig[me.profile].config.Ancients[sdk.difficulty.nameOf(me.diff)]) { + if (!RushConfig[me.profile].config.Wps) { + log("Hell rush complete~"); + delay(500); + quit(); + } + return false; + } + } + + if (!bumperCheck(nick)) { + if (AutoRush.rushMode === RushModes.chanter) { + log(nick + " you are not eligible for ancients. You need to be at least level " + bumperLvlReq()); + + return false; + } + if (!RushConfig[me.profile].config.Wps) { + log("No eligible bumpers detected. Rush complete~"); + delay(500); + quit(); + } + + return false; + } + + include("core/Common/Ancients.js"); + log("starting ancients"); + + Town.doChores(); + Pather.useWaypoint(sdk.areas.AncientsWay, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.ArreatSummit, true)) { + throw new Error("Failed to go to Ancients way."); + } + + Pather.moveTo(10089, 12622); + Pather.makePortal(); + log(AutoRush.allIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log("timed out"); + return false; + } + + Pather.moveTo(10048, 12628); + Common.Ancients.touchAltar(); + Common.Ancients.startAncients(); + + Pather.moveTo(10089, 12622); + me.cancel(); + Pather.makePortal(); + + if (AutoRush.rushMode !== RushModes.chanter) { + while (playerIn(me.area, nick)) { + delay(100); + } + } + + !Pather.usePortal(null, me.name) && Town.goToTown(); + + return true; + }; + /** @param {string} [nick] */ + const baal = function (nick) { + if (me.hell && AutoRush.rushMode !== RushModes.chanter) { + if (!RushConfig[me.profile].config.Wps) { + log("Baal not done in Hell ~Hell rush complete~"); + delay(500); + quit(); + } + + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter && !bumperCheck(nick)) { + if (!RushConfig[me.profile].config.Wps) { + log("No eligible bumpers detected. ~Rush complete~"); + delay(500); + quit(); + } + + return false; + } + + include("core/Common/Baal.js"); + log("starting baal"); + + if (me.inTown) { + Town.doChores(); + Pather.useWaypoint(sdk.areas.WorldstoneLvl2) && Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], true)) { + throw new Error("Failed to move to Throne of Destruction."); + } + } + + Pather.moveTo(15113, 5040); + Attack.clear(15); + Common.Baal.clearThrone(); + + if (AutoRush.rushMode !== RushModes.rusher) { + Pather.moveTo(15118, 5045); + Pather.makePortal(); + say(AutoRush.playersIn); + } + + if (!Common.Baal.clearWaves()) { + throw new Error("Couldn't clear baal waves"); + } + + Common.Baal.clearThrone(); + + if (AutoRush.rushMode !== RushModes.chanter) { + me.checkForMobs({ range: 30 }) && this.clearWaves(); // ensure waves are actually done + Pather.moveTo(15090, 5008); + delay(5000); + Precast.doPrecast(true); + Misc.poll(() => !Game.getMonster(sdk.monsters.ThroneBaal), Time.minutes(3), 1000); + + let portal = Game.getObject(sdk.objects.WorldstonePortal); + + if (portal) { + Pather.usePortal(null, null, portal); + } else { + throw new Error("Couldn't find portal."); + } + + Pather.moveTo(15213, 5908); + Pather.makePortal(); + Pather.moveTo(15170, 5950); + delay(1000); + log(AutoRush.allIn); + + while (!playerIn(me.area, nick)) { + delay(250); + } + + Pather.moveTo(15134, 5923); + Attack.kill(sdk.monsters.Baal); + Pickit.pickItems(); + // Move back to rushee + Pather.moveTo(15213, 5908); + Pather.makePortal(); + log(AutoRush.playersOut); + } + + return true; + }; + + module.exports = { + log: log, + playerIn: playerIn, + playersInAct: playersInAct, + bumperCheck: bumperCheck, + andariel: andariel, + bloodraven: bloodraven, + smith: smith, + cube: cube, + amulet: amulet, + staff: staff, + summoner: summoner, + duriel: duriel, + eye: eye, + brain: brain, + heart: heart, + travincal: travincal, + mephisto: mephisto, + diablo: diablo, + ancients: ancients, + baal: baal, + cain: cain, + radament: radament, + gidbinn: gidbinn, + lamesen: lamesen, + izual: izual, + shenk: shenk, + anya: anya + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/autorush/RushConfig.js b/d2bs/kolbot/libs/systems/autorush/RushConfig.js new file mode 100644 index 000000000..a3b3935f0 --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/RushConfig.js @@ -0,0 +1,166 @@ +/** +* @filename RushConfig.js +* @author theBGuy +* @desc Configuration file for AutoRush system +* +*/ + +(function (module) { + /** @enum */ + const RushModes = { + /** The rushee that does the quests */ + quester: 0, + /** The rushee that follows */ + follower: 1, + /** The rushee that bumps the quester */ + bumper: 2, + /** Autorush mode */ + rusher: 3, + /** ControlBot/Chant scripts mode */ + chanter: 4, + /** Manual follow mode - disables some of the bot <-> bot communication we need with auto */ + manual: 5, + }; + + const AutoRush = { + /** Command by rusher to tell players to enter a portal */ + playersIn: "1", + /** Command by rusher to tell players to go back to town */ + playersOut: "2", + allIn: "3", + rushMode: RushModes.rusher, + /** How long to wait for a player to leave/enter an area before ending quest script with failed */ + playerWaitTimeout: Time.minutes(1), + /** controls the order */ + sequences: [ + "cain", + "andariel", + "radament", + "cube", + "amulet", + "staff", + "summoner", + "duriel", + "lamesen", + "travincal", + "mephisto", + "izual", + "diablo", + "shenk", + "anya", + "ancients", + "baal", + "givewps", + ], + }; + + /** @type {Object.} */ + const RushConfig = { + "example-quester": { + type: RushModes.quester, + startProfiles: ["example-bumper"], + /** Optional - Fill this out to create a account/character if it doesn't exist already */ + create: { + account: "testacc", + password: "password", + charName: "quester", + charInfo: "scl-sorc", + } + }, + "example-follower": { + type: RushModes.follower, + leader: "example-quester", + create: { + charName: "follower", + charInfo: "scl-zon", + } + }, + "example-bumper": { + type: RushModes.bumper, + leader: "example-quester", + }, + "example-rusher": { + type: RushModes.rusher, + leader: "example-quester", + config: { + WaitPlayerCount: 1, + Cain: true, + Radament: true, + LamEsen: true, + Izual: true, + Shenk: true, + Anya: true, + Ancients: { + Normal: true, + Nightmare: true, + Hell: false, + }, + Wps: false, + LastRun: "", + }, + }, + }; + + const _defaultConfig = { + /** @type {RushModes} */ + type: RushModes.quester, + /** @type {string[]} */ + startProfiles: [], + /** @type {string} */ + leader: "", + /** @type {Object} */ + create: { + /** @type {string} */ + account: "", + /** @type {string} */ + password: "", + /** @type {string} */ + charName: "", + /** + * @type {string} + * @desc Format: "scl-sorc" - "scl" = softcore ladder, "sorc" = sorceress + */ + charInfo: "", + }, + /** @type {Object} */ + config: { + /** @type {number} */ + WaitPlayerCount: 1, + /** @type {boolean} */ + Cain: true, + /** @type {boolean} */ + Radament: true, + /** @type {boolean} */ + LamEsen: true, + /** @type {boolean} */ + Izual: true, + /** @type {boolean} */ + Shenk: true, + /** @type {boolean} */ + Anya: true, + /** @type {Object} */ + Ancients: { + /** @type {boolean} */ + Normal: true, + /** @type {boolean} */ + Nightmare: true, + /** @type {boolean} */ + Hell: false, + }, + /** @type {boolean} */ + Wps: false, + /** @type {string} */ + LastRun: "", + }, + }; + + for (let key in RushConfig) { + RushConfig[key] = Object.assign({}, _defaultConfig, RushConfig[key]); + } + + module.exports = { + AutoRush: AutoRush, + RushModes: RushModes, + RushConfig: RushConfig, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/autorush/index.d.ts b/d2bs/kolbot/libs/systems/autorush/index.d.ts new file mode 100644 index 000000000..1bbfc7b12 --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/index.d.ts @@ -0,0 +1,44 @@ +declare global { + enum RushModes { + quester = 0, + follower = 1, + bumper = 2, + rusher = 3, + chanter = 4, + manual = 5, + } + type DefaultConfig = { + type: RushModes; + startProfiles: string[]; + leader: string; + create: { + account: string; + password: string; + charName: string; + /** + * @desc Format: "scl-sorc" - "scl" = softcore ladder, "sorc" = sorceress + */ + charInfo: string; + }; + config: { + WaitPlayerCount: number; + Cain: boolean; + Radament: boolean; + LamEsen: boolean; + Izual: boolean; + Shenk: boolean; + Anya: boolean; + Ancients: { + Normal: boolean; + Nightmare: boolean; + Hell: boolean; + }; + Wps: boolean; + LastRun: string; + }; + }; + type RushConfig = { + [key: string]: DefaultConfig; + }; +} +export {}; diff --git a/d2bs/kolbot/libs/systems/channel/ChannelConfig.js b/d2bs/kolbot/libs/systems/channel/ChannelConfig.js new file mode 100644 index 000000000..eeaf04e62 --- /dev/null +++ b/d2bs/kolbot/libs/systems/channel/ChannelConfig.js @@ -0,0 +1,56 @@ +/** +* @filename ChannelConfig.js +* @author theBGuy +* @desc Configuration file for D2BotChannel system +* +*/ + +(function (module) { + const ChannelConfig = { + SkipMutedKey: true, + MutedKeyTrigger: "Your account has had all chat privileges suspended.", + JoinDelay: 10, // Seconds to wait between announcement and clicking join + JoinRetry: 5, // Amount of times to re-attempt joining game + // watch for whisper event instead? + FriendListQuery: 0, // Seconds between "/f l" retries. 0 = disable. To prevent spamming when using set time rand(80, 160) + /** + * @typedef {Object} GameInfo + * @property {string} game + * @property {string} password + * + * @type {GameInfo[]} + * @example + * Games: [ + * { game: "baal-", password: "" }, + * ], + */ + Games: [ + { game: "", password: "" }, + ], + /** + * @description excludeFilter format + * @example Multiple entries in the same array mean AND + * // ignores games that contain "baal" and "-" + * const includeFilter = ["baal", "-"]; + * + * @example Multiple entries in different arrays mean OR + * // will ignore games with either "baal" or "diablo" in their name + * const includeFilter = [ + * ["baal"], + * ["diablo"] + * ]; + * @type {Array>} + */ + excludeFilter: [], + /** + * Leaders in game character name, only use this if the leader is using announce in the chat. + * Can be an array or names ["somename", "somename2"] + * @type {string[]} + */ + Follow: [], + }; + + module.exports = { + ChannelConfig: ChannelConfig, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/cleaner/CleanerConfig.js b/d2bs/kolbot/libs/systems/cleaner/CleanerConfig.js new file mode 100644 index 000000000..8bfb4824e --- /dev/null +++ b/d2bs/kolbot/libs/systems/cleaner/CleanerConfig.js @@ -0,0 +1,125 @@ +/** +* @filename CleanerConfig.js +* @author theBGuy +* @desc Configuration file for Cleaner system +* +*/ + +(function (module) { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + // D2BotCleaner settings - for global settings @see libs/starter/StarterConfig.js + // New Stuff: + // DataCleaner - to delete old files associated with running kolbot or SoloPlay + // SaveFiles - to save important SoloPlay files to SoloPlay/Data/ for performance review + //***********************************************************************************************************************// + // DataCleaner and SaveFiles can both be used for cleaning/saving files without having to delete associated characters // + //***********************************************************************************************************************// + const CleanerConfig = { + /** + * Always run this when re-using a profile with Kolbot-SoloPlay + */ + DataCleaner: true, + /** + * NOTE: Only works on SoloPlay profiles. + * Highly recommened to run this if using the peformance tracking system and wish to review them later + */ + SaveFiles: false, + /** + * Seconds to wait before cleaning next account + * If doing 10+ accounts recommended to increase this delay to rand(30, 60) prevent R/D + */ + DelayBetweenAccounts: rand(15, 30), + }; + + /** + * @todo this section should be in it's own config leaving this file only containing core logic + * @example Format + * "account1/password1/realm": ["charname1", "charname2"], + * "account2/password2/realm": ["charnameX", "charnameY"], + * "account3/password3/realm": ["all"] + * + * // To clean a full account, put "account/password/realm": ["all"] + * + * // realm = useast, uswest, europe, asia + * + * // for singleplayer follow format "singleplayer": ["charname1", "charname2"] + * + * // Individual entries are separated with a comma. + * @example + * "MyAcc1/tempPass/useast": ["soloSorc"], + * "singleplayer": ["solobarb"], + * + * @type {Object} + */ + const AccountsToClean = { + // Enter your lines under here + }; + + const CharactersToExclude = [""]; + + /** + * NEW STUFF - Please enter your profile name exactly as it appears in D2Bot# + * @example + * "SCL-ZON123", "hcnl-pal123", + * @type {string[]} + */ + const profiles = [ + // Enter your lines under here + + ]; + + /** + * @description If you have a lot of profiles that are clones this can be used as an easier way to clean all of them + * @param {string} profilePrefix + * - this is everthing before the suffix numbers. Ex: mypal01 or sccl-pal-001, ect + * @param {string} profileSuffixStart + * - this is the suffix to start at, Ex: 01 or 001 or 1, all the profiles need have the same format. + * CANNOT HAVE scl-pal-1 and scl-pal-001 + * @param {string} end + * - the ending profile suffix, this is used to stop the loop. + * If you are doing scl-pal-001 to scl-pal-100 (that'd be alot) then 100 would go here + * @example + * // This will clean all profiles from scl-sorc-002 to scl-sorc-009 + * { + * profilePrefix: "scl-sorc-", + * profileSuffixStart: "002", + * end: "009" + * } + * @type {Array<{profilePrefix: string, profileSuffixStart: string, end: string}>} + */ + const AdvancedProfileCleanerConfig = [ + // { + // profilePrefix: "scl-sorc-", + // profileSuffixStart: "002", + // end: "009" + // }, + // Your lines under here + ]; + + /** + * @description Generate accounts to entirely clean ("all") + * - To use this, set `generateAccounts` to true and setup the rest of the parameters + * + * - It will generates accounts from start to stop range(included): + * account1/password/realm + * account2/password/realm + * etc... + */ + const AdvancedCleanerConfig = { + generateAccounts: false, + accountPrefix: "account", + accountPassword: "password", + accountRealm: "realm", + rangeStart: 1, + rangeStop: 10 + }; + + module.exports = { + CleanerConfig: CleanerConfig, + AccountsToClean: AccountsToClean, + CharactersToExclude: CharactersToExclude, + profiles: profiles, + AdvancedProfileCleanerConfig: AdvancedProfileCleanerConfig, + AdvancedCleanerConfig: AdvancedCleanerConfig + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/crafting/CraftingSystem.js b/d2bs/kolbot/libs/systems/crafting/CraftingSystem.js new file mode 100644 index 000000000..0a85cd2d8 --- /dev/null +++ b/d2bs/kolbot/libs/systems/crafting/CraftingSystem.js @@ -0,0 +1,457 @@ +/** +* @filename CraftingSystem.js +* @author kolton +* @desc Multi-profile crafting system +* @notes This system is experimental, there will be no support offered for it. +* If you can't get it to work, leave it be. +* This is the main driver file, for the Teams config @see TeamsConfig.js +* +*/ + +const CraftingSystem = {}; + +// load configuration file +CraftingSystem.Teams = Object.assign({}, require("./TeamsConfig", null, false)); + +// ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## + +// Get the Crafting System information for current profile +CraftingSystem.getInfo = function () { + for (let i in CraftingSystem.Teams) { + if (CraftingSystem.Teams.hasOwnProperty(i)) { + for (let j = 0; j < CraftingSystem.Teams[i].Collectors.length; j += 1) { + if (CraftingSystem.Teams[i].Collectors[j].toLowerCase() === me.profile.toLowerCase()) { + let info = CraftingSystem.Teams[i]; + info.collector = true; + info.worker = false; + + return info; + } + } + + for (let j = 0; j < CraftingSystem.Teams[i].Workers.length; j += 1) { + if (CraftingSystem.Teams[i].Workers[j].toLowerCase() === me.profile.toLowerCase()) { + let info = CraftingSystem.Teams[i]; + info.collector = false; + info.worker = true; + + return info; + } + } + } + } + + return false; +}; + +// ################################################# +// # Item collector out of game specific functions # +// ################################################# + +CraftingSystem.check = false; +CraftingSystem.inGame = false; + +CraftingSystem.outOfGameCheck = function () { + if (!CraftingSystem.check) return false; + + let info = CraftingSystem.getInfo(); + + function scriptMsg(msg) { + let obj; + + try { + obj = JSON.parse(msg); + } catch (e) { + return false; + } + + obj.name === "RequestWorker" && scriptBroadcast(JSON.stringify({ name: "WorkerName", value: worker.name })); + + return true; + } + + if (info && info.collector) { + let worker = CraftingSystem.getWorker(); + + if (worker && worker.game) { + D2Bot.printToConsole("CraftingSystem: Transfering items.", sdk.colors.D2Bot.DarkGold); + D2Bot.updateStatus("CraftingSystem: In game."); + addEventListener("scriptmsg", scriptMsg); + + CraftingSystem.inGame = true; + me.blockMouse = true; + + delay(2000); + joinGame(worker.game[0], worker.game[1]); + + me.blockMouse = false; + + delay(5000); + + while (me.ingame) { + delay(1000); + } + + CraftingSystem.inGame = false; + CraftingSystem.check = false; + + removeEventListener("scriptmsg", scriptMsg); + + return true; + } + } + + return false; +}; + +CraftingSystem.getWorker = function () { + let rval = { + game: false, + name: false + }; + let info = CraftingSystem.getInfo(); + + function checkEvent(mode, msg) { + if (mode === 4) { + for (let i = 0; i < info.CraftingGames.length; i += 1) { + if (info.CraftingGames[i] && msg.match(info.CraftingGames[i], "i")) { + rval.game = msg.split("/"); + + break; + } + } + } + } + + if (info && info.collector) { + addEventListener("copydata", checkEvent); + + rval.game = false; + + for (let i = 0; i < info.Workers.length; i += 1) { + sendCopyData(null, info.Workers[i], 0, JSON.stringify({ name: "GetGame", profile: me.profile })); + delay(100); + + if (rval.game) { + rval.name = info.Workers[i]; + + break; + } + } + + removeEventListener("copydata", checkEvent); + + return rval; + } + + return false; +}; + +// ############################################# +// # Item collector in-game specific functions # +// ############################################# + +CraftingSystem.inGameCheck = function () { + let info = CraftingSystem.getInfo(); + + if (info && info.collector) { + for (let i = 0; i < info.CraftingGames.length; i += 1) { + if (info.CraftingGames[i] && me.gamename.match(info.CraftingGames[i], "i")) { + CraftingSystem.dropItems(); + me.cancel(); + delay(5000); + quit(); + + return true; + } + } + } + + return false; +}; + +CraftingSystem.neededItems = []; +CraftingSystem.validGids = []; +CraftingSystem.itemList = []; +CraftingSystem.fullSets = []; + +// Check whether item can be used for crafting +CraftingSystem.validItem = function (item) { + switch (item.itemType) { + case sdk.items.type.Jewel: + // Use junk jewels only + return NTIP.CheckItem(item) === Pickit.Result.UNWANTED; + } + + return true; +}; + +// Check if the item should be picked for crafting +CraftingSystem.checkItem = function (item) { + let info = CraftingSystem.getInfo(); + + if (info) { + for (let i = 0; i < CraftingSystem.neededItems.length; i += 1) { + if (item.classid === CraftingSystem.neededItems[i] && CraftingSystem.validItem(item)) { + return true; + } + } + } + + return false; +}; + +// Check if the item should be kept or dropped +CraftingSystem.keepItem = function (item) { + let info = CraftingSystem.getInfo(); + + if (info) { + if (info.collector) return CraftingSystem.validGids.includes(item.gid); + + if (info.worker) { + // Let pickit decide whether to keep crafted + return item.crafted ? false : true; + } + } + + return false; +}; + +// Collect ingredients only if a worker needs them +CraftingSystem.getSetInfoFromWorker = function (workerName) { + let setInfo = false; + let info = CraftingSystem.getInfo(); + + function copyData(mode, msg) { + let obj; + + if (mode === 4) { + try { + obj = JSON.parse(msg); + } catch (e) { + return false; + } + + obj && obj.name === "SetInfo" && (setInfo = obj.value); + } + + return true; + } + + if (info && info.collector) { + addEventListener("copydata", copyData); + sendCopyData(null, workerName, 0, JSON.stringify({ name: "GetSetInfo", profile: me.profile })); + delay(100); + + if (setInfo !== false) { + removeEventListener("copydata", copyData); + + return setInfo; + } + + removeEventListener("copydata", copyData); + } + + return false; +}; + +CraftingSystem.init = function (name) { + let info = CraftingSystem.getInfo(); + + if (info && info.collector) { + for (let i = 0; i < info.Sets.length; i += 1) { + info.Sets[i].Enabled = false; + } + + let setInfo = CraftingSystem.getSetInfoFromWorker(name); + + if (setInfo) { + for (let i = 0; i < setInfo.length; i += 1) { + if (setInfo[i] === 1 && info.Sets[i].Enabled === false) { + info.Sets[i].Enabled = true; + } + } + } + } +}; + +// Build global lists of needed items and valid ingredients +CraftingSystem.buildLists = function (onlyNeeded) { + let info = CraftingSystem.getInfo(); + + if (info && info.collector) { + CraftingSystem.neededItems = []; + CraftingSystem.validGids = []; + CraftingSystem.fullSets = []; + CraftingSystem.itemList = me.findItems(-1, sdk.items.mode.inStorage); + + for (let i = 0; i < info.Sets.length; i += 1) { + if (!onlyNeeded || info.Sets[i].Enabled) { + CraftingSystem.checkSet(info.Sets[i]); + } + } + + return true; + } + + return false; +}; + +// Check which ingredients a set needs and has +CraftingSystem.checkSet = function (set) { + let rval = {}; + let setNeeds = []; + let setHas = []; + + // Get what set needs + // Multiply by SetAmount + for (let amount = 0; amount < set.SetAmount; amount += 1) { + for (let i = 0; i < set.Ingredients.length; i += 1) { + setNeeds.push(set.Ingredients[i]); + } + } + + // Remove what set already has + for (let i = 0; i < setNeeds.length; i += 1) { + for (let j = 0; j < CraftingSystem.itemList.length; j += 1) { + if (CraftingSystem.itemList[j].classid === setNeeds[i]) { + setHas.push(CraftingSystem.itemList[j].gid); + setNeeds.splice(i, 1); + CraftingSystem.itemList.splice(j, 1); + + i -= 1; + j -= 1; + } + } + } + + // The set is complete + setNeeds.length === 0 && CraftingSystem.fullSets.push(setHas.slice()); + + CraftingSystem.neededItems = CraftingSystem.neededItems.concat(setNeeds); + CraftingSystem.validGids = CraftingSystem.validGids.concat(setHas); + + CraftingSystem.neededItems.sort(Sort.numbers); + CraftingSystem.validGids.sort(Sort.numbers); + + return rval; +}; + +// Update lists when a valid ingredient is picked +CraftingSystem.update = function (item) { + CraftingSystem.neededItems.splice(CraftingSystem.neededItems.indexOf(item.classid), 1); + CraftingSystem.validGids.push(item.gid); + + return true; +}; + +// Cube flawless gems if the ingredient is a perfect gem +CraftingSystem.checkSubrecipes = function () { + for (let i = 0; i < CraftingSystem.neededItems.length; i += 1) { + switch (CraftingSystem.neededItems[i]) { + case sdk.items.gems.Perfect.Amethyst: + case sdk.items.gems.Perfect.Topaz: + case sdk.items.gems.Perfect.Sapphire: + case sdk.items.gems.Perfect.Emerald: + case sdk.items.gems.Perfect.Ruby: + case sdk.items.gems.Perfect.Diamond: + case sdk.items.gems.Perfect.Skull: + if (Cubing.subRecipes.indexOf(CraftingSystem.neededItems[i]) === -1) { + Cubing.subRecipes.push(CraftingSystem.neededItems[i]); + Cubing.recipes.push({ + Ingredients: [ + CraftingSystem.neededItems[i] - 1, + CraftingSystem.neededItems[i] - 1, + CraftingSystem.neededItems[i] - 1 + ], + Index: 0, + AlwaysEnabled: true, + MainRecipe: "Crafting" + }); + } + + break; + } + } + + return true; +}; + +// Check if there are any complete ingredient sets +CraftingSystem.checkFullSets = function () { + let info = CraftingSystem.getInfo(); + + if (info && info.collector) { + for (let i = 0; i < info.Workers.length; i += 1) { + CraftingSystem.init(info.Workers[i]); + CraftingSystem.buildLists(true); + + if (CraftingSystem.fullSets.length) { + return true; + } + } + } + + return false; +}; + +// Drop complete ingredient sets +CraftingSystem.dropItems = function () { + Town.goToTown(1); + Town.move("stash"); + Town.openStash(); + + let worker; + + function scriptMsg(msg) { + let obj; + + try { + obj = JSON.parse(msg); + } catch (e) { + return false; + } + + !!obj && obj.name === "WorkerName" && (worker = obj.value); + + return true; + } + + addEventListener("scriptmsg", scriptMsg); + scriptBroadcast(JSON.stringify({ name: "RequestWorker" })); + delay(100); + + if (worker) { + CraftingSystem.init(worker); + CraftingSystem.buildLists(true); + removeEventListener("scriptmsg", scriptMsg); + + while (CraftingSystem.fullSets.length) { + let gidList = CraftingSystem.fullSets.shift(); + + while (gidList.length) { + let item = me.getItem(-1, -1, gidList.shift()); + !!item && item.drop(); + } + } + + CraftingSystem.dropGold(); + delay(1000); + me.cancel(); + } + + return true; +}; + +CraftingSystem.dropGold = function () { + Town.goToTown(1); + Town.move("stash"); + + if (me.getStat(sdk.stats.Gold) >= 10000) { + gold(10000); + } else if (me.getStat(sdk.stats.GoldBank) + me.getStat(sdk.stats.Gold) >= 10000) { + Town.openStash(); + gold(10000 - me.getStat(sdk.stats.Gold), 4); + gold(10000); + } +}; diff --git a/d2bs/kolbot/libs/systems/crafting/TeamsConfig.js b/d2bs/kolbot/libs/systems/crafting/TeamsConfig.js new file mode 100644 index 000000000..45a0f6e55 --- /dev/null +++ b/d2bs/kolbot/libs/systems/crafting/TeamsConfig.js @@ -0,0 +1,45 @@ +/** +* @filename TeamsConfig.js +* @author theBGuy +* @desc Configuration file for Crafting system +* +*/ + +(function (module) { + module.exports = { + "Team 1": { + // List of profiles that will collect ingredients + Collectors: [], + + // List of profiles that will craft/reroll items + Workers: [], + + // List of Worker game names (without the numbers) + CraftingGames: [], + + /* BaseItems - list of base item class ids + * Ingredients - list of recipe ingredients + * SetAmount - number of full sets to gather before transfering + * Type - the type of recipe. Available options: "crafting", "runewords", "cubing" + */ + Sets: [ + // LLD Crafting + + // Caster Belt set, char lvl 29 + // Light Belt classid 345, shopped at nightmare Elzix + // Sharkskin Belt classid 391, shopped at nightmare Elzix + //{BaseItems: [345, 391], Ingredients: [615, 643, 561], SetAmount: 2, Type: "crafting"}, + + // Runeword Making + + // Spirit Runeset + Hel + //{BaseItems: [29, 30, 31], Ingredients: [616, 618, 619, 620, 624], SetAmount: 2, Type: "runewords"}, + + // Misc. Cubing + + // Reroll rare Diadem + //{BaseItems: [421], Ingredients: [601, 601, 601], SetAmount: 1, Type: "cubing"} + ] + }, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/follow/FollowConfig.js b/d2bs/kolbot/libs/systems/follow/FollowConfig.js new file mode 100644 index 000000000..f0960dfce --- /dev/null +++ b/d2bs/kolbot/libs/systems/follow/FollowConfig.js @@ -0,0 +1,28 @@ +/** +* @filename FollowConfig.js +* @author theBGuy +* @desc Configuration file for D2BotFollow system +* +*/ + +(function (module) { + /** + * @description Join game settings + * - Format: "leader's profile": ["leecher 1 profile", "leecher 2 profile", ...] + * - If you want everyone to join the same leader, use "leader's profile": ["all"] + * - NOTE: Use *PROFILE* names (profile matches window title), NOT character/account names + * - leader:leecher groups need to be divided by a comma + * @example + * const JoinSettings = { + * "lead1": ["follow1", "follow2"], + * "lead2": ["follow3", "follow4"] + * }; + */ + const JoinSettings = { + "Leader": ["Leecher"], + }; + + module.exports = { + JoinSettings: JoinSettings, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/gambling/Gambling.js b/d2bs/kolbot/libs/systems/gambling/Gambling.js new file mode 100644 index 000000000..8b5e38bbf --- /dev/null +++ b/d2bs/kolbot/libs/systems/gambling/Gambling.js @@ -0,0 +1,159 @@ +/** +* @filename Gambling.js +* @author kolton +* @desc Multi-profile gambling system. +* Allows lower level characters to get a steady income of gold to gamble LLD/VLLD items +* Not recommended for rings/amulets because of their high price (unless you want 3 gold finders to supply one gambler) +* It's possible to have multiple teams of gamblers/gold finders. Individual entries are separated by commas. +* @see TeamsConfig.js for setup +* +*/ + +const Gambling = { + // load configuration file + Teams: Object.assign({}, require("./TeamsConfig", null, false)), + + inGame: false, + + getInfo: function (profile) { + !profile && (profile = me.profile); + + for (let i in this.Teams) { + if (this.Teams.hasOwnProperty(i)) { + for (let j = 0; j < this.Teams[i].goldFinders.length; j += 1) { + if (this.Teams[i].goldFinders[j].toLowerCase() === profile.toLowerCase()) { + this.Teams[i].goldFinder = true; + this.Teams[i].gambler = false; + + return this.Teams[i]; + } + } + + for (let j = 0; j < this.Teams[i].gamblers.length; j += 1) { + if (this.Teams[i].gamblers[j].toLowerCase() === profile.toLowerCase()) { + this.Teams[i].goldFinder = false; + this.Teams[i].gambler = true; + + return this.Teams[i]; + } + } + } + } + + return false; + }, + + inGameCheck: function () { + let info = this.getInfo(); + + if (info && info.goldFinder) { + for (let i = 0; i < info.gambleGames.length; i += 1) { + if (info.gambleGames[i] && me.gamename.match(info.gambleGames[i], "i")) { + this.dropGold(); + DataFile.updateStats("gold"); + delay(5000); + quit(); + + return true; + } + } + } + + return false; + }, + + dropGold: function () { + let info = this.getInfo(); + + if (info && info.goldFinder) { + Town.goToTown(1); + Town.move("stash"); + + while (me.getStat(sdk.stats.Gold) + me.getStat(sdk.stats.GoldBank) > info.goldReserve) { + gold(me.getStat(sdk.stats.Gold)); // drop current gold + Town.openStash(); + + // check stashed gold vs max carrying capacity + if (me.getStat(sdk.stats.GoldBank) <= me.getStat(sdk.stats.Level) * 1e4) { + // leave minGold in stash, pick the rest + gold(me.getStat(sdk.stats.GoldBank) - info.goldReserve, 4); + } else { + // pick max carrying capacity + gold(me.getStat(sdk.stats.Level) * 1e4, 4); + } + + delay(1000); + } + } + }, + + outOfGameCheck: function () { + let info = this.getInfo(); + + if (info && info.goldFinder && DataFile.getStats().gold >= info.goldTrigger) { + let game = this.getGame(); + + if (game) { + D2Bot.printToConsole("Joining gold drop game.", sdk.colors.D2Bot.DarkGold); + + this.inGame = true; + me.blockMouse = true; + + delay(2000); + joinGame(game[0], game[1]); + + me.blockMouse = false; + + delay(5000); + + while (me.ingame) { + delay(1000); + } + + this.inGame = false; + + return true; + } + } + + return false; + }, + + getGame: function () { + let game; + let info = this.getInfo(); + + if (!info || !info.goldFinder) { + return false; + } + + function checkEvent(mode, msg) { + if (mode === 4) { + for (let i = 0; i < info.gambleGames.length; i += 1) { + if (info.gambleGames[i] && msg.match(info.gambleGames[i], "i")) { + game = msg.split("/"); + + break; + } + } + } + } + + addEventListener("copydata", checkEvent); + + game = null; + + for (let i = 0; i < info.gamblers.length; i += 1) { + sendCopyData(null, info.gamblers[i], 0, me.profile); + delay(100); + + if (game) { + break; + } + } + + removeEventListener("copydata", checkEvent); + + return game; + } +}; diff --git a/d2bs/kolbot/libs/systems/gambling/TeamsConfig.js b/d2bs/kolbot/libs/systems/gambling/TeamsConfig.js new file mode 100644 index 000000000..b3fc90cee --- /dev/null +++ b/d2bs/kolbot/libs/systems/gambling/TeamsConfig.js @@ -0,0 +1,37 @@ +/** +* @filename TeamsConfig.js +* @author theBGuy +* @desc Configuration file for Gambling system +* +*/ + +(function (module) { + module.exports = { + /** + Setting up: + + "Gamble Team 1": { // Put a unique team name here. + + goldFinders: ["GF Profile 1", "GF Profile 2"], // List of gold finder PROFILE names. They will join gamble games to drop gold + + gamblers: ["Gambler 1", "Gambler 2"], // List of gambler PROFILE names. They will keep gambling and picking up gold from gold finders. + + gambleGames: ["Gambling-", "HeyIGamble-"], // Games that gold finders will join, don't use numbers. + + goldTrigger: 2500000, // Minimum amount of gold before giving it to gamblers. + + goldReserve: 200000 // Amount of gold to keep after dropping. + } + + Once set up properly, the gold finders will run their own games and join gamblers' games when they're out of gold. + */ + "Gamble Team 1": { + goldFinders: [""], + gamblers: [""], + gambleGames: [""], + + goldTrigger: 2500000, + goldReserve: 200000 + }, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/gameaction/GameAction.d.ts b/d2bs/kolbot/libs/systems/gameaction/GameAction.d.ts new file mode 100644 index 000000000..749de673b --- /dev/null +++ b/d2bs/kolbot/libs/systems/gameaction/GameAction.d.ts @@ -0,0 +1,23 @@ +// @ts-nocheck +declare global { + namespace GameAction { + let LogNames: boolean; + let LogItemLevel: boolean; + let LogEquipped: boolean; + let LogMerc: boolean; + let SaveScreenShot: boolean; + let IngameTime: number; + let task: any; // Update the type of `task` as needed + + function init(task: any): void; + function update(action: string, data: any): void; + function gameInfo(): { gameName: string, gamePass: string }; + function getLogin(): { realm: string, account: string, password: string }; + function getCharacters(): string[]; + function inGameCheck(): boolean; + function load(hash: string): string; + function save(hash: string, data: string): void; + function dropItems(dropList: string[]): void; + } +} +export {}; \ No newline at end of file diff --git a/d2bs/kolbot/libs/systems/gameaction/GameAction.js b/d2bs/kolbot/libs/systems/gameaction/GameAction.js new file mode 100644 index 000000000..3d762d107 --- /dev/null +++ b/d2bs/kolbot/libs/systems/gameaction/GameAction.js @@ -0,0 +1,208 @@ +/* eslint-disable dot-notation */ +/** +* @filename GameAction.js +* @author noah-@github.com +* @desc Perform task based actions specified by Profile Tag +* +*/ +include("systems/mulelogger/MuleLogger.js"); + +const GameAction = { + // keeping with the general structure changes this section should probably be in its own config file + // but its not a lot so does it really need to be? + LogNames: true, // Put account/character name on the picture + LogItemLevel: true, // Add item level to the picture + LogEquipped: false, // include equipped items + LogMerc: false, // include items merc has equipped (if alive) + SaveScreenShot: false, // Save pictures in jpg format (saved in 'Images' folder) + IngameTime: 60, // Time to wait before leaving game + + task: null, + // don't edit + init: function (task) { + GameAction.task = JSON.parse(task); + + if (this.task["data"] && typeof this.task.data === "string") { + this.task.data = JSON.parse(this.task.data); + } + + MuleLogger.LogNames = this.LogNames; + MuleLogger.LogItemLevel = this.LogItemLevel; + MuleLogger.LogEquipped = this.LogEquipped; + MuleLogger.LogMerc = this.LogMerc; + MuleLogger.SaveScreenShot = this.SaveScreenShot; + + return true; + }, + + update: function (action, data) { + if (typeof action !== "string") throw new Error("Action must be a string!"); + + typeof data !== "string" && (data = JSON.stringify(data)); + + D2Bot.printToConsole(data); + + let tag = JSON.parse(JSON.stringify(this.task)); // deep copy + tag.action = action; + tag.data = data; + D2Bot.setTag(tag); + }, + + gameInfo: function () { + let gi = { gameName: null, gamePass: null }; + + switch (this.task.action) { + case "doMule": + gi = null; + + break; // create random game + case "doDrop": + gi.gameName = this.task.data.gameName; + gi.gamePass = this.task.data.gamePass; + + break; // join game + default: + gi = null; + + break; + } + + return gi; + }, + + getLogin: function () { + let li = { realm: null, account: null, password: null }; + + (this.task && this.task.data) && (li.password = this.load(this.task.hash)); + + // drop specific object + if (this.task.data["items"] && this.task.data.items.length > 0) { + li.realm = this.task.data.items[0].realm; + li.account = this.task.data.items[0].account; + } + + // mule log specific objects + this.task.data["realm"] && (li.realm = this.task.data.realm); + this.task.data["account"] && (li.account = this.task.data.account); + + if (!li.password || !li.account || !li.realm) { + this.update("done", "Realm, Account, or Password was invalid!"); + D2Bot.stop(); + delay(500); + } + + return li; + }, + + getCharacters: function () { + let chars = []; + + // drop specific object + if (this.task.data["items"]) { + for (let i = 0; i < this.task.data.items.length; i += 1) { + if (chars.indexOf(this.task.data.items[i].character) === -1) { + chars.push(this.task.data.items[i].character); + } + } + } + + // mule log specific object + this.task.data["chars"] && (chars = this.task.data.chars); + + return chars; + }, + + inGameCheck: function () { + if (getScript("D2BotGameAction.dbj")) { + while (!this["task"]) { + D2Bot.getProfile(); + delay(500); + } + + switch (this.task.action) { + case "doMule": + MuleLogger.logChar(); + + break; + case "doDrop": + this.dropItems(this.task.data.items); + MuleLogger.logChar(); + + break; + default: + break; + } + + while ((getTickCount() - me.gamestarttime) < this.IngameTime * 1000) { + delay(1000); + } + + try { + quit(); + } finally { + while (me.ingame) { + delay(100); + } + } + + return true; + } + + return false; + }, + + load: function (hash) { + let filename = "data/secure/" + hash + ".txt"; + + if (!FileTools.exists(filename)) { + this.update("done", "File " + filename + " does not exist!"); + D2Bot.stop(); + delay(5000); + quitGame(); // pretty sure quitGame crashes? + } + + return FileTools.readText(filename); + }, + + save: function (hash, data) { + let filename = "data/secure/" + hash + ".txt"; + FileTools.writeText(filename, data); + }, + + dropItems: function (droplist) { + if (!droplist) return; + + while (!me.gameReady) { + delay(100); + } + + let items = me.getItemsEx(); + + if (!items || !items.length) return; + + for (let i = 0; i < droplist.length; i += 1) { + if (droplist[i].character !== me.charname) { + continue; + } + + let info = droplist[i].itemid.split(":"); //":" + unit.classid + ":" + unit.location + ":" + unit.x + ":" + unit.y; + + let classid = info[1]; + let loc = info[2]; + let unitX = info[3]; + let unitY = info[4]; + + // for debug purposes + print("classid: " + classid + " location: " + loc + " X: " + unitX + " Y: " + unitY); + + for (let j = 0; j < items.length; j += 1) { + if (items[j].classid.toString() === classid + && items[j].location.toString() === loc + && items[j].x.toString() === unitX + && items[j].y.toString() === unitY) { + items[j].drop(); + } + } + } + }, +}; diff --git a/d2bs/kolbot/libs/systems/mulelogger/LoggerConfig.js b/d2bs/kolbot/libs/systems/mulelogger/LoggerConfig.js new file mode 100644 index 000000000..3bb3603c4 --- /dev/null +++ b/d2bs/kolbot/libs/systems/mulelogger/LoggerConfig.js @@ -0,0 +1,36 @@ +/** +* @filename LoggerConfig.js +* @author theBGuy +* @desc Configuration file for MuleLogger system +* +*/ + +(function (module) { + module.exports = { + LogGame: ["", ""], // ["gamename", "password"] + LogNames: true, // Put account/character name on the picture + LogItemLevel: true, // Add item level to the picture + LogEquipped: true, // include equipped items + LogMerc: true, // include items merc has equipped (if alive) + SaveScreenShot: false, // Save pictures in jpg format (saved in 'Images' folder) + AutoPerm: true, // override InGameTime to perm character + IngameTime: rand(60, 120), // (180, 210) to avoid RD, increase it to (7230, 7290) for mule perming + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + LogAccounts: { + /* Format: + "account1/password1/realm": ["charname1", "charname2 etc"], + "account2/password2/realm": ["charnameX", "charnameY etc"], + "account3/password3/realm": ["all"] + + To log a full account, put "account/password/realm": ["all"] + + realm = useast, uswest, europe or asia + + Enter Individual entries are separated with a comma below + */ + "exampleAcc/pa33word3/realm": ["all"], + }, + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.d.ts b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.d.ts new file mode 100644 index 000000000..8b96d0ad9 --- /dev/null +++ b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.d.ts @@ -0,0 +1,66 @@ +declare global { + namespace MuleLogger { + const LogGame: [string, string]; + let LogNames: boolean; + let LogItemLevel: boolean; + let LogEquipped: boolean; + let LogMerc: boolean; + let SaveScreenShot: boolean; + let AutoPerm: boolean; + let IngameTime: number; + const LogAccounts: { [account: string]: string[] }; + + function inGameCheck(): boolean; + /** + * Save perm status to logs/MuleLogPermInfo.json. + * @param charPermInfo - The character's permanent status information. + */ + function savePermedStatus(charPermInfo?: { charname: string; perm: boolean }): void; + + /** + * Load perm status from logs/MuleLogPermInfo.json. + * @returns The character's permanent status information. + */ + function loadPermedStatus(): { charname: string; perm: boolean }; + + /** + * @param hash - The hash value. + * @returns The loaded data. + */ + function load(hash: string): string; + + /** + * @param hash - The hash value. + * @param data - The data to save. + */ + function save(hash: string, data: string): void; + + function remove(): void; + + /** + * Log kept item stats in the manager. + * @param unit - The item unit. + * @param logIlvl - Log the item's item level. Default: `LogItemLevel` value. + * @returns The logged item information. + */ + function logItem(unit: ItemUnit, logIlvl?: boolean): { + itemColor: string; + image: string; + title: string; + description: string; + header: string; + sockets: any; // Update the type of `sockets` as needed + }; + + /** + * Log character to D2Bot# itemviewer. + * @param logIlvl - Log the item's item level. Default: `LogItemLevel` value. + * @param logName - Log the character's name. Default: `LogNames` value. + * @param saveImg - Save the item image. Default: `SaveScreenShot` value. + */ + function logChar(logIlvl?: boolean, logName?: boolean, saveImg?: boolean): void; + + // Add more functions and properties as needed + } +} +export {}; \ No newline at end of file diff --git a/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.js b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.js new file mode 100644 index 000000000..f89756981 --- /dev/null +++ b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.js @@ -0,0 +1,290 @@ +/* eslint-disable max-len */ +/** +* @filename MuleLogger.js +* @author kolton, theBGuy +* @desc Log items and perm accounts/characters, for setup @see LoggerConfig.js +* +*/ + +const MuleLogger = { + // ~~~ DON'T TOUCH, configuration file loaded at bottom. Use LoggerConfig.js ~~~ // + LogGame: ["", ""], // ["gamename", "password"] + LogNames: true, // Put account/character name on the picture + LogItemLevel: true, // Add item level to the picture + LogEquipped: true, // include equipped items + LogMerc: true, // include items merc has equipped (if alive) + SaveScreenShot: false, // Save pictures in jpg format (saved in 'Images' folder) + AutoPerm: true, // override InGameTime to perm character + IngameTime: 0, // (180, 210) to avoid RD, increase it to (7230, 7290) for mule perming + LogAccounts: {}, + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + inGameCheck: function () { + if (getScript("D2BotMuleLog.dbj") && this.LogGame[0] && me.gamename.match(this.LogGame[0], "i")) { + print("ÿc4MuleLoggerÿc0: Logging items on " + me.account + " - " + me.name + "."); + D2Bot.printToConsole("MuleLogger: Logging items on " + me.account + " - " + me.name + ".", sdk.colors.D2Bot.DarkGold); + this.logChar(); + let stayInGame = this.IngameTime; + let tick = getTickCount() + rand(1500, 1750) * 1000; // trigger anti-idle every ~30 minutes + + if (this.AutoPerm) { + let permInfo = this.loadPermedStatus(); + + if (!!permInfo.charname) { + if (permInfo.charname === me.charname && !permInfo.perm) { + stayInGame = rand(7230, 7290); + } + } + } + + while ((getTickCount() - me.gamestarttime) < Time.seconds(stayInGame)) { + me.overhead( + "ÿc2Log items done. ÿc4Stay in " + "ÿc4game more:ÿc0 " + + Math.floor(stayInGame - (getTickCount() - me.gamestarttime) / 1000) + " sec" + ); + + delay(1000); + + if ((getTickCount() - tick) > 0) { + Packet.questRefresh(); // quest status refresh, working as anti-idle + tick += rand(1500, 1750) * 1000; + } + } + + try { + quit(); + } finally { + while (me.ingame) { + delay(100); + } + } + + return true; + } + + return false; + }, + + /** + * Save perm status to logs/MuleLogPermInfo.json. + * @param {{ charname: string, perm: boolean }} charPermInfo + */ + savePermedStatus: function (charPermInfo = {}) { + FileTools.writeText("logs/MuleLogPermInfo.json", JSON.stringify(charPermInfo)); + }, + + /** + * Load perm status from logs/MuleLogPermInfo.json. + * @return {{ charname: string, perm: boolean }} + */ + loadPermedStatus: function () { + if (!FileTools.exists("logs/MuleLogPermInfo.json")) { + throw new Error("File logs/MuleLogPermInfo.json does not exist!"); + } + let info = (FileTools.readText("logs/MuleLogPermInfo.json")); + return info ? JSON.parse(info) : {}; + }, + + /** + * @param {string} hash + * @returns {string} + */ + load: function (hash) { + let filename = "data/secure/" + hash + ".txt"; + if (!FileTools.exists(filename)) { + throw new Error("File " + filename + " does not exist!"); + } + return FileTools.readText(filename); + }, + + /** + * @param {string} hash + * @param {string} data + */ + save: function (hash, data) { + let filename = "data/secure/" + hash + ".txt"; + FileTools.writeText(filename, data); + }, + + remove: function () { + FileTools.remove("logs/MuleLog.json"); + FileTools.remove("logs/MuleLogPermInfo.json"); + }, + + /** + * Log kept item stats in the manager. + * @param {ItemUnit} unit + * @param {boolean} [logIlvl] + */ + logItem: function (unit, logIlvl = this.LogItemLevel) { + includeIfNotIncluded("core/misc.js"); + + let header = ""; + let name = ( + unit.itemType + "_" + + unit.fname + .split("\n") + .reverse() + .join(" ") + .replace(/(y|ÿ)c[0-9!"+<:;.*]|\/|\\/g, "") + .trim() + ); + let desc = ( + Item.getItemDesc(unit, logIlvl) + "$" + + unit.gid + ":" + + unit.classid + ":" + + unit.location + ":" + + unit.x + ":" + + unit.y + + (unit.getFlag(sdk.items.flags.Ethereal) ? ":eth" : "") + ); + let color = unit.getColor(); + let code = Item.getItemCode(unit); + let sock = unit.getItemsEx(); + + if (sock.length) { + for (let i = 0; i < sock.length; i += 1) { + if (sock[i].itemType === sdk.items.type.Jewel) { + desc += "\n\n"; + desc += Item.getItemDesc(sock[i], logIlvl); + } + } + } + + return { + itemColor: color, + image: code, + title: name, + description: desc, + header: header, + sockets: Item.getItemSockets(unit) + }; + }, + + /** + * Log character to D2Bot# itemviewer. + * @param {boolean} [logIlvl] + * @param {boolean} [logName] + * @param {boolean} [saveImg] + */ + logChar: function (logIlvl = this.LogItemLevel, logName = this.LogNames, saveImg = this.SaveScreenShot) { + while (!me.gameReady) { + delay(3); + } + + // Dropper handler, todo figure out another way to do this + if (isIncluded("systems/dropper/ItemDB.js") || include("systems/dropper/ItemDB.js")) { + /** @typedef {import("../dropper/ItemDB")} */ + while (!ItemDB.init(false)) { + delay(1000); + } + } + + let items = me.getItemsEx(); + if (!items.length) return; + + let folder, realm = me.realm || "Single Player"; + let finalString = ""; + + if (!FileTools.exists("mules/" + realm)) { + folder = dopen("mules"); + + folder.create(realm); + } + + if (!FileTools.exists("mules/" + realm + "/" + me.account)) { + folder = dopen("mules/" + realm); + + folder.create(me.account); + } + + // from bottom up: merc, equipped, inventory, stash, cube + items.sort(function (a, b) { + if (a.mode < b.mode) return -1; + if (a.mode > b.mode) return 1; + if (a.location === sdk.storage.Cube) return -1; + if (b.location === sdk.storage.Cube) return 1; + return b.location - a.location; + }); + + for (let item of items) { + if ((this.LogEquipped || item.isInStorage) + && (item.quality > sdk.items.quality.Normal || !Item.skipItem(item.classid))) { + let parsedItem = this.logItem(item, logIlvl); + + // Log names to saved image + logName && (parsedItem.header = (me.account || "Single Player") + " / " + me.name); + // Save image to kolbot/images/ + saveImg && D2Bot.saveItem(parsedItem); + // Always put name on Char Viewer items + !parsedItem.header && (parsedItem.header = (me.account || "Single Player") + " / " + me.name); + // Remove itemtype_ prefix from the name + parsedItem.title = parsedItem.title.substr(parsedItem.title.indexOf("_") + 1); + + item.isEquipped && (parsedItem.title += (item.isOnSwap ? " (secondary equipped)" : " (equipped)")); + item.isInInventory && (parsedItem.title += " (inventory)"); + item.isInStash && (parsedItem.title += " (stash)"); + item.isInCube && (parsedItem.title += " (cube)"); + + let string = JSON.stringify(parsedItem); + finalString += (string + "\n"); + } + } + + if (this.LogMerc) { + let merc = Misc.poll(function () { + return me.getMerc(); + }, 1000, 100); + + if (merc) { + let mercItems = merc.getItemsEx(); + + for (let item of mercItems) { + let parsedItem = this.logItem(item); + parsedItem.title += " (merc)"; + let string = JSON.stringify(parsedItem); + finalString += (string + "\n"); + saveImg && D2Bot.saveItem(parsedItem); + } + } + } + + // hcl = hardcore class ladder + // sen = softcore expan nonladder + /** + * @param {string} account + * @param {string} charName + * @param {boolean} playerType + * @param {number} gameType + * @param {boolean} ladder + * @returns {string} + */ + const buildFileName = (account, charName, playerType, gameType, ladder) => ( + "mules/" + realm + "/" + + account + "/" + + charName + "." + + (playerType ? "h" : "s" ) + + (gameType ? "e" : "c" ) + + (ladder ? "l" : "n" ) + + ".txt" + ); + FileTools.writeText( + buildFileName(me.account, me.name, me.playertype, me.gametype, me.ladder > 0), + finalString + ); + + if (!me.ladder) { + let ladderVersion = buildFileName(me.account, me.name, me.playertype, me.gametype, true); + if (FileTools.exists(ladderVersion)) { + // this character is probably being relogged after ladder reset, log that we are deleting and remove the old file + console.log("Found ladder version of this char, removing the old file as assuming this is leftover from before ladder reset."); + FileTools.remove(ladderVersion); + } + } + console.log("Item logging done."); + } +}; + +// load configuration file and apply settings to MuleLogger, has to be after the namespace is created +(function () { + Object.assign(MuleLogger, require("./LoggerConfig", null, false)); +})(); diff --git a/d2bs/kolbot/libs/systems/pubjoin/PubJoinConfig.js b/d2bs/kolbot/libs/systems/pubjoin/PubJoinConfig.js new file mode 100644 index 000000000..7c6dba24e --- /dev/null +++ b/d2bs/kolbot/libs/systems/pubjoin/PubJoinConfig.js @@ -0,0 +1,49 @@ +/** +* @filename PubJoinConfig.js +* @author theBGuy +* @desc Configuration file for D2BotPubJoin system +* +*/ + +(function (module) { + /** + * @description includeFilter format + * @example Multiple entries in the same array mean AND + * // game has to contain "baal" and "-" + * const includeFilter = ["baal", "-"]; + * + * @example Multiple entries in different arrays mean OR + * // will join games with either "baal" or "diablo" in their name + * const includeFilter = [ + * ["baal"], + * ["diablo"] + * ]; + * @type {Array>} + */ + const includeFilter = [ + [""] + ]; + + /** + * @description excludeFilter format + * @example Multiple entries in the same array mean AND + * // ignores games that contain "baal" and "-" + * const includeFilter = ["baal", "-"]; + * + * @example Multiple entries in different arrays mean OR + * // will ignore games with either "baal" or "diablo" in their name + * const includeFilter = [ + * ["baal"], + * ["diablo"] + * ]; + * @type {Array>} + */ + const excludeFilter = [ + [""] + ]; + + module.exports = { + includeFilter: includeFilter, + excludeFilter: excludeFilter, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/torch/FarmerConfig.js b/d2bs/kolbot/libs/systems/torch/FarmerConfig.js new file mode 100644 index 000000000..6290b9d07 --- /dev/null +++ b/d2bs/kolbot/libs/systems/torch/FarmerConfig.js @@ -0,0 +1,45 @@ +/** +* @filename FarmerConfig.js +* @author theBGuy +* @desc Configuration file for TorchSystem system +* +*/ + +(function (module) { + module.exports = { + // ############################ S E T U P ########################################## + + /* Each uber killer profile can have their own army of key finders + Multiple entries are separated with a comma + Example config: + + "Farmer 1": { // Farmer profile name + // Put key finder profiles here. Example - KeyFinderProfiles: ["MF 1", "MF 2"], + KeyFinderProfiles: ["mf 1", "mf 2"], + + // Put the game name of uber killer here (without numbers). Key finders will join this game to drop keys. Example - FarmGame: "Ubers-", + FarmGame: "torch1-" + }, + + "Farmer 2": { // Farmer profile name + // Put key finder profiles here. Example - KeyFinderProfiles: ["MF 1", "MF 2"], + KeyFinderProfiles: ["mf 3", "mf 4"], + + // Put the game name of uber killer here (without numbers). Key finders will join this game to drop keys. Example - FarmGame: "Ubers-", + FarmGame: "torch2-" + } + */ + + // Edit here! + + "PROFILE NAME": { // Farmer profile name + // Put key finder profiles here. Example - KeyFinderProfiles: ["MF 1", "MF 2"], + KeyFinderProfiles: [""], + + // Put the game name of uber killer here (without numbers). Key finders will join this game to drop keys. Example - FarmGame: "Ubers-", + FarmGame: "" + }, + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/torch/TorchSystem.js b/d2bs/kolbot/libs/systems/torch/TorchSystem.js new file mode 100644 index 000000000..92589b84f --- /dev/null +++ b/d2bs/kolbot/libs/systems/torch/TorchSystem.js @@ -0,0 +1,369 @@ +/** +* @filename TorchSystem.js +* @author kolton +* @desc Works in conjunction with OrgTorch script. Allows the uber killer to get keys from other profiles. +* For setup @see FarmerConfig.js +* +*/ + +const TorchSystem = { + // load configuration file + FarmerProfiles: Object.assign({}, require("./FarmerConfig", null, false)), + + // Don't touch + inGame: false, + check: false, + + getFarmers: function () { + let list = []; + + for (let i in this.FarmerProfiles) { + if (this.FarmerProfiles.hasOwnProperty(i)) { + for (let j = 0; j < this.FarmerProfiles[i].KeyFinderProfiles.length; j += 1) { + if (String.isEqual(this.FarmerProfiles[i].KeyFinderProfiles[j], me.profile)) { + this.FarmerProfiles[i].profile = i; + + list.push(this.FarmerProfiles[i]); + } + } + } + } + + return list.length > 0 ? list : false; + }, + + isFarmer: function () { + if (this.FarmerProfiles.hasOwnProperty(me.profile)) { + this.FarmerProfiles[me.profile].profile = me.profile; + + return this.FarmerProfiles[me.profile]; + } + + return false; + }, + + inGameCheck: function () { + let farmers = this.getFarmers(); + if (!farmers) return false; + + for (let i = 0; i < farmers.length; i += 1) { + if (farmers[i].FarmGame.length > 0 && me.gamename.toLowerCase().match(farmers[i].FarmGame.toLowerCase())) { + print("ÿc4Torch Systemÿc0: In Farm game."); + D2Bot.printToConsole("Torch System: Transfering keys.", sdk.colors.D2Bot.DarkGold); + D2Bot.updateStatus("Torch System: In game."); + Town.goToTown(1); + + if (Town.openStash()) { + let neededItems = this.keyCheck(); + + if (neededItems) { + for (let n in neededItems) { + if (neededItems.hasOwnProperty(n)) { + while (neededItems[n].length) { + neededItems[n].shift().drop(); + } + } + } + } + } + + if (me.getStat(sdk.stats.Gold) >= 100000) { + gold(100000); + } + + delay(5000); + + try { + quit(); + } finally { + while (me.ingame) { + delay(100); + } + } + + return true; + } + } + + return false; + }, + + keyCheck: function () { + let neededItems = {}; + let farmers = this.getFarmers(); + if (!farmers) return false; + + function keyCheckEvent(mode, msg) { + if (mode === 6) { + let obj = JSON.parse(msg); + + if (obj.name === "neededItems") { + let item; + + for (let i in obj.value) { + if (obj.value.hasOwnProperty(i) && obj.value[i] > 0) { + switch (i) { + case "pk1": + case "pk2": + case "pk3": + item = me.getItem(i); + + if (item) { + do { + if (!neededItems[i]) { + neededItems[i] = []; + } + + neededItems[i].push(copyUnit(item)); + + if (neededItems[i].length >= obj.value[i]) { + break; + } + } while (item.getNext()); + } + + break; + case "rv": + item = me.getItem(); + + if (item) { + do { + if (item.code === "rvs" || item.code === "rvl") { + if (!neededItems[i]) { + neededItems[i] = []; + } + + neededItems[i].push(copyUnit(item)); + + if (neededItems[i].length >= Math.min(2, obj.value[i])) { + break; + } + } + } while (item.getNext()); + } + + break; + } + } + } + } + } + } + + addEventListener("copydata", keyCheckEvent); + + // TODO: one mfer for multiple farmers handling + for (let i = 0; i < farmers.length; i += 1) { + sendCopyData(null, farmers[i].profile, 6, JSON.stringify({ name: "keyCheck", profile: me.profile })); + delay(250); + + if (neededItems.hasOwnProperty("pk1") || neededItems.hasOwnProperty("pk2") || neededItems.hasOwnProperty("pk3")) { + removeEventListener("copydata", keyCheckEvent); + + return neededItems; + } + } + + removeEventListener("copydata", keyCheckEvent); + + return false; + }, + + outOfGameCheck: function () { + if (!this.check) return false; + TorchSystem.check = false; + + let game; + + function checkEvent(mode, msg) { + let farmers = TorchSystem.getFarmers(); + + if (mode === 6) { + let obj = JSON.parse(msg); + + if (obj && obj.name === "gameName") { + for (let i = 0; i < farmers.length; i += 1) { + if (obj.value.gameName.toLowerCase().match(farmers[i].FarmGame.toLowerCase())) { + game = [obj.value.gameName, obj.value.password]; + } + } + } + } + + return true; + } + + let farmers = this.getFarmers(); + if (!farmers) return false; + + addEventListener("copydata", checkEvent); + + for (let i = 0; i < farmers.length; i += 1) { + sendCopyData(null, farmers[i].profile, 6, JSON.stringify({ name: "gameCheck", profile: me.profile })); + delay(500); + + if (game) { + break; + } + } + + removeEventListener("copydata", checkEvent); + + if (game) { + delay(2000); + + TorchSystem.inGame = true; + me.blockMouse = true; + + joinGame(game[0], game[1]); + + me.blockMouse = false; + + delay(5000); + + while (me.ingame) { + delay(1000); + } + + TorchSystem.inGame = false; + + return true; + } + + return false; + }, + + waitForKeys: function () { + let timer = getTickCount(); + let busy = false; + let busyTick; + let tkeys = me.findItems("pk1", sdk.items.mode.inStorage).length || 0; + let hkeys = me.findItems("pk2", sdk.items.mode.inStorage).length || 0; + let dkeys = me.findItems("pk3", sdk.items.mode.inStorage).length || 0; + let neededItems = { pk1: 0, pk2: 0, pk3: 0, rv: 0 }; + + // Check whether the killer is alone in the game + const aloneInGame = function () { + return (Misc.getPlayerCount() <= 1); + }; + + const juvCheck = function () { + let needJuvs = 0; + let col = Town.checkColumns(Storage.BeltSize()); + + for (let i = 0; i < 4; i += 1) { + if (Config.BeltColumn[i] === "rv") { + needJuvs += col[i]; + } + } + + console.log("Need " + needJuvs + " juvs."); + + return needJuvs; + }; + + // Check if current character is the farmer + let farmer = TorchSystem.isFarmer(); + + const torchSystemEvent = function (mode, msg) { + let obj, farmer; + + if (mode === 6) { + farmer = TorchSystem.isFarmer(); + + if (farmer) { + obj = JSON.parse(msg); + + if (obj) { + switch (obj.name) { + case "gameCheck": + if (busy) { + break; + } + + if (farmer.KeyFinderProfiles.includes(obj.profile)) { + print("Got game request from: " + obj.profile); + sendCopyData( + null, obj.profile, 6, + JSON.stringify({ name: "gameName", value: { gameName: me.gamename, password: me.gamepassword } }) + ); + + busy = true; + busyTick = getTickCount(); + } + + break; + case "keyCheck": + if (farmer.KeyFinderProfiles.includes(obj.profile)) { + print("Got key count request from: " + obj.profile); + + // Get the number of needed keys + neededItems = { pk1: 3 - tkeys, pk2: 3 - hkeys, pk3: 3 - dkeys, rv: juvCheck() }; + sendCopyData(null, obj.profile, 6, JSON.stringify({ name: "neededItems", value: neededItems })); + } + + break; + } + } + } + } + }; + + // Register event that will communicate with key hunters, go to Act 1 town and wait by stash + addEventListener("copydata", torchSystemEvent); + Town.goToTown(1); + Town.move("stash"); + + try { + while (true) { + // Abort if the current character isn't a farmer + if (!farmer) { + break; + } + + // Free up inventory + me.needStash() && Town.stash(); + + // Get the number keys + tkeys = me.findItems("pk1", sdk.items.mode.inStorage).length || 0; + hkeys = me.findItems("pk2", sdk.items.mode.inStorage).length || 0; + dkeys = me.findItems("pk3", sdk.items.mode.inStorage).length || 0; + + // Stop the loop if we have enough keys or if wait time expired + if (((tkeys >= 3 && hkeys >= 3 && dkeys >= 3) + || (Config.OrgTorch.WaitTimeout + && (getTickCount() - timer > Time.minutes(Config.OrgTorch.WaitTimeout)))) + && aloneInGame()) { + + break; + } + + if (busy) { + while (getTickCount() - busyTick < 30000) { + if (!aloneInGame()) { + break; + } + + delay(100); + } + + if (getTickCount() - busyTick > 30000 || aloneInGame()) { + busy = false; + } + } + + // Wait for other characters to leave + while (!aloneInGame()) { + delay(500); + } + + delay(1000); + + // Pick the keys after the hunters drop them and leave the game + Pickit.pickItems(); + } + } finally { + removeEventListener("copydata", torchSystemEvent); + } + }, +}; diff --git a/d2bs/kolbot/sdk/LocaleStringID.js b/d2bs/kolbot/sdk/LocaleStringID.js deleted file mode 100644 index 1bf479200..000000000 --- a/d2bs/kolbot/sdk/LocaleStringID.js +++ /dev/null @@ -1,7794 +0,0 @@ -/** -* @filename LocaleStringID.js -* @author Nishimura-Katsuo -* @desc locale string indexes from NameStr ids -*/ - -var LocaleStringID = { - "WarrivAct1IntroGossip1": 0, - "WarrivAct1IntroPalGossip1": 1, - "WarrivGossip1": 2, - "WarrivGossip2": 3, - "WarrivGossip3": 4, - "WarrivGossip4": 5, - "WarrivGossip5": 6, - "WarrivGossip6": 7, - "WarrivGossip7": 8, - "WarrivGossip8": 9, - "WarrivGossip9": 10, - "AkaraIntroGossip1": 11, - "AkaraIntroSorGossip1": 12, - "AkaraGossip1": 13, - "AkaraGossip2": 14, - "AkaraGossip3": 15, - "AkaraGossip4": 16, - "AkaraGossip5": 17, - "AkaraGossip6": 18, - "AkaraGossip7": 19, - "AkaraGossip8": 20, - "AkaraGossip9": 21, - "AkaraGossip10": 22, - "AkaraGossip11": 23, - "KashyaIntroGossip1": 24, - "KashyaIntroAmaGossip1": 25, - "KashyaGossip1": 26, - "KashyaGossip2": 27, - "KashyaGossip3": 28, - "KashyaGossip4": 29, - "KashyaGossip5": 30, - "KashyaGossip6": 31, - "KashyaGossip7": 32, - "KashyaGossip8": 33, - "KashyaGossip9": 34, - "KashyaGossip10": 35, - "CharsiIntroGossip1": 36, - "CharsiIntroBarGossip1": 37, - "CharsiGossip1": 38, - "CharsiGossip2": 39, - "CharsiGossip3": 40, - "CharsiGossip4": 41, - "CharsiGossip5": 42, - "CharsiGossip6": 43, - "CharsiGossip7": 44, - "GheedIntroGossip1": 45, - "GheedIntroNecGossip1": 46, - "GheedGossip1": 47, - "GheedGossip2": 48, - "GheedGossip3": 49, - "GheedGossip4": 50, - "GheedGossip5": 51, - "GheedGossip6": 52, - "GheedGossip7": 53, - "CainGossip1": 54, - "CainGossip2": 55, - "CainGossip3": 56, - "CainGossip4": 57, - "CainGossip5": 58, - "RogueSignpostGossip1": 59, - "RogueSignpostGossip2": 60, - "RogueSignpostGossip3": 61, - "RogueSignpostGossip4": 62, - "RogueSignpostGossip5": 63, - "A1Q1InitAkara": 64, - "A1Q1AfterInitAkara": 65, - "A1Q1AfterInitKashya": 66, - "A1Q1AfterInitCharsiMain": 67, - "A1Q1AfterInitCharsiAlt": 68, - "A1Q1AfterInitGheed": 69, - "A1Q1AfterInitWarriv": 70, - "A1Q1EarlyReturnAkara": 71, - "A1Q1EarlyReturnKashya": 72, - "A1Q1EarlyReturnCharsi": 73, - "A1Q1EarlyReturnGheed": 74, - "A1Q1EarlyReturnWarriv": 75, - "A1Q1SuccessfulAkara": 76, - "A1Q1SuccessfulKashya": 77, - "A1Q1SuccessfulCharsi": 78, - "A1Q1SuccessfulGheed": 79, - "A1Q1SuccessfulWarriv": 80, - "A1Q2InitKashya": 81, - "A1Q2AfterInitKashya": 82, - "A1Q2AfterInitCharsi": 83, - "A1Q2AfterInitGheed": 84, - "A1Q2AfterInitAkara": 85, - "A1Q2AfterInitWarriv": 86, - "A1Q2EarlyReturnKashya": 87, - "A1Q2EarlyReturnAkara": 88, - "A1Q2EarlyReturnCharsi": 89, - "A1Q2EarlyReturnGheed": 90, - "A1Q2EarlyReturnWarriv": 91, - "A1Q2SuccessfulKashya": 92, - "A1Q2SuccessfulAkara": 93, - "A1Q2SuccessfulCharsi": 94, - "A1Q2SuccessfulGheed": 95, - "A1Q2SuccessfulWarriv": 96, - "A1Q4InitAkara": 97, - "A1Q4AfterInitScrollKashya": 98, - "A1Q4AfterInitScrollAkara": 99, - "A1Q4AfterInitScrollCharsi": 100, - "A1Q4AfterInitScrollWarriv": 101, - "A1Q4AfterInitScrollGheed": 102, - "A1Q4InstructionsCharsi": 103, - "A1Q4EarlyReturnSAkara": 104, - "A1Q4EarlyReturnSKashya": 105, - "A1Q4EarlyReturnSGheed": 106, - "A1Q4EarlyReturnSWarriv": 107, - "A1Q4SuccessfulScrollKashya": 108, - "A1Q4SuccessfulScrollCharsi": 109, - "A1Q4SuccessfulScrollGheed": 110, - "A1Q4SuccessfulScrollWarriv": 111, - "A1Q4InstructionsAkara": 112, - "A1Q4EarlyReturnKashya": 113, - "A1Q4EarlyReturnCharsi": 114, - "A1Q4EarlyReturnGheed": 115, - "A1Q4EarlyReturnWarriv": 116, - "A1Q4EarlyReturnAkara": 117, - "A1Q4QuestSuccessfulAkara": 118, - "A1Q4QuestSuccessfulKashya": 119, - "A1Q4QuestSuccessfulGheed": 120, - "A1Q4QuestSuccessfulCharsi": 121, - "A1Q4QuestSuccessfulWarriv": 122, - "A1Q4QuestSuccessfulCain": 123, - "A1Q4RescuedByHeroCain": 124, - "A1Q4RescuedByRoguesCain": 125, - "A1Q4TragedyOfTristramCain": 126, - "A1Q5InitQuestTome": 127, - "A1Q5AfterInitGheed": 128, - "A1Q5AfterInitCharsi": 129, - "A1Q5AfterInitAkara": 130, - "A1Q5AfterInitCain": 131, - "A1Q5AfterInitWarriv": 132, - "A1Q5AfterInitKashya": 133, - "A1Q5EarlyReturnKashya": 134, - "A1Q5EarlyReturnCain": 135, - "A1Q5EarlyReturnWarriv": 136, - "A1Q5EarlyReturnCharsi": 137, - "A1Q5EarlyReturnAkara": 138, - "A1Q5EarlyReturnGheed": 139, - "A1Q5SuccessfulKashya": 140, - "A1Q5SuccessfulWarriv": 141, - "A1Q5SuccessfulGheed": 142, - "A1Q5SuccessfulAkara": 143, - "A1Q5SuccessfulCharsi": 144, - "A1Q5SuccessfulCain": 145, - "A1Q3InitCharsi": 146, - "A1Q3AfterInitCain": 147, - "A1Q3AfterInitAkara": 148, - "A1Q3AfterInitKashya": 149, - "A1Q3AfterInitCharsi": 150, - "A1Q3AfterInitGheed": 151, - "A1Q3AfterInitGheedAlt": 152, - "A1Q3AfterInitWarriv": 153, - "A1Q3EarlyReturnCain": 154, - "A1Q3EarlyReturnAkara": 155, - "A1Q3EarlyReturnKashya": 156, - "A1Q3EarlyReturnCharsi": 157, - "A1Q3EarlyReturnGheed": 158, - "A1Q3EarlyReturnWarriv": 159, - "A1Q3SuccessfulCain": 160, - "A1Q3SuccessfulAkara": 161, - "A1Q3SuccessfulKashya": 162, - "A1Q3SuccessfulCharsi": 163, - "A1Q3SuccessfulGheed": 164, - "A1Q3SuccessfulWarriv": 165, - "A1Q6InitCain": 166, - "A1Q6AfterInitCain": 167, - "A1Q6AfterInitAkara": 168, - "A1Q6AfterInitCharsi": 169, - "A1Q6AfterInitGheed": 170, - "A1Q6AfterInitWarriv": 171, - "A1Q6AfterInitKashya": 172, - "A1Q6EarlyReturnCain": 173, - "A1Q6EarlyReturnAkara": 174, - "A1Q6EarlyReturnGheed": 175, - "A1Q6EarlyReturnCharsi": 176, - "A1Q6EarlyReturnWarriv": 177, - "A1Q6EarlyReturn2Kashya": 178, - "A1Q6SuccessfulAkara": 179, - "A1Q6SuccessfulCharsi": 180, - "A1Q6SuccessfulKashya": 181, - "A1Q6SuccessfulGheed": 182, - "A1Q6SuccessfulWarriv": 183, - "A1Q6SuccessfulCain": 184, - "PalaceGuardGossip1": 185, - "PalaceGuardGossip2": 186, - "PalaceGuardGossip3": 187, - "PalaceGuardGossip4": 188, - "PalaceGuardGossip5": 189, - "GriezIntroGossip1": 190, - "GriezGossip1": 191, - "GriezGossip2": 192, - "GriezGossip3": 193, - "GriezGossip4": 194, - "GriezGossip5": 195, - "GriezGossip6": 196, - "GriezGossip7": 197, - "GriezGossip8": 198, - "GriezGossip9": 199, - "GriezGossip10": 200, - "GriezGossip11": 201, - "GriezGossip12": 202, - "ElzixIntroGossip1": 203, - "ElzixIntroNecGossip1": 204, - "ElzixGossip1": 205, - "ElzixGossip2": 206, - "ElzixGossip3": 207, - "ElzixGossip4": 208, - "ElzixGossip5": 209, - "ElzixGossip6": 210, - "ElzixGossip7": 211, - "ElzixGossip8": 212, - "ElzixGossip9": 213, - "ElzixGossip10": 214, - "WarrivAct2IntroGossip1": 215, - "WarrivAct2Gossip1": 216, - "WarrivAct2Gossip2": 217, - "WarrivAct2Gossip3": 218, - "WarrivAct2Gossip4": 219, - "WarrivAct2Gossip5": 220, - "AtmaIntroGossip1": 221, - "AtmaGossip1": 222, - "AtmaGossip2": 223, - "AtmaGossip3": 224, - "AtmaGossip4": 225, - "AtmaGossip5": 226, - "AtmaGossip6": 227, - "AtmaGossip7": 228, - "AtmaGossip8": 229, - "GeglashIntroGossip1": 230, - "GeglashIntroBarGossip1": 231, - "GeglashGossip1": 232, - "GeglashGossip2": 233, - "GeglashGossip3": 234, - "GeglashGossip4": 235, - "GeglashGossip5": 236, - "GeglashGossip6": 237, - "GeglashGossip7": 238, - "GeglashGossip8": 239, - "GeglashGossip9": 240, - "MeshifIntroGossip1": 241, - "MeshifIntroAmaGossip1": 242, - "MeshifGossip1": 243, - "MeshifGossip2": 244, - "MeshifGossip3": 245, - "MeshifGossip4": 246, - "MeshifGossip5": 247, - "MeshifGossip6": 248, - "MeshifGossip7": 249, - "MeshifGossip8": 250, - "MeshifGossip9": 251, - "MeshifGossip10": 252, - "JerhynActIntroGossip1": 253, - "JerhynActIntroMoreGossip1": 254, - "JerhynIntroGossip1": 255, - "JerhynGossip1": 256, - "JerhynGossip2": 257, - "JerhynGossip3": 258, - "JerhynGossip4": 259, - "JerhynGossip5": 260, - "JerhynGossip6": 261, - "JerhynGossip7": 262, - "FaraIntroGossip1": 263, - "FaraIntroPalGossip1": 264, - "FaraGossip1": 265, - "FaraGossip2": 266, - "FaraGossip3": 267, - "FaraGossip4": 268, - "FaraGossip5": 269, - "FaraGossip6": 270, - "FaraGossip7": 271, - "FaraGossip8": 272, - "FaraGossip9": 273, - "LysanderIntroGossip1": 274, - "LysanderGossip1": 275, - "LysanderGossip2": 276, - "LysanderGossip3": 277, - "LysanderGossip4": 278, - "LysanderGossip5": 279, - "LysanderGossip6": 280, - "LysanderGossip7": 281, - "LysanderGossip8": 282, - "LysanderGossip9": 283, - "LysanderGossip10": 284, - "DrognanIntroGossip1": 285, - "DrognanIntroSorGossip1": 286, - "DrognanGossip1": 287, - "DrognanGossip2": 288, - "DrognanGossip3": 289, - "DrognanGossip4": 290, - "DrognanGossip5": 291, - "DrognanGossip6": 292, - "DrognanGossip7": 293, - "DrognanGossip8": 294, - "DrognanGossip9": 295, - "DrognanGossip10": 296, - "CainAct2Gossip1": 297, - "CainAct2Gossip2": 298, - "CainAct2Gossip3": 299, - "CainAct2Gossip4": 300, - "CainAct2Gossip5": 301, - "TyraelGossip1": 302, - "Desert2GuardGossip1": 303, - "A2Q1InitAtma": 304, - "A2Q1AfterInitGreiz": 305, - "A2Q1AfterInitElzix": 306, - "A2Q1AfterInitWarrivAct2": 307, - "A2Q1AfterInitGeglash": 308, - "A2Q1AfterInitFara": 309, - "A2Q1AfterInitAtma": 310, - "A2Q1AfterInitMeshif": 311, - "A2Q1AfterInitDrognan": 312, - "A2Q1AfterInitLysander": 313, - "A2Q1AfterInitCain": 314, - "A2Q1EarlyReturnWarrivAct2": 315, - "A2Q1EarlyReturnMeshif": 316, - "A2Q1EarlyReturnAtma": 317, - "A2Q1EarlyReturnGreiz": 318, - "A2Q1EarlyReturnGeglash": 319, - "A2Q1EarlyReturnElzix": 320, - "A2Q1EarlyReturnLysander": 321, - "A2Q1EarlyReturnDrognan": 322, - "A2Q1EarlyReturnFara": 323, - "A2Q1EarlyReturnCain": 324, - "A2Q1SuccessfulGreiz": 325, - "A2Q1SuccessfulDrognan": 326, - "A2Q1SuccessfulLysander": 327, - "A2Q1SuccessfulMeshif": 328, - "A2Q1SuccessfulGeglash": 329, - "A2Q1SuccessfulElzix": 330, - "A2Q1SuccessfulWarrivAct2": 331, - "A2Q1SuccessfulFara": 332, - "A2Q1SuccessfulCain": 333, - "A2Q1SuccessfulAtma": 334, - "A2Q2EarlyReturnScrollCain": 335, - "A2Q2EarlyReturnCapCain": 336, - "A2Q2EarlyReturnStaveCain": 337, - "A2Q2EarlyReturnCubeCain": 338, - "A2Q2SuccessfulStaffCain": 339, - "A2Q3AfterInitJerhyn": 340, - "A2Q3AfterInitGreiz": 341, - "A2Q3AfterInitElzix": 342, - "A2Q3AfterInitWarrivAct2": 343, - "A2Q3AfterInitAtma": 344, - "A2Q3AfterInitGeglash": 345, - "A2Q3AfterInitFara": 346, - "A2Q3AfterInitLysander": 347, - "A2Q3AfterInitDrognan": 348, - "A2Q3AfterInitMeshif": 349, - "A2Q3AfterInitCain": 350, - "A2Q3EarlyReturnJerhyn": 351, - "A2Q3EarlyReturnGreiz": 352, - "A2Q3EarlyReturnWarrivAct2": 353, - "A2Q3EarlyReturnGeglash": 354, - "A2Q3EarlyReturnMeshif": 355, - "A2Q3EarlyReturnFara": 356, - "A2Q3EarlyReturnLysander": 357, - "A2Q3EarlyReturnDrognan": 358, - "A2Q3EarlyReturnElzix": 359, - "A2Q3EarlyReturnCain": 360, - "A2Q3EarlyReturnAtma": 361, - "A2Q3SuccessfulJerhyn": 362, - "A2Q3SuccessfulGreiz": 363, - "A2Q3SuccessfulElzix": 364, - "A2Q3SuccessfulGeglash": 365, - "A2Q3SuccessfulWarrivAct2": 366, - "A2Q3SuccessfulMeshif": 367, - "A2Q3SuccessfulAtma": 368, - "A2Q3SuccessfulFara": 369, - "A2Q3SuccessfulLysander": 370, - "A2Q3SuccessfulDrognan": 371, - "A2Q3SuccessfulCain": 372, - "A2Q4InitDrognan": 373, - "A2Q4AfterInitFara": 374, - "A2Q4AfterInitGreiz": 375, - "A2Q4AfterInitElzix": 376, - "A2Q4AfterInitJerhyn": 377, - "A2Q4AfterInitCain": 378, - "A2Q4AfterInitGeglash": 379, - "A2Q4AfterInitAtma": 380, - "A2Q4AfterInitWarrivAct2": 381, - "A2Q4AfterInitLysander": 382, - "A2Q4AfterInitDrognan": 383, - "A2Q4AfterInitMeshif": 384, - "A2Q4EarlyReturnElzix": 385, - "A2Q4EarlyReturnJerhyn": 386, - "A2Q4EarlyReturnGreiz": 387, - "A2Q4EarlyReturnDrognan": 388, - "A2Q4EarlyReturnLysander": 389, - "A2Q4EarlyReturnFara": 390, - "A2Q4EarlyReturnGeglash": 391, - "A2Q4EarlyReturnMeshif": 392, - "A2Q4EarlyReturnAtma": 393, - "A2Q4EarlyReturnWarrivAct2": 394, - "A2Q4EarlyReturnCain": 395, - "A2Q4SuccessfulNarrator": 396, - "A2Q4SuccessfulGriez": 397, - "A2Q4SuccessfulJerhyn": 398, - "A2Q4SuccessfulDrognan": 399, - "A2Q4SuccessfulElzix": 400, - "A2Q4SuccessfulGeglash": 401, - "A2Q4SuccessfulMeshif": 402, - "A2Q4SuccessfulWarrivAct2": 403, - "A2Q4SuccessfulFara": 404, - "A2Q4SuccessfulLysander": 405, - "A2Q4SuccessfulAtma": 406, - "A2Q4SuccessfulCain": 407, - "A2Q5EarlyReturnGreiz": 408, - "A2Q5EarlyReturnJerhyn": 409, - "A2Q5EarlyReturnDrognan": 410, - "A2Q5EarlyReturnLysander": 411, - "A2Q5EarlyReturnMeshif": 412, - "A2Q5EarlyReturnWarrivAct2": 413, - "A2Q5EarlyReturnAtma": 414, - "A2Q5EarlyReturnGeglash": 415, - "A2Q5EarlyReturnFara": 416, - "A2Q5EarlyReturnElzix": 417, - "A2Q5EarlyReturnCain": 418, - "A2Q5SuccessfulGreiz": 419, - "A2Q5SuccessfulGeglash": 420, - "A2Q5SuccessfulJerhyn": 421, - "A2Q5SuccessfulDrognan": 422, - "A2Q5SuccessfulElzix": 423, - "A2Q5SuccessfulWarrivAct2": 424, - "A2Q5SuccessfulMeshif": 425, - "A2Q5SuccessfulLysander": 426, - "A2Q5SuccessfulAtma": 427, - "A2Q5SuccessfulFara": 428, - "A2Q5SuccessfulCain": 429, - "A2Q6InitJerhyn": 430, - "A2Q6AfterInitJerhyn": 431, - "A2Q6AfterInitElzix": 432, - "A2Q6AfterInitWarrivAct2": 433, - "A2Q6AfterInitAtma": 434, - "A2Q6AfterInitGeglash": 435, - "A2Q6AfterInitMeshif": 436, - "A2Q6AfterInitFara": 437, - "A2Q6AfterInitLysander": 438, - "A2Q6AfterInitDrognan": 439, - "A2Q6AfterInitCain": 440, - "A2Q6AfterInitGreiz": 441, - "A2Q6SuccessfulJerhyn": 442, - "A2Q6SuccessfulElzix": 443, - "A2Q6SuccessfulLysander": 444, - "A2Q6SuccessfulAtma": 445, - "A2Q6SuccessfulWarrivAct2": 446, - "A2Q6SuccessfulFara": 447, - "A2Q6SuccessfulGeglash": 448, - "A2Q6SuccessfulDrognan": 449, - "A2Q6SuccessfulMeshif": 450, - "A2Q6SuccessfulGreiz": 451, - "A2Q6SuccessfulCain": 452, - "NatalyaIntroGossip1": 453, - "NatalyaGossip1": 454, - "NatalyaGossip2": 455, - "NatalyaGossip3": 456, - "NatalyaGossip4": 457, - "CainAct3IntroGossip1": 458, - "CainAct3Gossip1": 459, - "CainAct3Gossip2": 460, - "CainAct3Gossip3": 461, - "CainAct3Gossip4": 462, - "CainAct3Gossip5": 463, - "CainAct3Gossip6": 464, - "HratliActIntroGossip1": 465, - "HratliActIntroSorGossip1": 466, - "HratliGossip1": 467, - "HratliGossip2": 468, - "HratliGossip3": 469, - "HratliGossip4": 470, - "HratliGossip5": 471, - "HratliGossip6": 472, - "HratliGossip7": 473, - "HratliGossip8": 474, - "HratliGossip9": 475, - "HratliGossip10": 476, - "HratliGossip11": 477, - "MeshifAct3IntroGossip1": 478, - "MeshifAct3IntroBarGossip1": 479, - "MeshifAct3Gossip1": 480, - "MeshifAct3Gossip2": 481, - "MeshifAct3Gossip3": 482, - "MeshifAct3Gossip4": 483, - "MeshifAct3Gossip5": 484, - "MeshifAct3Gossip6": 485, - "MeshifAct3Gossip7": 486, - "MeshifAct3Gossip8": 487, - "MeshifAct3Gossip9": 488, - "MeshifAct3Gossip10": 489, - "AshearaIntroGossip1": 490, - "AshearaIntroAmaGossip1": 491, - "AshearaGossip1": 492, - "AshearaGossip2": 493, - "AshearaGossip3": 494, - "AshearaGossip4": 495, - "AshearaGossip5": 496, - "AshearaGossip6": 497, - "AshearaGossip7": 498, - "AshearaGossip8": 499, - "AshearaGossip9": 500, - "AlkorIntroGossip1": 501, - "AlkorIntroNecGossip1": 502, - "AlkorGossip1": 503, - "AlkorGossip2": 504, - "AlkorGossip3": 505, - "AlkorGossip4": 506, - "AlkorGossip5": 507, - "AlkorGossip6": 508, - "AlkorGossip7": 509, - "AlkorGossip8": 510, - "AlkorGossip9": 511, - "AlkorGossip10": 512, - "AlkorGossip11": 513, - "OrmusIntroGossip1": 514, - "OrmusIntroPalGossip1": 515, - "OrmusGossip1": 516, - "OrmusGossip2": 517, - "OrmusGossip3": 518, - "OrmusGossip4": 519, - "OrmusGossip5": 520, - "OrmusGossip6": 521, - "OrmusGossip7": 522, - "OrmusGossip8": 523, - "OrmusGossip9": 524, - "OrmusGossip10": 525, - "OrmusGossip11": 526, - "A3Q4Init1CainAct3": 527, - "A3Q4Init1Asheara": 528, - "A3Q4Init2MeshifAct3": 529, - "A3Q4Init2Natalya": 530, - "A3Q4Init3CainAct3": 531, - "A3Q4Init3Hratli": 532, - "A3Q4Init3Asheara": 533, - "A3Q4AfterInitAlkor": 534, - "A3Q4AfterInitOrmus": 535, - "A3Q4AfterInitHratli": 536, - "A3Q4AfterInitNatalya": 537, - "A3Q4SuccessfulAlkor": 538, - "A3Q4SuccessfulMeshifAct3": 539, - "A3Q4SuccessfulCainAct3": 540, - "A3Q4SuccessfulOrmus": 541, - "A3Q4SuccessfulNatalya": 542, - "A3Q2InitCain": 543, - "A3Q2EarlyReturnHeartCain": 544, - "A3Q2EarlyReturnEyeCain": 545, - "A3Q2EarlyReturnBrainCain": 546, - "A3Q2EarlyReturnFlailCain": 547, - "A3Q2SuccessfulCain": 548, - "A3Q1InitAlkor": 549, - "A3Q1AfterInitAlkor": 550, - "A3Q1AfterInitOrmus": 551, - "A3Q1AfterInitMeshifAct3": 552, - "A3Q1AfterInitAsheara": 553, - "A3Q1AfterInitHratli": 554, - "A3Q1AfterInitCainAct3": 555, - "A3Q1AfterInitNatalya": 556, - "A3Q1EarlyReturnAlkor": 557, - "A3Q1EarlyReturnOrmus": 558, - "A3Q1EarlyReturnMeshifAct3": 559, - "A3Q1EarlyReturnAsheara": 560, - "A3Q1EarlyReturnHratli": 561, - "A3Q1EarlyReturnCainAct3": 562, - "A3Q1EarlyReturnNatalya": 563, - "A3Q1SuccessfulAlkor": 564, - "A3Q1SuccessfulOrmus": 565, - "A3Q1SuccessfulMeshifAct3": 566, - "A3Q1SuccessfulAsheara": 567, - "A3Q1SuccessfulHratli": 568, - "A3Q1SuccessfulCainAct3": 569, - "A3Q1SuccessfulNatalya": 570, - "A3Q3InitHratli": 571, - "A3Q3AfterInitAlkor": 572, - "A3Q3AfterInitOrmus": 573, - "A3Q3AfterInitMeshifAct3": 574, - "A3Q3AfterInitAsheara": 575, - "A3Q3AfterInitHratli": 576, - "A3Q3AfterInitCainAct3": 577, - "A3Q3AfterInitNatalya": 578, - "A3Q3EarlyReturnAlkor": 579, - "A3Q3EarlyReturnOrmus": 580, - "A3Q3EarlyReturnMeshifAct3": 581, - "A3Q3EarlyReturnAsheara": 582, - "A3Q3EarlyReturnHratli": 583, - "A3Q3EarlyReturnCainAct3": 584, - "A3Q3EarlyReturnNatalya": 585, - "A3Q3SuccessfulAlkor": 586, - "A3Q3SuccessfulOrmus": 587, - "A3Q3SuccessfulMeshifAct3": 588, - "A3Q3SuccessfulAsheara": 589, - "A3Q3SuccessfulHratli": 590, - "A3Q3SuccessfulCainAct3": 591, - "A3Q3SuccessfulNatalya": 592, - "A3Q3RewardOrmus": 593, - "A3Q5InitOrmus": 594, - "A3Q5AfterInitAlkor": 595, - "A3Q5AfterInitAlkorVA": 596, - "A3Q5AfterInitOrmus": 597, - "A3Q5AfterInitOrmusVA": 598, - "A3Q5AfterInitMeshifAct3": 599, - "A3Q5AfterInitMeshifAct3VA": 600, - "A3Q5AfterInitAsheara": 601, - "A3Q5AfterInitAshearaVA": 602, - "A3Q5AfterInitHratli": 603, - "A3Q5AfterInitHratliVA": 604, - "A3Q5AfterInitCainAct3": 605, - "A3Q5AfterInitCainAct3VA": 606, - "A3Q5AfterInitNatalya": 607, - "A3Q5AfterInitNatalyaVA": 608, - "A3Q5EarlyReturnAlkor": 609, - "A3Q5EarlyReturnAlkorVA": 610, - "A3Q5EarlyReturnOrmus": 611, - "A3Q5EarlyReturnMeshifAct3": 612, - "A3Q5EarlyReturnMeshifAct3VA": 613, - "A3Q5EarlyReturnAsheara": 614, - "A3Q5EarlyReturnAshearaVA": 615, - "A3Q5EarlyReturnHratli": 616, - "A3Q5EarlyReturnHratliVA": 617, - "A3Q5EarlyReturnCainAct3": 618, - "A3Q5EarlyReturnNatalya": 619, - "A3Q5EarlyReturnNatalyaVA": 620, - "A3Q5SuccessfulAlkor": 621, - "A3Q5SuccessfulOrmus": 622, - "A3Q5SuccessfulMeshifAct3": 623, - "A3Q5SuccessfulAsheara": 624, - "A3Q5SuccessfulHratli": 625, - "A3Q5SuccessfulCainAct3": 626, - "A3Q5SuccessfulNatalya": 627, - "A3Q6InitOrmus": 628, - "A3Q6AfterInitAlkor": 629, - "A3Q6AfterInitAlkorVA": 630, - "A3Q6AfterInitOrmus": 631, - "A3Q6AfterInitOrmusVA": 632, - "A3Q6AfterInitMeshifAct3": 633, - "A3Q6AfterInitMeshifAct3VA": 634, - "A3Q6AfterInitAsheara": 635, - "A3Q6AfterInitAshearaVA": 636, - "A3Q6AfterInitHratli": 637, - "A3Q6AfterInitHratliVA": 638, - "A3Q6AfterInitCainAct3": 639, - "A3Q6AfterInitCainAct3VA": 640, - "A3Q6AfterInitNatalya": 641, - "A3Q6AfterInitNatalyaVA": 642, - "A3Q6EarlyReturnAlkor": 643, - "A3Q6EarlyReturnAlkorVA": 644, - "A3Q6EarlyReturnOrmus": 645, - "A3Q6EarlyReturnOrmusVA": 646, - "A3Q6EarlyReturnMeshifAct3": 647, - "A3Q6EarlyReturnMeshifAct3VA": 648, - "A3Q6EarlyReturnAsheara": 649, - "A3Q6EarlyReturnAshearaVA": 650, - "A3Q6EarlyReturnHratli": 651, - "A3Q6EarlyReturnHratliVA": 652, - "A3Q6EarlyReturnCainAct3": 653, - "A3Q6EarlyReturnCainAct3VA": 654, - "A3Q6EarlyReturnNatalya": 655, - "A3Q6EarlyReturnNatalyaVA": 656, - "A3Q6SuccessfulAlkor": 657, - "A3Q6SuccessfulOrmus": 658, - "A3Q6SuccessfulMeshifAct3": 659, - "A3Q6SuccessfulAsheara": 660, - "A3Q6SuccessfulHratli": 661, - "A3Q6SuccessfulCainAct3": 662, - "A3Q6SuccessfulNatalya": 663, - "TyraelActIntroGossip1": 664, - "TyraelAct4Gossip1": 665, - "CainAct4IntroGossip1": 666, - "CainAct4Gossip1": 667, - "HellsAngelGossip1": 668, - "HellsAngelGossip2": 669, - "A4Q1InitTyrael": 670, - "A4Q1AfterInitTyrael": 671, - "A4Q1AfterInitCain": 672, - "A4Q1EarlyReturnTyrael": 673, - "A4Q1EarlyReturnCain": 674, - "A4Q1SuccessfulIzual": 675, - "A4Q1SuccessfulTyrael": 676, - "A4Q1SuccessfulCain": 677, - "A4Q3InitHasStoneCain": 678, - "A4Q3InitNoStoneCain": 679, - "A4Q3SuccessfulCain": 680, - "A4Q2InitTyrael": 681, - "A4Q2AfterInitCain": 682, - "A4Q2AfterInitTyrael": 683, - "A4Q2SuccessfulTyrael": 684, - "A4Q2SuccessfulCain": 685, - "D2bnetHelp50": 686, - "D2bnetHelp": 687, - "D2bnetHelp2a": 688, - "D2bnetHelpa": 689, - "D2bnetHelp1": 690, - "D2bnetHelp2": 691, - "D2bnetHelp3": 692, - "D2bnetHelp4": 693, - "D2bnetHelp5": 694, - "D2bnetHelp5a": 695, - "D2bnetHelp6": 696, - "D2bnetHelp7": 697, - "D2bnetHelp8": 698, - "D2bnetHelp9": 699, - "D2bnetHelp10": 700, - "D2bnetHelp11": 701, - "D2bnetHelp36": 702, - "D2bnetHelp36a": 703, - "D2bnetHelp37": 704, - "D2bnetHelp37a": 705, - "D2bnetHelp38": 706, - "D2bnetHelp39": 707, - "D2bnetHelp40": 708, - "D2bnetHelp41": 709, - "D2bnetHelp42": 710, - "D2bnetHelp42a": 711, - "D2bnetHelp43": 712, - "D2bnetHelp44": 713, - "D2bnetHelp44ab": 714, - "D2bnetHelp44a": 715, - "D2bnetHelp45": 716, - "D2bnetHelp45b": 717, - "D2bnetHelp45a": 718, - "D2bnetHel46": 719, - "D2bnetHelp46a": 720, - "D2bnetHelp47": 721, - "D2bnetHelp48": 722, - "D2bnetHelp49": 723, - "D2bnetHelp12": 724, - "D2bnetHelp12c": 725, - "D2bnetHelp12b": 726, - "D2bnetHelp12a": 727, - "D2bnetHelp13": 728, - "D2bnetHelp13b": 729, - "D2bnetHelp13a": 730, - "D2bnetHelp14": 731, - "D2bnetHelp14a": 732, - "D2bnetHelp15": 733, - "D2bnetHelp15b": 734, - "D2bnetHelp15a": 735, - "D2bnetHelp16": 736, - "D2bnetHelp16b": 737, - "D2bnetHelp16a": 738, - "D2bnetHelp17": 739, - "D2bnetHelp17a": 740, - "D2bnetHelp18": 741, - "D2bnetHelp18a": 742, - "D2bnetHelp19": 743, - "D2bnetHelp19a": 744, - "D2bnetHelp20": 745, - "D2bnetHelp20a": 746, - "D2bnetHelp21": 747, - "D2bnetHelp21a": 748, - "D2bnetHelp22": 749, - "D2bnetHelp22a": 750, - "D2bnetHelp23": 751, - "D2bnetHelp23a": 752, - "D2bnetHelp24": 753, - "D2bnetHelp24a": 754, - "D2bnetHelp25": 755, - "D2bnetHelp25a": 756, - "D2bnetHelp26": 757, - "D2bnetHelp26b": 758, - "D2bnetHelp26a": 759, - "D2bnetHelp27": 760, - "D2bnetHelp27a": 761, - "D2bnetHelp28": 762, - "D2bnetHelp28a": 763, - "D2bnetHelp29": 764, - "D2bnetHelp29a": 765, - "D2bnetHelp30": 766, - "D2bnetHelp30a": 767, - "D2bnetHelp31": 768, - "D2bnetHelp31a": 769, - "D2bnetHelp32": 770, - "D2bnetHelp32a": 771, - "D2bnetHelp33": 772, - "D2bnetHelp34": 773, - "D2bnetHelp35": 774, - "D2bnetHelp51": 775, - "D2bnetHelp52": 776, - "D2bnetHelp53": 777, - "D2bnetHelp54": 778, - "D2bnetHelp55": 779, - "D2bnetHelp56": 780, - "D2bnetHelp57": 781, - "D2bnetHelp58": 782, - "D2bnetHelp59": 783, - "D2bnetHelp60": 784, - "D2bnetHelp61": 785, - "D2bnetHelp62": 786, - "D2bnetHelp63": 787, - "Moo Moo Farm": 788, - "Chaos Sanctum": 789, - "The Pandemonium Fortress": 790, - "River of Flame": 791, - "Outer Steppes": 792, - "Plains of Despair": 793, - "City of the Damned": 794, - "Durance of Hate Level 3": 795, - "Durance of Hate Level 2": 796, - "Durance of Hate Level 1": 797, - "Disused Reliquary": 798, - "Ruined Fane": 799, - "Forgotten Temple": 800, - "Forgotten Reliquary": 801, - "Disused Fane": 802, - "Ruined Temple": 803, - "Flayer Dungeon Level 3": 804, - "Flayer Dungeon Level 2": 805, - "Flayer Dungeon Level 1": 806, - "Swampy Pit Level 3": 807, - "Swampy Pit Level 2": 808, - "Swampy Pit Level 1": 809, - "Spider Cave": 810, - "Spider Cavern": 811, - "Travincal": 812, - "Kurast Causeway": 813, - "Upper Kurast": 814, - "Kurast Bazaar": 815, - "Lower Kurast": 816, - "Flayer Jungle": 817, - "Great Marsh": 818, - "Spider Forest": 819, - "Kurast Docktown": 820, - "Durance of Hate": 821, - "Flayer Dungeon": 822, - "Swampy Pit": 823, - "Arcane Sanctuary": 824, - "Duriel's Lair": 825, - "Tal Rasha's Tomb": 826, - "Ancient Tunnels": 827, - "Maggot Lair Level 3": 828, - "Maggot Lair Level 2": 829, - "Maggot Lair Level 1": 830, - "Claw Viper Temple Level 2": 831, - "Halls of the Dead Level 3": 832, - "Stony Tomb Level 2": 833, - "Claw Viper Temple Level 1": 834, - "Halls of the Dead Level 2": 835, - "Halls of the Dead Level 1": 836, - "Stony Tomb Level 1": 837, - "Palace Cellar Level 3": 838, - "Palace Cellar Level 2": 839, - "Palace Cellar Level 1 \tPalace Cellar Level 1": 840, - "Harem Level 2": 841, - "Harem Level 1": 842, - "Sewers Level 3": 843, - "Sewers Level 2": 844, - "Sewers Level 1": 845, - "Canyon of the Magi": 846, - "Valley of Snakes": 847, - "Lost City": 848, - "Far Oasis": 849, - "Dry Hills": 850, - "Rocky Waste": 851, - "Lut Gholein": 852, - "Maggot Lair": 853, - "Claw Viper Temple": 854, - "Halls of the Dead": 855, - "Stony Tomb": 856, - "Palace Cellar": 857, - "Harem": 858, - "Sewers": 859, - "To The Moo Moo Farm": 860, - "To Chaos Sanctum": 861, - "To The River of Flame": 862, - "To The Outer Steppes": 863, - "To The Plains of Despair": 864, - "To The City of the Damned": 865, - "To The Pandemonium Fortress": 866, - "To The Durance of Hate Level 3": 867, - "To The Durance of Hate Level 2": 868, - "To The Durance of Hate Level 1": 869, - "To The Disused Reliquary": 870, - "To The Ruined Fane": 871, - "To The Forgotten Temple": 872, - "To The Forgotten Reliquary": 873, - "To The Disused Fane": 874, - "To The Ruined Temple": 875, - "To The Flayer Dungeon Level 1": 876, - "To The Flayer Dungeon Level 2": 877, - "To The Flayer Dungeon Level 3": 878, - "To The Swampy Pit Level 3": 879, - "To The Swampy Pit Level 2": 880, - "To The Swampy Pit Level 1": 881, - "To The Spider Cave": 882, - "To The Spider Cavern": 883, - "To Travincal": 884, - "To The Kurast Causeway": 885, - "To Upper Kurast": 886, - "To The Kurast Bazaar": 887, - "To Lower Kurast": 888, - "To The Flayer Jungle": 889, - "To The Great Marsh": 890, - "To The Spider Forest": 891, - "To The Kurast Docktown": 892, - "To The Arcane Sanctuary": 893, - "To Duriel's Lair": 894, - "To Tal Rasha's Tomb": 895, - "To The Ancient Tunnels": 896, - "To The Maggot Lair Level 3": 897, - "To The Maggot Lair Level 2": 898, - "To The Maggot Lair Level 1": 899, - "To The Claw Viper Temple Level 2": 900, - "To The Halls of the Dead Level 3": 901, - "To The Stony Tomb Level 2": 902, - "To The Claw Viper Temple Level 1": 903, - "To The Halls of the Dead Level 2": 904, - "To The Halls of the Dead Level 1": 905, - "To The Stony Tomb Level 1": 906, - "To The Palace Cellar Level 3": 907, - "To The Palace Cellar Level 2": 908, - "To The Palace Cellar Level 1 \tTo The Palace Cellar Level 1 ": 909, - "To The Harem Level 2": 910, - "To The Harem Level 1": 911, - "To The Sewers Level 3": 912, - "To The Sewers Level 2": 913, - "To The Sewers Level 1": 914, - "To The Canyon of the Magi": 915, - "To The Valley of Snakes": 916, - "To The Lost City": 917, - "To The Far Oasis": 918, - "To The Dry Hills": 919, - "To The Rocky Waste": 920, - "To Lut Gholein": 921, - "qstsa2q0": 922, - "qstsa2q1": 923, - "qstsa2q2": 924, - "qstsa2q3": 925, - "qstsa2q4": 926, - "qstsa2q5": 927, - "qstsa2q6": 928, - "qstsa3q0": 929, - "qstsa3q1": 930, - "qstsa3q2": 931, - "qstsa3q3": 932, - "qstsa3q4": 933, - "qstsa3q5": 934, - "qstsa3q6": 935, - "qstsa4q0": 936, - "qstsa4q1": 937, - "qstsa4q2": 938, - "qstsa4q3": 939, - "qstsa2q01": 940, - "qstsa2q11": 941, - "qstsa2q12": 942, - "qstsa2q13": 943, - "qstsa2q21": 944, - "qstsa2q22": 945, - "qstsa2q23": 946, - "qstsa2q24": 947, - "qstsa2q25": 948, - "qstsa2q31": 949, - "qstsa2q31a": 950, - "qstsa2q32": 951, - "qstsa2q33": 952, - "qstsa2q41": 953, - "qstsa2q41a": 954, - "qstsa2q42": 955, - "qstsa2q43": 956, - "qstsa2q51": 957, - "qstsa2q52": 958, - "qstsa2q53": 959, - "qstsa2q61": 960, - "qstsa2q61a": 961, - "qstsa2q62": 962, - "qstsa2q63": 963, - "qstsa2q63a": 964, - "qstsa2q64": 965, - "qstsa2q65": 966, - "qstsa3q01": 967, - "qstsa3q11": 968, - "qstsa3q12": 969, - "qstsa3q21": 970, - "qstsa3q22": 971, - "qstsa3q23": 972, - "qstsa3q24": 973, - "qstsa3q25": 974, - "qstsa3q26": 975, - "qstsa3q21a": 976, - "qstsa3q31": 977, - "qstsa3q32": 978, - "qstsa3q33": 979, - "qstsa3q34": 980, - "qstsa3q35": 981, - "qstsa3q41": 982, - "qstsa3q42": 983, - "qstsa3q43": 984, - "qstsa3q44": 985, - "qstsa3q45": 986, - "qstsa3q51": 987, - "qstsa3q52": 988, - "qstsa3q53": 989, - "qstsa3q61": 990, - "qstsa3q62": 991, - "qstsa3q63": 992, - "qstsa3q31a": 993, - "qstsa3q51a": 994, - "qstsa3q61a": 995, - "qstsa4q11": 996, - "qstsa4q12": 997, - "qstsa4q13a": 998, - "qstsa4q13": 999, - "qstsa4q31": 1000, - "qstsa4q32": 1001, - "qstsa4q33": 1002, - "qstsa4q34": 1003, - "qstsa4q21": 1004, - "qstsa4q22": 1005, - "qstsa4q23": 1006, - "qstsa4q24": 1007, - "asheara": 1008, - "hratli": 1009, - "alkor": 1010, - "ormus": 1011, - "nikita": 1012, - "tyrael": 1013, - "Izual": 1014, - "izual": 1015, - "Jamella": 1016, - "halbu": 1017, - "Malachai": 1018, - "merca201": 1019, - "merca202": 1020, - "merca203": 1021, - "merca204": 1022, - "merca205": 1023, - "merca206": 1024, - "merca207": 1025, - "merca208": 1026, - "merca209": 1027, - "merca210": 1028, - "merca211": 1029, - "merca212": 1030, - "merca213": 1031, - "merca214": 1032, - "merca215": 1033, - "merca216": 1034, - "merca217": 1035, - "merca218": 1036, - "merca219": 1037, - "merca220": 1038, - "merca221": 1039, - "merca222": 1040, - "merca223": 1041, - "merca224": 1042, - "merca225": 1043, - "merca226": 1044, - "merca227": 1045, - "merca228": 1046, - "merca229": 1047, - "merca230": 1048, - "merca231": 1049, - "merca232": 1050, - "merca233": 1051, - "merca234": 1052, - "merca235": 1053, - "merca236": 1054, - "merca237": 1055, - "merca238": 1056, - "merca239": 1057, - "merca240": 1058, - "merca241": 1059, - "qf1": 1060, - "qf2": 1061, - "KhalimFlail": 1062, - "SuperKhalimFlail": 1063, - "qey": 1064, - "qbr": 1065, - "qhr": 1066, - "The Feature Creep": 1067, - "Hell Bovine": 1068, - "Playersubtitles00": 1069, - "Playersubtitles01": 1070, - "Playersubtitles02": 1071, - "Playersubtitles03": 1072, - "Playersubtitles04": 1073, - "Playersubtitles05": 1074, - "Playersubtitles06": 1075, - "Playersubtitles07": 1076, - "Playersubtitles09": 1077, - "Playersubtitles10": 1078, - "Playersubtitles11": 1079, - "Playersubtitles12": 1080, - "Playersubtitles13": 1081, - "Playersubtitles14": 1082, - "Playersubtitles15": 1083, - "Playersubtitles16": 1084, - "Playersubtitles17": 1085, - "Playersubtitles18": 1086, - "Playersubtitles21": 1087, - "Playersubtitles22": 1088, - "Playersubtitles23": 1089, - "Playersubtitles24": 1090, - "Playersubtitles25": 1091, - "Playersubtitles26": 1092, - "Playersubtitles27": 1093, - "Playersubtitles28": 1094, - "LeaveCampAma": 1095, - "LeaveCampBar": 1096, - "LeaveCampPal": 1097, - "LeaveCampSor": 1098, - "LeaveCampNec": 1099, - "EnterDOEAma": 1100, - "EnterDOEBar": 1101, - "EnterDOEPal": 1102, - "EnterDOESor": 1103, - "EnterDOENec": 1104, - "EnterBurialAma": 1105, - "EnterBurialBar": 1106, - "EnterBurialPal": 1107, - "EnterBurialSor": 1108, - "EnterBurialNec": 1109, - "EnterMonasteryAma": 1110, - "EnterMonasteryBar": 1111, - "EnterMonasteryPal": 1112, - "EnterMonasterySor": 1113, - "EnterMonasteryNec": 1114, - "EnterForgottenTAma": 1115, - "EnterForgottenTBar": 1116, - "EnterForgottenTPal": 1117, - "EnterForgottenTSor": 1118, - "EnterForgottenTNec": 1119, - "EnterJailAma": 1120, - "EnterJailBar": 1121, - "EnterJailPal": 1122, - "EnterJailSor": 1123, - "EnterJailNec": 1124, - "Barracksremoved": 1129, - "EnterCatacombsAma": 1130, - "EnterCatacombsBar": 1131, - "EnterCatacombsPal": 1132, - "EnterCatacombsSor": 1133, - "EnterCatacombsNec": 1134, - "CompletingDOEAma": 1135, - "CompletingDOEBar": 1136, - "CompletingDOEPal": 1137, - "CompletingDOESor": 1138, - "CompletingDOENec": 1139, - "CompletingBurialAma": 1140, - "CompletingBurialBar": 1141, - "CompletingBurialPal": 1142, - "CompletingBurialSor": 1143, - "CompletingBurialNec": 1144, - "FindingInifusAma": 1145, - "FindingInifusBar": 1146, - "FindingInifusPal": 1147, - "FindingInifusSor": 1148, - "FindingInifusNec": 1149, - "FindingCairnAma": 1150, - "FindingCairnBar": 1151, - "FindingCairnPal": 1152, - "FindingCairnSor": 1153, - "FindingCairnNec": 1154, - "FindingTristramAma": 1155, - "FindingTristramBar": 1156, - "FindingTristramPal": 1157, - "FindingTristramSor": 1158, - "FindingTristramNec": 1159, - "RescueCainAma": 1160, - "RescueCainBar": 1161, - "RescueCainPal": 1162, - "RescueCainSor": 1163, - "RescueCainNec": 1164, - "HoradricMalusAma": 1165, - "HoradricMalusBar": 1166, - "HoradricMalusPal": 1167, - "HoradricMalusSor": 1168, - "HoradricMalusNec": 1169, - "CompletingForgottenTAma": 1170, - "CompletingForgottenTBar": 1171, - "CompletingForgottenTPal": 21924, - "CompletingForgottenTSor": 1173, - "CompletingForgottenTNec": 1174, - "CompletingAndarielAma": 1175, - "CompletingAndarielBar": 1176, - "CompletingAndarielPal": 1177, - "CompletingAndarielSor": 1178, - "CompletingAndarielNec": 1179, - "EnteringRadamentAma": 1180, - "EnteringRadamentBar": 1181, - "EnteringRadamentPal": 1182, - "EnteringRadamentSor": 1183, - "EnteringRadamentNec": 1184, - "CompletingRadamentAma": 1185, - "CompletingRadamentBar": 1186, - "CompletingRadamentPal": 1187, - "CompletingRadamentSor": 1188, - "CompletingRadamentNec": 1189, - "BeginTaintedSunAma": 1190, - "BeginTaintedSunBar": 1191, - "BeginTaintedSunPal": 1192, - "BeginTaintedSunSor": 1193, - "BeginTaintedSunNec": 1194, - "EnteringClawViperAma": 1195, - "EnteringClawViperBar": 1196, - "EnteringClawViperPal": 1197, - "EnteringClawViperSor": 1198, - "EnteringClawViperNec": 1199, - "CompletingTaintedSunAma": 1200, - "CompletingTaintedSunBar": 1201, - "CompletingTaintedSunPal": 1202, - "CompletingTaintedSunSor": 1203, - "CompletingTaintedSunNec": 1204, - "EnteringArcaneAma": 1205, - "EnteringArcaneBar": 1206, - "EnteringArcanePal": 1207, - "EnteringArcaneSor": 1208, - "EnteringArcaneNec": 1209, - "FindingSummonerAma": 1210, - "FindingSummonerBar": 1211, - "FindingSummonerPal": 1212, - "FindingSummonerSor": 1213, - "FindingSummonerNec": 1214, - "CompletingSummonerAma": 1215, - "CompletingSummonerBar": 1216, - "CompletingSummonerPal": 1217, - "CompletingSummonerSor": 1218, - "CompletingSummonerNec": 1219, - "FindingdecoyTombAma": 1220, - "FindingdecoyTombBar": 1221, - "FindingdecoyTombPal": 1222, - "FindingdecoyTombSor": 1223, - "FindingdecoyTombNec": 1224, - "FindingTrueTombAma": 1225, - "FindingTrueTombBar": 1226, - "FindingTrueTombPal": 1227, - "FindingTrueTombSor": 1228, - "FindingTrueTombNec": 1229, - "CompletingTombAma": 1230, - "CompletingTombBar": 1231, - "CompletingTombPal": 1232, - "CompletingTombSor": 1233, - "CompletingTombNec": 1234, - "nodarkwanderer": 1235, - "FindingLamEsenAma": 1236, - "FindingLamEsenBar": 1237, - "FindingLamEsenPal": 1238, - "FindingLamEsenSor": 1239, - "FindingLamEsenNec": 1240, - "CompletingLamEsenAma": 1241, - "CompletingLamEsenBar": 1242, - "CompletingLamEsenPal": 1243, - "CompletingLamEsenSor": 1244, - "CompletingLamEsenNec": 1245, - "FindingBeneathCityAma": 1246, - "FindingBeneathCityBar": 1247, - "FindingBeneathCityPal": 1248, - "FindingBeneathCitySor": 1249, - "FindingBeneathCityNec": 1250, - "FindingDrainLeverAma": 1251, - "FindingDrainLeverBar": 1252, - "FindingDrainLeverPal": 1253, - "FindingDrainLeverSor": 1254, - "FindingDrainLeverNec": 1255, - "CompletingBeneathCityAma": 1256, - "CompletingBeneathCityBar": 1257, - "CompletingBeneathCityPal": 1258, - "CompletingBeneathCitySor": 1259, - "CompletingBeneathCityNec": 1260, - "CompletingBladeAma": 1261, - "CompletingBladeBar": 1262, - "CompletingBladePal": 1263, - "CompletingBladeSor": 1264, - "CompletingBladeNec": 1265, - "FindingJadeFigAma": 1270, - "FindingTempleAma": 1271, - "FindingTempleBar": 1272, - "FindingTemplePal": 1273, - "FindingTempleSor": 1274, - "FindingTempleNec": 1275, - "CompletingTempleAma": 1276, - "CompletingTempleBar": 1277, - "CompletingTemplePal": 1278, - "CompletingTempleSor": 1279, - "CompletingTempleNec": 1280, - "FindingGuardianTowerAma": 1281, - "FindingGuardianTowerBar": 1282, - "FindingGuardianTowerPal": 1283, - "FindingGuardianTowerSor": 1284, - "FindingGuardianTowerNec": 1285, - "CompletingGuardianTowerAma": 1286, - "CompletingGuardianTowerBar": 1287, - "CompletingGuardianTowerPal": 1288, - "CompletingGuardianTowerSor": 1289, - "CompletingGuardianTowerNec": 1290, - "FreezingIzualAma": 21972, - "FreezingIzualBar": 1292, - "FreezingIzualPal": 1293, - "FreezingIzualSor": 1294, - "FreezingIzualNec": 1295, - "Eskillname0": 1296, - "Eskillsd0": 1297, - "Eskillld0": 1298, - "Eskillan0": 1299, - "EskillnameExp1": 1300, - "EskillsExpd1": 1301, - "EskilllExpd1": 1302, - "EskillExpan1": 1303, - "Eskillname2": 1304, - "Eskillsd2": 1305, - "Eskillld2": 1306, - "Eskillan2": 1307, - "Eskillname3": 1308, - "Eskillsd3": 1309, - "Eskillld3": 1310, - "Eskillan3": 1311, - "Eskillname4": 1312, - "Eskillsd4": 1313, - "Eskillld4": 1314, - "Eskillan4": 1315, - "Eskillname5": 1316, - "Eskillsd5": 1317, - "Eskillld5": 1318, - "Eskillan5": 1319, - "Eskillname6": 1320, - "Eskillsd6": 1321, - "Eskillld6": 1322, - "Eskillan6": 1323, - "Eskillname7": 1324, - "Eskillsd7": 1325, - "Eskillld7": 1326, - "Eskillan7": 1327, - "Eskillname8": 1328, - "Eskillsd8": 1329, - "Eskillld8": 1330, - "Eskillan8": 1331, - "Eskillname9": 1332, - "Eskillsd9": 1333, - "Eskillld9": 1334, - "Eskillan9": 1335, - "Eskillname10": 1336, - "Eskillsd10": 1337, - "Eskillld10": 1338, - "Eskillan10": 1339, - "Eskillname11": 1340, - "Eskillsd11": 1341, - "Eskillld11": 1342, - "Eskillan11": 1343, - "Eskillname12": 1344, - "Eskillsd12": 1345, - "Eskillld12": 1346, - "Eskillan12": 1347, - "Eskillname13": 1348, - "Eskillsd13": 1349, - "Eskillld13": 1350, - "Eskillan13": 1351, - "Eskillname14": 1352, - "Eskillsd14": 1353, - "Eskillld14": 1354, - "Eskillan14": 1355, - "Eskillname15": 1356, - "Eskillsd15": 1357, - "Eskillld15": 1358, - "Eskillan15": 1359, - "Eskillname16": 1360, - "Eskillsd16": 1361, - "Eskillld16": 1362, - "Eskillan16": 1363, - "Eskillname17": 1364, - "Eskillsd17": 1365, - "Eskillld17": 1366, - "Eskillan17": 1367, - "Eskillname18": 1368, - "Eskillsd18": 1369, - "Eskillld18": 1370, - "Eskillan18": 1371, - "Eskillname19": 1372, - "Eskillsd19": 1373, - "Eskillld19": 1374, - "Eskillan19": 1375, - "Eskillname20": 1376, - "Eskillsd20": 1377, - "Eskillld20": 1378, - "Eskillan20": 1379, - "Eskillname21": 1380, - "Eskillsd21": 1381, - "Eskillld21": 1382, - "Eskillan21": 1383, - "Eskillname22": 1384, - "Eskillsd22": 1385, - "Eskillld22": 1386, - "Eskillan22": 1387, - "Eskillname23": 1388, - "Eskillsd23": 1389, - "Eskillld23": 1390, - "Eskillan23": 1391, - "Eskillname24": 1392, - "Eskillsd24": 1393, - "Eskillld24": 1394, - "Eskillan24": 1395, - "Eskillname25": 1396, - "Eskillsd25": 1397, - "Eskillld25": 1398, - "Eskillan25": 1399, - "Eskillname26": 1400, - "Eskillsd26": 1401, - "Eskillld26": 1402, - "Eskillan26": 1403, - "Eskillname27": 1404, - "Eskillsd27": 1405, - "Eskillld27": 1406, - "Eskillan27": 1407, - "Eskillname28": 1408, - "Eskillsd28": 1409, - "Eskillld28": 1410, - "Eskillan28": 1411, - "Eskillname29": 1412, - "Eskillsd29": 1413, - "Eskillld29": 1414, - "Eskillan29": 1415, - "Eskillname30": 1416, - "Eskillsd30": 1417, - "Eskillld30": 1418, - "Eskillan30": 1419, - "Eskillname31": 1420, - "Eskillsd31": 1421, - "Eskillld31": 1422, - "Eskillan31": 1423, - "Eskillname32": 1424, - "Eskillsd32": 1425, - "Eskillld32": 1426, - "Eskillan32": 1427, - "Eskillname33": 1428, - "Eskillsd33": 1429, - "Eskillld33": 1430, - "Eskillan33": 1431, - "Eskillname34": 1432, - "Eskillsd34": 1433, - "Eskillld34": 1434, - "Eskillan34": 1435, - "Eskillname35": 1436, - "Eskillsd35": 1437, - "Eskillld35": 1438, - "Eskillan35": 1439, - "Eskillname36": 1440, - "Eskillsd36": 1441, - "Eskillld36": 1442, - "Eskillan36": 1443, - "Eskillname37": 1444, - "Eskillsd37": 1445, - "Eskillld37": 1446, - "Eskillan37": 1447, - "Eskillname38": 1448, - "Eskillsd38": 1449, - "Eskillld38": 1450, - "Eskillan38": 1451, - "Eskillname39": 1452, - "Eskillsd39": 1453, - "Eskillld39": 1454, - "Eskillan39": 1455, - "Eskillname40": 1456, - "Eskillsd40": 1457, - "Eskillld40": 1458, - "Eskillan40": 1459, - "Eskillname41": 1460, - "Eskillsd41": 1461, - "Eskillld41": 1462, - "Eskillan41": 1463, - "Eskillname42": 1464, - "Eskillsd42": 1465, - "Eskillld42": 1466, - "Eskillan42": 1467, - "Eskillname43": 1468, - "Eskillsd43": 1469, - "Eskillld43": 1470, - "Eskillan43": 1471, - "Eskillname44": 1472, - "Eskillsd44": 1473, - "Eskillld44": 1474, - "Eskillan44": 1475, - "Eskillname45": 1476, - "Eskillsd45": 1477, - "Eskillld45": 1478, - "Eskillan45": 1479, - "Eskillname46": 1480, - "Eskillsd46": 1481, - "Eskillld46": 1482, - "Eskillan46": 1483, - "Eskillname47": 1484, - "Eskillsd47": 1485, - "Eskillld47": 1486, - "Eskillan47": 1487, - "Eskillname48": 1488, - "Eskillsd48": 1489, - "Eskillld48": 1490, - "Eskillan48": 1491, - "Eskillname49": 1492, - "Eskillsd49": 1493, - "Eskillld49": 1494, - "Eskillan49": 1495, - "Eskillname50": 1496, - "Eskillsd50": 1497, - "Eskillld50": 1498, - "Eskillan50": 1499, - "Eskillname51": 1500, - "Eskillsd51": 1501, - "Eskillld51": 1502, - "Eskillan51": 1503, - "Eskillname52": 1504, - "Eskillsd52": 1505, - "Eskillld52": 1506, - "Eskillan52": 1507, - "Eskillname53": 1508, - "Eskillsd53": 1509, - "Eskillld53": 1510, - "Eskillan53": 1511, - "Eskillname54": 1512, - "Eskillsd54": 1513, - "Eskillld54": 1514, - "Eskillan54": 1515, - "Eskillname55": 1516, - "Eskillsd55": 1517, - "Eskillld55": 1518, - "Eskillan55": 1519, - "Eskillname56": 1520, - "Eskillsd56": 1521, - "Eskillld56": 1522, - "Eskillan56": 1523, - "Eskillname57": 1524, - "Eskillsd57": 1525, - "Eskillld57": 1526, - "Eskillan57": 1527, - "Eskillname58": 1528, - "Eskillsd58": 1529, - "Eskillld58": 1530, - "Eskillan58": 1531, - "Eskillname59": 1532, - "Eskillsd59": 1533, - "Eskillld59": 1534, - "Eskillan59": 1535, - "ESkillHawk": 22278, - "ESkillSpikes": 22279, - "ESkillStars": 22280, - "ESkillWolf": 22281, - "ESkillWolves": 22282, - "ESkillShoots": 22283, - "ESkillTimes": 22284, - "ESkillSpikes2": 22285, - "ob1": 20281, - "ob2": 20282, - "ob3": 20283, - "ob4": 20284, - "ob5": 21778, - "ne1": 20332, - "ne2": 20333, - "ne3": 20334, - "ne4": 20335, - "ne5": 20336, - "dr1": 20320, - "dr2": 20318, - "dr3": 20319, - "dr4": 20317, - "dr5": 20321, - "as1": 20285, - "as2": 20286, - "as3": 20287, - "as4": 20288, - "as5": 20289, - "as6": 20290, - "as7": 20291, - "AmaOnly": 20426, - "SorOnly": 20427, - "NecOnly": 20428, - "PalOnly": 20429, - "BarOnly": 20430, - "DruOnly": 20431, - "AssOnly": 20432, - "WeaponDescH2H": 21258, - "Seige Tower": 22352, - "RotWalker": 22353, - "ReanimatedHorde": 22354, - "ProwlingDead": 22355, - "UnholyCorpse": 22356, - "DefiledWarrior": 22357, - "Seige Beast": 1580, - "CrushBiest": 22359, - "BloodBringer": 22360, - "GoreBearer": 22361, - "DeamonSteed": 22362, - "WailingSpirit": 22363, - "LifeSeeker": 22364, - "LifeStealer": 22365, - "DeathlyVisage": 22366, - "BoundSpirit": 22367, - "BanishedSoul": 22368, - "Deathexp": 22369, - "Minionexp": 22370, - "Slayerexp": 22371, - "IceBoar": 22372, - "FireBoar": 22373, - "HellSpawn": 22374, - "IceSpawn": 22375, - "GreaterHellSpawn": 22376, - "GreaterIceSpawn": 22377, - "FanaticMinion": 22378, - "BerserkSlayer": 22379, - "ConsumedFireBoar": 22380, - "ConsumedIceBoar": 22381, - "FrenziedHellSpawn": 22382, - "FrenziedIceSpawn": 22383, - "InsaneHellSpawn": 22384, - "InsaneIceSpawn": 22385, - "Succubusexp": 22386, - "VileTemptress": 22387, - "StygianHarlot": 22388, - "BlightWing": 1611, - "BloodWitch": 1612, - "Dominus": 22391, - "VileWitch": 22392, - "StygianFury": 22393, - "MageWing": 1616, - "HellWitch": 1617, - "OverSeer": 22396, - "Lasher": 22397, - "OverLord": 22398, - "BloodBoss": 22399, - "HellWhip": 22400, - "MinionSpawner": 22401, - "MinionSlayerSpawner": 22402, - "MinionIce/fireBoarSpawner": 22403, - "Minionice/hellSpawnSpawner": 22404, - "MinionGreaterIce/hellSpawnSpawner": 22405, - "Imp1": 22406, - "Imp2": 22407, - "Imp3": 22408, - "Imp4": 22409, - "Imp5": 22410, - "CapsJoinMenu4": 1633, - "CapsJoinMenu5": 1634, - "Guild 1": 1635, - "Guild 2": 1636, - "Guild 3": 1637, - "Guild 4": 1638, - "Guild 5": 1639, - "To Guild 5": 1640, - "To Guild 4": 1641, - "To Guild 3": 1642, - "To Guild 2": 1643, - "To Guild 1": 1644, - "CapsBnet9": 1645, - "CapsBnet10": 1646, - "CapsBnet11": 1647, - "CapsBnet12": 1648, - "CapsBnet13": 1649, - "CapsBnet14": 1650, - "CapsBnet15": 1651, - "CapsGuildName": 1652, - "CapsGuildTag": 1653, - "GuildText1": 1654, - "GuildText2": 1655, - "Ladder3": 1656, - "Ladder7": 1657, - "gmGuildTitle": 1658, - "gmGuildName": 1659, - "gmGuildTag": 1660, - "gmWWW": 1661, - "gmGuildCharter": 1662, - "gmGuildCurrentGolds": 1663, - "gmGuildNextLevel": 1664, - "gmGuildMaster": 1665, - "gmOfficer": 1666, - "gmName": 1667, - "gmClass": 1668, - "gmLevel": 1669, - "gmDonate": 1670, - "gmRemove": 1671, - "gmPal": 1672, - "gmSor": 1673, - "gmAma": 1674, - "gmNec": 1675, - "gmBar": 1676, - "gmChangeSym": 1677, - "gmChangeCharter": 1678, - "gmChangeWebLink": 1679, - "Guild Portal": 1680, - "createdguildsuccess": 1681, - "createdguildfailure": 1682, - "inviteguildsuccess": 1683, - "inviteguildfailure": 1684, - "inviteguildins": 1685, - "joinedguildsuccess": 1686, - "joinedguildfailure": 1687, - "quitguildsuccess": 1688, - "quitguildfailure": 1689, - "guildentererror": 1690, - "strGuildMasterKicked": 1691, - "strGuildPerk1": 1692, - "strGuildPerk2": 1693, - "strGuildPerk3": 1694, - "strGuildPerk4": 1695, - "strGuildPerk5": 1696, - "strGuildPerk6": 1697, - "strGuildGoldDonated": 1698, - "strGuildDonateGold": 1699, - "gmGuildCurrentGoldPopup": 1700, - "gmGuildNextLevelPopup": 1701, - "gmGuildDonateGoldPopup": 1702, - "Message Board": 1703, - "Trophy Case": 1704, - "Guild Vault": 1705, - "Steeg Stone": 1706, - "guildaccepticon": 1707, - "guildmsgtext": 1708, - "ScrollFormat": 1709, - "BookFormat": 1710, - "HiqualityFormat": 1711, - "LowqualityFormat": 1712, - "HerbFormat": 1713, - "MagicFormat": 1714, - "GemmedNormalName": 1715, - "BodyPartsFormat": 1716, - "PlayerBodyPartFormat": 1717, - "RareFormat": 1718, - "SetItemFormat": 1719, - "ChampionFormat": 1720, - "Monster1Format": 1721, - "Monster2Format": 1722, - "Low Quality": 1723, - "Damaged": 1724, - "Cracked": 1725, - "Crude": 20910, - "Hiquality": 1727, - "Gemmed": 1728, - "Resiliant": 1729, - "Sturdy": 1730, - "Strong": 1731, - "Glorious": 1732, - "Blessed": 1733, - "Saintly": 1734, - "Holy": 1735, - "Devious": 1736, - "Fortified": 1737, - "Urgent": 1738, - "Fleet": 1739, - "Muscular": 1740, - "Jagged": 1741, - "Deadly": 1742, - "Vicious": 1743, - "Brutal": 1744, - "Massive": 1745, - "Savage": 1746, - "Merciless": 1747, - "Vulpine": 1748, - "Swift": 1749, - "Artful": 1750, - "Skillful": 1751, - "Adroit": 1752, - "Tireless": 1753, - "Rugged": 1754, - "Bronze": 1755, - "Iron": 1756, - "Steel": 1757, - "Silver": 1758, - "Gold": 1759, - "Platinum": 1760, - "Meteoric": 1761, - "Sharp": 1762, - "Fine": 1763, - "Warrior's": 1764, - "Soldier's": 1765, - "Knight's": 1766, - "Lord's": 1767, - "King's": 1768, - "Howling": 1769, - "Fortuitous": 1770, - "Brilliant": 1771, - "Omniscient": 1772, - "Sage": 1773, - "Shrewd": 1774, - "Vivid": 1775, - "Glimmering": 1776, - "Glowing": 1777, - "Bright": 1778, - "Solar": 1779, - "Lizard's": 1780, - "Forceful": 1781, - "Snake's": 1782, - "Serpent's": 1783, - "Drake's": 1784, - "Dragon's": 1785, - "Wyrm's": 1786, - "Dazzling": 1787, - "Facinating": 1788, - "Prismatic": 1789, - "Azure": 1790, - "Lapis": 1791, - "Cobalt": 1792, - "Indigo": 1793, - "Sapphire": 1794, - "Cerulean": 1795, - "Red": 1796, - "Crimson": 1797, - "Burgundy": 1798, - "Garnet": 1799, - "Russet": 1800, - "Ruby": 1801, - "Vermilion": 1802, - "Orange": 1803, - "Ocher": 1804, - "Tangerine": 1805, - "Coral": 1806, - "Crackling": 1807, - "Amber": 1808, - "Forked": 1809, - "Green": 20905, - "Beryl": 1811, - "Jade": 1812, - "Viridian": 1813, - "Vital": 1814, - "Emerald": 1815, - "Enduring": 1816, - "Fletcher's": 1817, - "Archer's": 1818, - "Monk's": 1819, - "Priest's": 1820, - "Summoner's": 1821, - "Necromancer's": 1822, - "Angel's": 1823, - "Arch-Angel's": 1824, - "Slayer's": 1825, - "Berserker's": 2507, - "Kicking": 1827, - "Triumphant": 1828, - "Mighty": 1829, - "Energizing": 1830, - "Strengthening": 1831, - "Empowering": 1832, - "Brisk": 1833, - "Tough": 1834, - "Hardy": 1835, - "Robust": 1836, - "of Health": 1837, - "of Protection": 1838, - "of Absorption": 1839, - "of Warding": 1840, - "of the Sentinel": 1841, - "of Guarding": 1842, - "of Negation": 1843, - "of Piercing": 1844, - "of Bashing": 1845, - "of Puncturing": 1846, - "of Thorns": 1847, - "of Spikes": 1848, - "of Readiness": 1849, - "of Alacrity": 1850, - "of Swiftness": 1851, - "of Quickness": 1852, - "of Blocking": 1853, - "of Deflecting": 1854, - "of the Apprentice": 1855, - "of the Magus": 1856, - "of Frost": 1857, - "of the Glacier": 1858, - "of Warmth": 1859, - "of Flame": 1860, - "of Fire": 1861, - "of Burning": 1862, - "of Shock": 1863, - "of Lightning": 1864, - "of Thunder": 1865, - "of Craftsmanship": 1866, - "of Quality": 1867, - "of Maiming": 1868, - "of Slaying": 1869, - "of Gore": 1870, - "of Carnage": 1871, - "of Slaughter": 1872, - "of Worth": 1873, - "of Measure": 1874, - "of Excellence": 1875, - "of Performance": 1876, - "of Blight": 1877, - "of Venom": 1878, - "of Pestilence": 1879, - "of Dexterity": 1880, - "of Skill": 1881, - "of Accuracy": 1882, - "of Precision": 1883, - "of Perfection": 1884, - "of Balance": 1885, - "of Stability": 1886, - "of the Horse": 1887, - "of Regeneration": 1888, - "of Regrowth": 1889, - "of Vileness": 1890, - "of Greed": 1891, - "of Wealth": 1892, - "of Chance": 1893, - "of Fortune": 1894, - "of Energy": 1895, - "of the Mind": 1896, - "of Brilliance": 1897, - "of Sorcery": 1898, - "of Wizardry": 1899, - "of the Bear": 1900, - "of Light": 1901, - "of Radiance": 1902, - "of the Sun": 1903, - "of Life": 1904, - "of the Jackal": 1905, - "of the Fox": 1906, - "of the Wolf": 1907, - "of the Tiger": 1908, - "of the Mammoth": 1909, - "of the Colosuss": 1910, - "of the Leech": 1911, - "of the Locust": 1912, - "of the Bat": 1913, - "of the Vampire": 1914, - "of Defiance": 1915, - "of Remedy": 1916, - "of Amelioration": 1917, - "of Ice": 1918, - "of Simplicity": 1919, - "of Ease": 1920, - "of the Mule": 1921, - "of Strength": 1922, - "of Might": 1923, - "of the Ox": 1924, - "of the Giant": 1925, - "of the Titan": 1926, - "of Pacing": 1927, - "of Haste": 1928, - "of Speed": 1929, - "cap": 1930, - "skp": 1931, - "hlm": 1932, - "fhl": 1933, - "ghm": 1934, - "crn": 1935, - "msk": 1936, - "qui": 1937, - "lea": 1938, - "hla": 1939, - "stu": 1940, - "rng": 1941, - "scl": 1942, - "chn": 1943, - "brs": 1944, - "spl": 1945, - "plt": 1946, - "fld": 1947, - "gth": 1948, - "ful": 1949, - "aar": 1950, - "ltp": 1951, - "buc": 1952, - "sml": 1953, - "lrg": 1954, - "kit": 1955, - "tow": 1956, - "gts": 1957, - "lgl": 1958, - "vgl": 1959, - "mgl": 1960, - "tgl": 1961, - "hgl": 1962, - "lbt": 1963, - "vbt": 1964, - "mbt": 1965, - "tbt": 1966, - "hbt": 1967, - "lbl": 1968, - "vbl": 1969, - "mbl": 1970, - "tbl": 1971, - "hbl": 1972, - "bhm": 1973, - "bsh": 1974, - "spk": 1975, - "hax": 1976, - "axe": 1977, - "2ax": 1978, - "mpi": 1979, - "wax": 1980, - "lax": 1981, - "bax": 1982, - "btx": 1983, - "gax": 1984, - "gix": 1985, - "wnd": 1986, - "ywn": 1987, - "bwn": 1988, - "gwn": 1989, - "clb": 1990, - "scp": 1991, - "gsc": 1992, - "wsp": 1993, - "spc": 1994, - "mac": 1995, - "mst": 1996, - "fla": 1997, - "whm": 1998, - "mau": 1999, - "gma": 2000, - "ssd": 2001, - "scm": 2002, - "sbr": 2003, - "flc": 2004, - "crs": 2005, - "bsd": 2006, - "lsd": 2007, - "wsd": 2008, - "2hs": 2009, - "clm": 2010, - "gis": 2011, - "bsw": 2012, - "flb": 2013, - "gsd": 2014, - "dgr": 2015, - "dir": 2016, - "kri": 2017, - "bld": 2018, - "tkf": 2019, - "tax": 2020, - "bkf": 2021, - "bal": 2022, - "jav": 2023, - "pil": 2024, - "ssp": 2025, - "glv": 2026, - "tsp": 2027, - "spr": 2028, - "tri": 2029, - "brn": 2030, - "spt": 2031, - "pik": 2032, - "bar": 2033, - "vou": 2034, - "scy": 2035, - "pax": 2036, - "hal": 2037, - "wsc": 2038, - "sst": 2039, - "lst": 2040, - "cst": 2041, - "bst": 2042, - "wst": 2043, - "sbw": 2044, - "hbw": 2045, - "lbw": 2046, - "cbw": 2047, - "sbb": 2048, - "lbb": 2049, - "swb": 2050, - "lwb": 2051, - "lxb": 2052, - "mxb": 2053, - "hxb": 2054, - "rxb": 2055, - "xpk": 2056, - "xsh": 2057, - "xh9": 2058, - "zhb": 2059, - "ztb": 2060, - "zmb": 2061, - "zvb": 2062, - "zlb": 2063, - "xhb": 2064, - "xtb": 2065, - "xmb": 2066, - "xvb": 2067, - "xlb": 2068, - "xhg": 2069, - "xtg": 2070, - "xmg": 2071, - "xvg": 2072, - "xlg": 2073, - "xts": 2074, - "xow": 2075, - "xit": 2076, - "xrg": 2077, - "xml": 2078, - "xuc": 2079, - "xtp": 2080, - "xar": 2081, - "xul": 2082, - "xth": 2083, - "xld": 2084, - "xlt": 2085, - "xpl": 2086, - "xrs": 2087, - "xhn": 2088, - "xcl": 2089, - "xng": 2090, - "xtu": 2091, - "xla": 2092, - "xea": 2093, - "xui": 2094, - "xsk": 2095, - "xrn": 2096, - "xhm": 2097, - "xhl": 2098, - "xlm": 2099, - "xkp": 2100, - "xap": 2101, - "8rx": 2102, - "8hx": 2103, - "8mx": 2104, - "8lx": 2105, - "8lw": 2106, - "8sw": 2107, - "8l8": 2108, - "8s8": 2109, - "8cb": 2110, - "8lb": 2111, - "8hb": 2112, - "8sb": 2113, - "8ws": 2114, - "8bs": 2115, - "8cs": 2116, - "8ls": 2117, - "8ss": 2118, - "9wc": 2119, - "9h9": 2120, - "9pa": 2121, - "9s8": 2122, - "9vo": 2123, - "9b7": 2124, - "9p9": 2125, - "9st": 2126, - "9br": 2127, - "9tr": 2128, - "9sr": 2129, - "9ts": 2130, - "9gl": 2131, - "9s9": 2132, - "9pi": 2133, - "9ja": 2134, - "9b8": 2135, - "9bk": 2136, - "9ta": 2137, - "9tk": 2138, - "9bl": 2139, - "9kr": 2140, - "9di": 2141, - "9dg": 2142, - "9gd": 2143, - "9fb": 2144, - "9gs": 2145, - "9cm": 2146, - "92h": 2147, - "9wd": 2148, - "9ls": 2149, - "9bs": 2150, - "9cr": 2151, - "9fc": 2152, - "9sb": 2153, - "9sm": 2154, - "9ss": 2155, - "9gm": 2156, - "9m9": 2157, - "9wh": 2158, - "9fl": 2159, - "9mt": 2160, - "9ma": 2161, - "9sp": 2162, - "9ws": 2163, - "9qs": 2164, - "9sc": 2165, - "9cl": 2166, - "9gw": 2167, - "9bw": 2168, - "9yw": 2169, - "9wn": 2170, - "9gi": 2171, - "9ga": 2172, - "9bt": 2173, - "9ba": 2174, - "9la": 2175, - "9wa": 2176, - "9mp": 2177, - "92a": 2178, - "9ax": 2179, - "9ha": 2180, - "9b9": 2181, - "gpl": 2182, - "opl": 2183, - "gpm": 2184, - "opm": 2185, - "gps": 2186, - "ops": 2187, - "gidbinn": 2188, - "g33": 2189, - "d33": 2190, - "leg": 2191, - "Malus": 2192, - "hdm": 2193, - "hfh": 2194, - "hst": 2195, - "msf": 2196, - "orifice": 2197, - "elx": 2198, - "tbk": 2199, - "tsc": 2200, - "ibk": 2201, - "isc": 2202, - "RightClicktoUse": 2203, - "RightClicktoOpen": 2204, - "RightClicktoRead": 2205, - "InsertScrolls": 2206, - "vps": 2207, - "yps": 2208, - "rvs": 2209, - "rvl": 2210, - "wms": 2211, - "amu": 2212, - "vip": 2213, - "rin": 2214, - "gld": 2215, - "bks": 2216, - "bkd": 2217, - "aqv": 2218, - "tch": 2219, - "cqv": 2220, - "Key": 2221, - "key": 2222, - "luv": 2223, - "xyz": 2224, - "shrine": 2225, - "teleport pad": 2226, - "j34": 2227, - "g34": 2228, - "bbb": 2229, - "LamTome": 2230, - "box": 2231, - "tr1": 2232, - "mss": 2233, - "ass": 2234, - "ear": 2235, - "gcv": 2236, - "gfv": 2237, - "gsv": 2238, - "gzv": 2239, - "gpv": 2240, - "gcy": 2241, - "gfy": 2242, - "gsy": 2243, - "gly": 2244, - "gpy": 2245, - "gcb": 2246, - "gfb": 2247, - "gsb": 2248, - "glb": 2249, - "gpb": 2250, - "gcg": 2251, - "gfg": 2252, - "glg": 2253, - "gsg": 2254, - "gpg": 2255, - "gcr": 2256, - "gfr": 2257, - "gsr": 2258, - "glr": 2259, - "gpr": 2260, - "gcw": 2261, - "gfw": 2262, - "gsw": 2263, - "glw": 2264, - "gpw": 2265, - "hp1": 2266, - "hp2": 2267, - "hp3": 2268, - "hp4": 2269, - "hp5": 2270, - "mp1": 2271, - "mp2": 2272, - "mp3": 2273, - "mp4": 2274, - "mp5": 2275, - "hrb": 20434, - "skc": 2277, - "skf": 2278, - "sku": 2279, - "skl": 2280, - "skz": 2281, - "Beast": 2282, - "Eagle": 2283, - "Raven": 2284, - "Viper": 2285, - "GhoulRI": 2286, - "Skull": 2287, - "Blood": 2288, - "Dread": 2289, - "Doom": 2290, - "Grim": 2291, - "Bone": 2292, - "Death": 2293, - "Shadow": 2294, - "Storm": 2295, - "Rune": 2296, - "PlagueRI": 2297, - "Stone": 2298, - "Wraith": 2989, - "Spirit": 2300, - "Demon": 2301, - "Cruel": 2302, - "Empyrion": 2303, - "Bramble": 2304, - "Pain": 2305, - "Loath": 2306, - "Glyph": 2307, - "Imp": 2308, - "Fiend": 2309, - "Hailstone": 2310, - "Gale": 2311, - "Dire": 2312, - "Soul": 2313, - "Brimstone": 2314, - "Corpse": 2315, - "Carrion": 2316, - "Holocaust": 2317, - "Havoc": 2318, - "Bitter": 2319, - "Entropy": 2320, - "Chaos": 2321, - "Order": 2322, - "Rift": 2323, - "Corruption": 2324, - "bite": 2325, - "scratch": 2326, - "scalpel": 2327, - "fang": 2328, - "gutter": 2329, - "thirst": 2330, - "razor": 2331, - "scythe": 2332, - "edge": 2333, - "saw": 2334, - "splitter": 2335, - "cleaver": 2336, - "sever": 2337, - "sunder": 2338, - "rend": 2339, - "mangler": 2340, - "slayer": 2341, - "reaver": 2342, - "Spawn": 2343, - "gnash": 2344, - "star": 2345, - "blow": 2346, - "smasher": 2347, - "Bane": 2348, - "crusher": 2349, - "breaker": 2350, - "grinder": 2351, - "crack": 2352, - "mallet": 2353, - "knell": 2354, - "lance": 2355, - "spike": 2356, - "impaler": 2357, - "skewer": 2358, - "prod": 2359, - "scourge": 2360, - "wand": 2361, - "wrack": 2362, - "barb": 2363, - "needle": 2364, - "dart": 2365, - "bolt": 2366, - "quarrel": 2367, - "fletch": 2368, - "flight": 2369, - "nock": 2370, - "horn": 2371, - "stinger": 2372, - "quill": 2373, - "goad": 2374, - "branch": 2375, - "spire": 2376, - "song": 2377, - "call": 2378, - "cry": 2379, - "spell": 2380, - "chant": 2381, - "weaver": 2382, - "gnarl": 2383, - "visage": 2384, - "crest": 2385, - "circlet": 2386, - "veil": 2387, - "hood": 2388, - "mask": 2389, - "brow": 2390, - "casque": 2391, - "visor": 2392, - "cowl": 2393, - "hide": 2394, - "Pelt": 2395, - "carapace": 2396, - "coat": 2397, - "wrap": 2398, - "suit": 2399, - "cloak": 2400, - "shroud": 2401, - "jack": 2402, - "mantle": 2403, - "guard": 2404, - "badge": 2405, - "rock": 2406, - "aegis": 2407, - "ward": 2408, - "tower": 2409, - "shield": 2410, - "wing": 2411, - "mark": 2412, - "emblem": 2413, - "hand": 2414, - "fist": 2415, - "claw": 2416, - "clutches": 2417, - "grip": 2418, - "grasp": 2419, - "hold": 2420, - "touch": 2421, - "finger": 2422, - "knuckle": 2423, - "shank": 2424, - "spur": 2425, - "tread": 2426, - "stalker": 2427, - "greave": 2428, - "blazer": 2429, - "nails": 2430, - "trample": 2431, - "Brogues": 2432, - "track": 2433, - "slippers": 2434, - "clasp": 2435, - "buckle": 2436, - "harness": 2437, - "lock": 2438, - "fringe": 2439, - "winding": 2440, - "chain": 2441, - "strap": 2442, - "lash": 2443, - "cord": 2444, - "knot": 2445, - "circle": 2446, - "loop": 2447, - "eye": 2448, - "turn": 2449, - "spiral": 2450, - "coil": 2451, - "gyre": 2452, - "band": 2453, - "whorl": 2454, - "talisman": 2455, - "heart": 2456, - "noose": 2457, - "necklace": 2458, - "collar": 2459, - "beads": 2460, - "torc": 2461, - "gorget": 2462, - "scarab": 2463, - "wood": 2464, - "brand": 2465, - "bludgeon": 2466, - "cudgel": 2467, - "loom": 2468, - "harp": 2469, - "master": 2470, - "barRI": 2471, - "hew": 2472, - "crook": 2473, - "mar": 2474, - "shell": 2475, - "stake": 2476, - "picket": 2477, - "pale": 2478, - "flange": 2479, - "Civerb's Vestments": 2480, - "Hsarus' Trim": 2481, - "Cleglaw's Brace": 2482, - "Iratha's Finery": 2483, - "Isenhart's Armory": 2484, - "Vidala's Rig": 2485, - "Milabrega's Regalia": 2486, - "Cathan's Traps": 2487, - "Tancred's Battlegear": 2488, - "Sigon's Complete Steel": 2489, - "Infernal Tools": 2490, - "Berserker's Garb": 2491, - "Death's Disguise": 2492, - "Angelical Raiment": 2493, - "Arctic Gear": 2494, - "Arcanna's Tricks": 2495, - "Civerb's": 2496, - "Hsarus'\tHsaru's": 2497, - "Cleglaw's": 2498, - "Iratha's": 2499, - "Isenhart's": 2500, - "Vidala's": 2501, - "Milabrega's": 2502, - "Cathan's": 2503, - "Tancred's": 2504, - "Sigon's": 2505, - "Infernal": 2506, - "Death's": 2508, - "Angelical": 2509, - "Arctic": 2510, - "Arcanna's": 2511, - "Ward": 2512, - "Iron Heel": 2513, - "Tooth": 2514, - "Collar": 2515, - "Lightbrand": 2516, - "Barb": 2517, - "Orb": 2518, - "Rule": 2519, - "Crowbill": 2520, - "Visor": 2521, - "Cranium": 2522, - "Headgear": 2523, - "Hand": 2524, - "Sickle": 2525, - "Horn": 2526, - "Sign": 2527, - "Icon": 2528, - "Iron Fist": 2529, - "Claw": 2530, - "Cuff": 2531, - "Parry": 2532, - "Fetlock": 2533, - "Rod": 2534, - "Mesh": 2535, - "Spine": 2536, - "Shelter": 2537, - "Torch": 2538, - "Hauberk": 2539, - "Guard": 2540, - "Mantle": 2541, - "Furs": 2542, - "Deathwand": 2543, - "CudgelSI3S": 2544, - "Iron Stay": 2545, - "Pincers": 2546, - "Coil": 2547, - "Case": 2548, - "Ambush": 2549, - "Diadem": 2550, - "Visage": 2551, - "Hobnails": 2552, - "Gage": 2553, - "SignSI3S": 2554, - "Hatchet": 2555, - "Touch": 2556, - "Halo": 2557, - "Binding": 2558, - "Head": 2559, - "Horns": 2560, - "Snare": 2561, - "Robe": 2562, - "Sigil": 2563, - "Weird": 2564, - "Sabot": 2565, - "Wings": 2566, - "Mitts": 2567, - "Flesh": 2568, - "Cord": 2569, - "Seal": 2570, - "SkullSI5S": 2571, - "Wrap": 2572, - "GuardSI6S": 2573, - "The Gnasher": 2574, - "Deathspade": 2575, - "Bladebone": 2576, - "Mindrend": 2577, - "Rakescar": 2578, - "Fechmars Axe": 2579, - "Goreshovel": 2580, - "The Chieftan": 2581, - "Brainhew": 2582, - "The Humongous": 2583, - "Iros Torch": 2584, - "Maelstromwrath": 2585, - "Gravenspine": 2586, - "Umes Lament": 2587, - "Felloak": 2588, - "Knell Striker": 2589, - "Rusthandle": 2590, - "Stormeye": 2591, - "Stoutnail": 2592, - "Crushflange": 2593, - "Bloodrise": 2594, - "The Generals Tan Do Li Ga": 2595, - "Ironstone": 2596, - "Bonesob": 2597, - "Steeldriver": 2598, - "Rixots Keen": 2599, - "Blood Crescent": 2600, - "Krintizs Skewer": 2601, - "Gleamscythe": 2602, - "Azurewrath": 2603, - "Griswolds Edge": 2604, - "Hellplague": 2605, - "Culwens Point": 2606, - "Shadowfang": 2607, - "Soulflay": 2608, - "Kinemils Awl": 2609, - "Blacktongue": 2610, - "Ripsaw": 2611, - "The Patriarch": 2612, - "Gull": 2613, - "The Diggler": 2614, - "The Jade Tan Do": 2615, - "Irices Shard": 2616, - "The Dragon Chang": 2617, - "Razortine": 2618, - "Bloodthief": 2619, - "Lance of Yaggai": 2620, - "The Tannr Gorerod": 2621, - "Dimoaks Hew": 2622, - "Steelgoad": 2623, - "Soul Harvest": 2624, - "The Battlebranch": 2625, - "Woestave": 2626, - "The Grim Reaper": 2627, - "Bane Ash": 2628, - "Serpent Lord": 2629, - "Lazarus Spire": 2630, - "The Salamander": 2631, - "The Iron Jang Bong": 2632, - "Pluckeye": 2633, - "Witherstring": 2634, - "Rimeraven": 2635, - "Piercerib": 2636, - "Pullspite": 2637, - "Wizendraw": 2638, - "Hellclap": 2639, - "Blastbark": 2640, - "Leadcrow": 2641, - "Ichorsting": 2642, - "Hellcast": 2643, - "Doomspittle": 2644, - "War Bonnet": 2645, - "Tarnhelm": 2646, - "Coif of Glory": 2647, - "Duskdeep": 2648, - "Wormskull": 2649, - "Howltusk": 2650, - "Undead Crown": 2651, - "The Face of Horror": 2652, - "Greyform": 2653, - "Blinkbats Form": 2654, - "The Centurion": 2655, - "Twitchthroe": 2656, - "Darkglow": 2657, - "Hawkmail": 2658, - "Sparking Mail": 2659, - "Venomsward": 2660, - "Iceblink": 2661, - "Boneflesh": 2662, - "Rockfleece": 2663, - "Rattlecage": 2664, - "Goldskin": 2665, - "Victors Silk": 2666, - "Heavenly Garb": 2667, - "Pelta Lunata": 2668, - "Umbral Disk": 2669, - "Stormguild": 2670, - "Wall of the Eyeless": 2671, - "Swordback Hold": 2672, - "Steelclash": 2673, - "Bverrit Keep": 2674, - "The Ward": 2675, - "The Hand of Broc": 2676, - "Bloodfist": 2677, - "Chance Guards": 2678, - "Magefist": 2679, - "Frostburn": 2680, - "Hotspur": 2681, - "Gorefoot": 2682, - "Treads of Cthon": 2683, - "Goblin Toe": 2684, - "Tearhaunch": 2685, - "Lenyms Cord": 2686, - "Snakecord": 2687, - "Nightsmoke": 2688, - "Goldwrap": 2689, - "Bladebuckle": 2690, - "Nokozan Relic": 2691, - "The Eye of Etlich": 2692, - "The Mahim-Oak Curio": 2693, - "Nagelring": 2694, - "Manald Heal": 2695, - "Gorgethroat": 2696, - "Amulet of the Viper": 2697, - "Staff of Kings": 2698, - "Horadric Staff": 2699, - "Hell Forge Hammer": 2700, - "The Stone of Jordan": 2701, - "GloomUM": 2702, - "Gray": 2703, - "DireUM": 2704, - "Black": 2705, - "ShadowUM": 2706, - "Haze": 2707, - "Wind": 2708, - "StormUM": 2709, - "Warp": 2710, - "Night": 2711, - "Moon": 2712, - "Star": 2713, - "Pit": 2714, - "Fire": 2715, - "Cold": 2716, - "Seethe": 2717, - "SharpUM": 2718, - "AshUM": 2719, - "Blade": 2720, - "SteelUM": 2721, - "StoneUM": 2722, - "Rust": 2723, - "Mold": 2724, - "Blight": 2725, - "Plague": 2726, - "Rot": 2727, - "Ooze": 2728, - "Puke": 2729, - "Snot": 2730, - "Bile": 2731, - "BloodUM": 2732, - "Pulse": 2733, - "Gut": 2734, - "Gore": 2735, - "FleshUM": 2736, - "BoneUM": 2737, - "SpineUM": 2738, - "Mind": 2739, - "SpiritUM": 2740, - "SoulUM": 2741, - "Wrath": 2742, - "GriefUM": 2743, - "Foul": 2744, - "Vile": 2745, - "Sin": 2746, - "ChaosUM": 2747, - "DreadUM": 2748, - "DoomUM": 2749, - "BaneUM": 2750, - "DeathUM": 2751, - "ViperUM": 2752, - "Dragon": 2753, - "Devil": 2754, - "touchUM": 2755, - "spellUM": 2756, - "feast": 2757, - "wound": 2758, - "grin": 2759, - "maim": 2760, - "hack": 2761, - "biteUM": 2762, - "rendUM": 2763, - "burn": 2764, - "rip": 2765, - "kill": 2766, - "callUM": 2767, - "vex": 2768, - "jade": 2769, - "web": 2770, - "shieldUM": 2771, - "KillerUM": 2772, - "RazorUM": 2773, - "drinker": 2774, - "shifter": 2775, - "crawler": 2776, - "dancer": 2777, - "bender": 2778, - "weaverUM": 2779, - "eater": 2780, - "widow": 2781, - "maggot": 2782, - "spawn": 2783, - "wight": 2784, - "GrumbleUM": 2785, - "GrowlerUM": 2786, - "SnarlUM": 2787, - "wolf": 2788, - "crow": 2789, - "raven": 2790, - "hawk": 2791, - "cloud": 2792, - "BangUM": 2793, - "head": 2794, - "skullUM": 2795, - "browUM": 2796, - "eyeUM": 2797, - "maw": 2798, - "tongue": 2799, - "fangUM": 2800, - "hornUM": 2801, - "thorn": 2802, - "clawUM": 2803, - "fistUM": 2804, - "heartUM": 2805, - "shankUM": 2806, - "skinUM": 2807, - "wingUM": 2808, - "pox": 2809, - "fester": 2810, - "blister": 3291, - "pus": 2812, - "SlimeUM": 2813, - "drool": 2814, - "froth": 2815, - "sludge": 2816, - "venom": 2817, - "poison": 2818, - "break": 2819, - "shard": 2820, - "flame": 2821, - "maul": 2822, - "thirstUM": 2823, - "lust": 2824, - "the Hammer": 2825, - "the Axe": 2826, - "the Sharp": 2827, - "the Jagged": 2828, - "the Flayer": 2829, - "the Slasher": 2830, - "the Impaler": 2831, - "the Hunter": 2832, - "the Slayer": 2833, - "the Mauler": 2834, - "the Destroyer": 2835, - "theQuick": 2836, - "the Witch": 2837, - "the Mad": 2838, - "the Wraith": 2839, - "the Shade": 2840, - "the Dead": 2841, - "the Unholy": 2842, - "the Howler": 2843, - "the Grim": 2844, - "the Dark": 2845, - "the Tainted": 2846, - "the Unclean": 2847, - "the Hungry": 2848, - "the Cold": 2849, - "The Cow King": 2850, - "Grand Vizier of Chaos": 2851, - "Lord De Seis": 2852, - "Infector of Souls": 2853, - "Riftwraith the Cannibal": 2854, - "Taintbreeder": 2855, - "The Tormentor": 2856, - "Winged Death": 2857, - "Maffer Dragonhand": 2858, - "Wyand Voidfinger": 2859, - "Toorc Icefist": 2860, - "Bremm Sparkfist": 2861, - "Geleb Flamefinger": 2862, - "Ismail Vilehand": 2863, - "Icehawk Riftwing": 2864, - "Sarina the Battlemaid": 2865, - "Stormtree": 2866, - "Witch Doctor Endugu": 2867, - "Web Mage the Burning": 2868, - "Bishibosh": 2869, - "Bonebreak": 2870, - "Coldcrow": 2871, - "Rakanishu": 2872, - "Treehead WoodFist": 2873, - "Griswold": 2874, - "The Countess": 2875, - "Pitspawn Fouldog": 2876, - "Flamespike the Crawler": 2877, - "Boneash": 2878, - "Radament": 2879, - "Bloodwitch the Wild": 2880, - "Fangskin": 2881, - "Beetleburst": 2882, - "Leatherarm": 2883, - "Coldworm the Burrower": 2884, - "Fire Eye": 2885, - "Dark Elder": 2886, - "The Summoner": 2887, - "Ancient Kaa the Soulless": 2888, - "The Smith": 2889, - "DeckardCain": 2890, - "Gheed": 2891, - "Akara": 2892, - "Kashya": 2893, - "Charsi": 2894, - "Wariv": 2895, - "Warriv": 2896, - "Rogue": 2897, - "StygianDoll": 2898, - "SoulKiller": 2899, - "Flayer": 2900, - "Fetish": 2901, - "RatMan": 2902, - "Undead StygianDoll": 2903, - "Undead SoulKiller": 2904, - "Undead Flayer": 2905, - "Undead Fetish": 2906, - "Undead RatMan": 2907, - "DarkFamiliar": 2908, - "BloodDiver": 2909, - "Gloombat": 2910, - "DesertWing": 2911, - "Banished": 2912, - "BloodLord": 2913, - "DarkLord": 2914, - "NightLord": 2915, - "GhoulLord": 2916, - "Spikefist": 2917, - "Thrasher": 2918, - "BrambleHulk": 2919, - "ThornedHulk": 2920, - "SpiderMagus": 2921, - "FlameSpider": 2922, - "PoisonSpinner": 2923, - "SandFisher": 2924, - "Arach": 2925, - "BloodWing": 2926, - "BloodHook": 2927, - "Feeder": 2928, - "Sucker": 2929, - "WingedNightmare": 2930, - "HellBuzzard": 2931, - "UndeadScavenger": 2932, - "CarrionBird": 2933, - "Unraveler": 2934, - "Guardian": 2935, - "HollowOne": 2936, - "Horadrim Ancient": 2937, - "AlbinoRoach": 2938, - "SteelWeevil": 2939, - "Scarab": 2940, - "SandWarrior": 2941, - "DungSoldier": 2942, - "HellSwarm": 2943, - "PlagueBugs": 2944, - "BlackLocusts": 2945, - "Itchies": 2946, - "HellCat": 2947, - "NightTiger": 2948, - "SaberCat": 2949, - "Huntress": 2950, - "RazorPitDemon": 2951, - "TreeLurker": 2952, - "CaveLeaper": 2953, - "TombCreeper": 2954, - "SandLeaper": 2955, - "TombViper": 2956, - "PitViper": 2957, - "Salamander": 2958, - "ClawViper": 2959, - "SerpentMagus": 2960, - "WorldKiller": 2961, - "GiantLamprey": 2962, - "Devourer": 2963, - "RockWorm": 2964, - "SandMaggot": 2965, - "JungleUrchin": 2966, - "RazorSpine": 2967, - "ThornBeast": 2968, - "SpikeFiend": 2969, - "QuillRat": 2970, - "HellClan": 2971, - "MoonClan": 2972, - "NightClan": 2973, - "DeathClan": 2974, - "BloodClan": 2975, - "TempleGuard": 2976, - "DoomApe": 2977, - "JungleHunter": 2978, - "RockDweller": 2979, - "DuneBeast": 2980, - "FleshHunter": 2981, - "BlackRogue": 2982, - "DarkStalker": 2983, - "VileHunter": 2984, - "DarkHunter": 2985, - "DarkShape": 2986, - "Apparition": 2987, - "Specter": 2988, - "Ghost": 2990, - "Assailant": 2991, - "Infidel": 2992, - "Invader": 2993, - "Marauder": 2994, - "SandRaider": 2995, - "GargantuanBeast": 2996, - "WailingBeast": 2997, - "Yeti": 2998, - "Crusher": 2999, - "Brute": 3000, - "CloudStalker": 3001, - "BlackVulture": 3002, - "BlackRaptor": 3003, - "BloodHawk": 3004, - "FoulCrow": 3005, - "PlagueBearer": 3006, - "Ghoul": 3007, - "DrownedCarcass": 3008, - "HungryDead": 3009, - "Zombie": 3010, - "Skeleton": 3011, - "Horror": 3012, - "Returned": 3013, - "BurningDead": 3014, - "BoneWarrior": 3015, - "Damned": 3016, - "Disfigured": 3017, - "Misshapen": 3018, - "Tainted": 3019, - "Afflicted": 3020, - "Andariel": 3021, - "Natalya": 3022, - "Drognan": 3023, - "Atma": 3024, - "Fara": 3025, - "Lysander": 3026, - "Jerhyn": 3027, - "jerhyn": 3028, - "Geglash": 3029, - "Elzix": 3030, - "Greiz": 3031, - "Meshif": 3032, - "Camel": 3033, - "Cadaver": 3034, - "PreservedDead": 3035, - "Embalmed": 3036, - "DriedCorpse": 3037, - "Decayed": 3038, - "Urdar": 3039, - "Mauler": 3040, - "Gorbelly": 3041, - "Blunderbore": 3042, - "WorldKillerYoung": 3043, - "GiantLampreyYoung": 3044, - "DevourerYoung": 3045, - "RockWormYoung": 3046, - "SandMaggotYoung": 3047, - "WorldKillerEgg": 3048, - "GiantLampreyEgg": 3049, - "DevourerEgg": 3050, - "RockWormEgg": 3051, - "SandMaggotEgg": 3052, - "Maggot": 3053, - "Duriel": 3054, - "BloodHawkNest": 3055, - "FlyingScimitar": 3056, - "CloudStalkerNest": 3057, - "BlackVultureNest": 3058, - "FoulCrowNest": 3059, - "Diablo": 3060, - "Baal": 3061, - "Mephisto": 3062, - "Cantor": 3063, - "Heirophant": 3064, - "Sexton": 3065, - "Zealot": 3066, - "Faithful": 3067, - "Zakarumite": 3068, - "BlackSoul": 3069, - "BurningSoul": 3070, - "SwampGhost": 3071, - "Gloam": 3072, - "WarpedShaman": 3073, - "DarkShaman": 3074, - "DevilkinShaman": 3075, - "CarverShaman": 3076, - "FallenShaman": 3077, - "WarpedFallen": 3078, - "DarkOne": 3079, - "Devilkin": 3080, - "Carver": 3081, - "Fallen": 3082, - "ReturnedArcher": 3083, - "HorrorArcher": 3084, - "BurningDeadArcher": 3085, - "BoneArcher": 3086, - "CorpseArcher": 3087, - "SkeletonArcher": 3088, - "FleshLancer": 3089, - "BlackLancer": 3090, - "DarkLancer": 3091, - "VileLancer": 3092, - "DarkSpearwoman": 3093, - "FleshArcher": 3094, - "BlackArcher": 3095, - "DarkRanger": 3096, - "VileArcher": 3097, - "DarkArcher": 3098, - "Summoner": 3099, - "StygianDollShaman": 3100, - "SoulKillerShaman": 3101, - "FlayerShaman": 3102, - "FetishShaman": 3103, - "RatManShaman": 3104, - "HorrorMage": 3105, - "BurningDeadMage": 3106, - "BoneMage": 3107, - "CorpseMage": 3108, - "ReturnedMage": 3109, - "GargoyleTrap": 3110, - "Bloodraven": 3111, - "navi": 3112, - "Kaelan": 3113, - "meshif": 3114, - "StygianWatcherHead": 3115, - "RiverStalkerHead": 3116, - "WaterWatcherHead": 3117, - "StygianWatcherLimb": 3118, - "RiverStalkerLimb": 3119, - "WaterWatcherLimb": 3120, - "NightMarauder": 3121, - "FireGolem": 3122, - "IronGolem": 3123, - "BloodGolem": 3124, - "ClayGolem": 3125, - "WorldKillerQueen": 3126, - "GiantLampreyQueen": 3127, - "DevourerQueen": 3128, - "RockWormQueen": 3129, - "SandMaggotQueen": 3130, - "Slime Prince": 3131, - "Bog Creature": 3132, - "Swamp Dweller": 3133, - "GiantUrchin": 3134, - "RazorBeast": 3135, - "ThornBrute": 3136, - "SpikeGiant": 3137, - "QuillBear": 3138, - "Council Member": 3139, - "youngdiablo": 3140, - "darkwanderer": 3141, - "HellSlinger": 3142, - "NightSlinger": 3143, - "SpearCat": 3144, - "Slinger": 3145, - "FireTower": 3146, - "LightningSpire": 3147, - "PitLord": 3148, - "Balrog": 3149, - "VenomLord": 3150, - "Iron Wolf": 3151, - "InvisoSpawner": 3152, - "OblivionKnight": 3153, - "Mage": 3154, - "AbyssKnight": 3155, - "Fighter Mage": 3156, - "DoomKnight": 3157, - "Fighter": 3158, - "MawFiend": 3159, - "CorpseSpitter": 3160, - "Corpulent": 3161, - "StormCaster": 3162, - "Strangler": 3163, - "Groper": 3164, - "GrotesqueWyrm": 3165, - "StygianDog": 3166, - "FleshBeast": 3167, - "Grotesque": 3168, - "StygianHag": 3169, - "FleshSpawner": 3170, - "RogueScout": 3171, - "BloodWingNest": 3172, - "BloodHookNest": 3173, - "FeederNest": 3174, - "SuckerNest": 3175, - "NecroMage": 3176, - "NecroSkeleton": 3177, - "TrappedSoul": 3178, - "Valkyrie": 3179, - "Dopplezon": 3180, - "Raises Fetishes": 3181, - "Raises Undead": 3182, - "Lays Eggs": 3183, - "Raises Fallen": 3184, - "heals Zealots and Cantors": 3185, - "drains mana and stamina": 3186, - "drains mana": 3187, - "drains stamina": 3188, - "stun attack": 3189, - "eats and spits corspes": 3190, - "homing missiles": 3191, - "raises Stygian Dolls": 3192, - "raises Soul Killers": 3193, - "raises Flayers": 3194, - "raises Fetishes": 3195, - "raises Ratmen": 3196, - "steals life": 3197, - "raises undead": 3198, - "raises Dark Ones": 3199, - "raises Devilkin": 3200, - "raises Carvers": 3201, - "raises Fallen": 3202, - "raises Warped Fallen": 3203, - "shocking hit": 3204, - "uniquextrastrong": 3205, - "uniqueextrafast": 3206, - "uniquecursed": 3207, - "uniquemagicresistance": 3208, - "uniquefireenchanted": 3209, - "monsteruniqueprop1": 3210, - "monsteruniqueprop2": 3211, - "monsteruniqueprop3": 3212, - "monsteruniqueprop4": 3213, - "monsteruniqueprop5": 3214, - "monsteruniqueprop6": 3215, - "monsteruniqueprop7": 3216, - "monsteruniqueprop8": 3217, - "monsteruniqueprop9": 3218, - "This Cow Bites": 3219, - "Champion": 3220, - "minion": 3221, - "Barrel": 3222, - "Lever1": 3223, - "BarrelEx": 3224, - "Door": 3225, - "Portal": 3226, - "ODoor": 3227, - "BlockedDoor": 3228, - "LockedDoor": 3229, - "StoneAlpha": 3230, - "StoneBeta": 3231, - "StoneDelta": 3232, - "StoneGamma": 3233, - "StoneLambda": 3234, - "StoneTheta": 3235, - "Crate": 3236, - "Casket": 3237, - "Cabinet": 3238, - "Vase": 3239, - "Inifuss": 3240, - "corpse": 3241, - "RogueCorpse": 3242, - "CorpseOnStick": 3243, - "TowerTome": 3244, - "Gibbet": 3245, - "MummyGenerator": 3246, - "ArmorStand": 3247, - "WeaponRack": 3248, - "Sarcophagus": 3249, - "Trap Door": 3250, - "LargeUrn": 3251, - "CanopicJar": 3252, - "Obelisk": 3253, - "HoleAnim": 3254, - "Shrine": 3255, - "Urn": 3256, - "Waypoint": 22526, - "Well": 3258, - "bag": 3259, - "Chest": 3260, - "chest": 3261, - "lockedchest": 3262, - "HorazonsJournal": 3263, - "templeshrine": 3264, - "stair": 3265, - "coffin": 3266, - "bookshelf": 3267, - "loose boulder": 3268, - "loose rock": 3269, - "hollow log": 3270, - "hiding spot": 3271, - "fire": 3328, - "Chest3": 3273, - "hidden stash": 3274, - "GuardCorpse": 3275, - "bowl": 3276, - "jug": 3277, - "AmbientSound": 3278, - "ratnest": 3279, - "burning body": 3280, - "well": 22525, - "door": 3282, - "skeleton": 3283, - "skullpile": 3284, - "cocoon": 3285, - "gidbinn altar": 3286, - "cowa": 3287, - "manashrine": 3288, - "bed": 3289, - "ratchest": 3290, - "bank": 3292, - "goo pile": 3293, - "holyshrine": 3294, - "teleportation pad": 3295, - "ratchest-r": 3296, - "skull pile": 3297, - "body": 3298, - "hell bridge": 3299, - "compellingorb": 3300, - "basket": 3301, - "Basket": 3302, - "RockPIle": 3303, - "Tome": 3304, - "dead body": 3305, - "eunuch": 3306, - "dead guard": 3307, - "portal": 3308, - "sarcophagus": 3309, - "dead villager": 3310, - "sewer lever": 3311, - "sewer stairs": 3312, - "magic shrine": 3313, - "wirt's body": 3314, - "stash": 3315, - "guyq": 3316, - "taintedsunaltar": 3317, - "Hellforge": 3318, - "Corpsefire": 3319, - "fissure": 3320, - "BoneChest": 3321, - "casket": 3322, - "HungSkeleton": 3323, - "pillar": 3324, - "Hydra": 3325, - "Turret": 3326, - "a trap": 3327, - "cost": 3329, - "Repair": 3330, - "Sell": 3331, - "Identify": 3332, - "priceless": 3333, - "NPCMenuTradeRepair": 3334, - "NPCPurchaseItems": 3335, - "NPCSellItems": 3336, - "NPCHeal": 3337, - "NPCRepairItems": 3338, - "NPCNextPage": 3339, - "NPCPreviousPage": 3340, - "strUiMenu2": 4131, - "TransactionMenu1a": 3342, - "TransactionMenu1f": 3343, - "VerifyTransaction1": 3344, - "VerifyTransaction2": 3345, - "VerifyTransaction3": 3346, - "VerifyTransaction4": 3347, - "VerifyTransaction5": 3348, - "VerifyTransaction6": 3349, - "VerifyTransaction7": 3350, - "VerifyTransaction8": 3351, - "VerifyTransaction9": 3352, - "TransactionResults1": 3353, - "TransactionResults2": 3354, - "TransactionResults3": 3355, - "TransactionResults4": 3356, - "TransactionResults5": 3357, - "TransactionResults6": 3358, - "TransactionResults7": 3359, - "TransactionResults8": 3360, - "TransactionResults9": 3361, - "TransactionResults10": 3362, - "TransactionResults11": 3363, - "ItemDesc1s": 3364, - "ItemDesc1t": 3365, - "HP": 3366, - "AC": 3367, - "Level": 3368, - "Cost": 3369, - "Damage": 3370, - "strhirespecial1": 3371, - "strhirespecial2": 3372, - "strhirespecial3": 3373, - "strhirespecial4": 3374, - "strhirespecial5": 3375, - "strhirespecial6": 3376, - "strhirespecial7": 3377, - "strhirespecial8": 3378, - "strhirespecial9": 3379, - "strhirespecial10": 3380, - "TalkMenu": 3381, - "WarrivMenu1b": 3382, - "WarrivMenu1c": 3383, - "MeshifMenuEast": 3384, - "MeshifMenuWest": 3385, - "NPCMenuNews0": 3386, - "NPCMenuNews1": 3387, - "NPCMenuNews2": 3388, - "NPCMenuNews3": 3389, - "NPCMenuNews4": 3390, - "NPCMenuTalkMore": 3391, - "NPCTownMore0": 3392, - "NPCTownMore1": 3393, - "NPCMenuLeave": 3394, - "NPCGossipMenu": 3395, - "NPCMenuTrade": 3396, - "NPCMenuHire": 3397, - "gamble": 3398, - "Intro": 3399, - "Back": 3400, - "ok": 3401, - "cancel": 3402, - "Continue": 3403, - "strMenuMain15": 3404, - "strOptMusic": 3405, - "strOptSound": 3406, - "strOptGamma": 3407, - "strOptRender": 3408, - "strOptPrevious": 3409, - "cfgCtrl": 3410, - "merc01": 3411, - "merc02": 3412, - "merc03": 3413, - "merc04": 3414, - "merc05": 3415, - "merc06": 3416, - "merc07": 3417, - "merc08": 3418, - "merc09": 3419, - "merc10": 3420, - "merc11": 3421, - "merc12": 3422, - "merc13": 3423, - "merc14": 3424, - "merc15": 3425, - "merc16": 3426, - "merc17": 3427, - "merc18": 3428, - "merc19": 3429, - "merc20": 3430, - "merc21": 3431, - "merc22": 3432, - "merc23": 3433, - "merc24": 3434, - "merc25": 3435, - "merc26": 3436, - "merc27": 3437, - "merc28": 3438, - "merc29": 3439, - "merc30": 3440, - "merc31": 3441, - "merc32": 3442, - "merc33": 3443, - "merc34": 3444, - "merc35": 3445, - "merc36": 3446, - "merc37": 3447, - "merc38": 3448, - "merc39": 3449, - "merc40": 3450, - "merc41": 3451, - "merclevelup": 3452, - "Socketable": 3453, - "ItemStats1a": 3454, - "ItemStats1b": 3455, - "ItemStats1c": 3456, - "ItemStats1d": 3457, - "ItemStats1e": 3458, - "ItemStats1f": 3459, - "ItemStats1g": 3460, - "ItemStats1h": 3461, - "ItemStats1i": 3462, - "ItemStats1j": 3463, - "ItemStast1k": 3464, - "ItemStats1l": 3465, - "ItemStats1m": 3466, - "ItemStats1n": 3467, - "ItemStats1o": 3468, - "ItemStats1p": 3469, - "ItemStats1q": 3470, - "ItemStatsrejuv1": 3471, - "ItemStatsrejuv2": 3472, - "ModStr1a": 3473, - "ModStr1b": 3474, - "ModStr1c": 3475, - "ModStr1d": 3476, - "ModStr1e": 3477, - "ModStr1f": 3478, - "ModStr1g": 3479, - "ModStr1h": 3480, - "ModStr1i": 3481, - "ModStr1j": 3482, - "ModStr1k": 3483, - "ModStr1l": 3484, - "ModStr1m": 3485, - "ModStr1n": 3486, - "ModStr1o": 3487, - "ModStr1p": 3488, - "ModStr1q": 3489, - "ModStr1r": 3490, - "ModStr1s": 3491, - "ModStr1t": 3492, - "ModStr1u": 3493, - "ModStr1v": 3494, - "ModStr1w": 3495, - "ModStr1x": 3496, - "ModStr1y": 3497, - "ModStr1z": 3498, - "ModStr2a": 3499, - "ModStr2b": 3500, - "ModStr2c": 3501, - "ModStr2d": 3502, - "ModStr2e": 3503, - "ModStr2f": 3504, - "ModStr2g": 3505, - "ModStr2h": 3506, - "ModStr2i": 3507, - "ModStr2j": 3508, - "ModStr2k": 3509, - "ModStr2l": 3510, - "ModStr2m": 3511, - "ModStr2n": 3512, - "ModStr2o": 3513, - "ModStr2p": 3514, - "ModStr2q": 3515, - "ModStr2r": 3516, - "ModStr2s": 3517, - "ModStr2t": 3518, - "ModStr2u": 3519, - "Modstr2v": 3520, - "ModStr2w": 3521, - "ModStr2x": 3522, - "ModStr2y": 3523, - "ModStr2z": 3524, - "ModStr3a": 3525, - "ModStr3b": 3526, - "ModStr3c": 3527, - "ModStr3d": 3528, - "ModStr3e": 3529, - "ModStr3f": 3530, - "ModStr3g": 3531, - "ModStr3h": 3532, - "ModStr3i": 3533, - "ModStr3j": 3534, - "ModStr3k": 3535, - "ModStr3l": 3536, - "ModStr3m": 3537, - "ModStr3n": 3538, - "ModStr3o": 3539, - "ModStr3p": 3540, - "ModStr3q": 3541, - "ModStr3r": 3542, - "ModStr3u": 3543, - "ModStr3v": 3544, - "ModStr3w": 3545, - "ModStr3x": 3546, - "ModStr3y": 3547, - "ModStr3z": 3548, - "ModStr4a": 3549, - "ModStr4b": 3550, - "ModStr4c": 3551, - "ModStr4d": 3552, - "ModStr4e": 3553, - "ModStr4f": 3554, - "ModStr4g": 3555, - "ModStr4h": 3556, - "ModStr4i": 3557, - "ModStr4j": 3558, - "ModStr4k": 3559, - "ModStr4l": 3560, - "ModStr4m": 3561, - "ModStr4n": 3562, - "ModStr4o": 3563, - "ModStr4p": 3564, - "ModStr4q": 3565, - "ModStr4r": 3566, - "ModStr4s": 3567, - "ModStr4t": 3568, - "ModStr4u": 3569, - "ModStr4v": 3570, - "ModStr4w": 3571, - "ModStr4x": 3572, - "ModStr4y": 3573, - "ModStr4z": 3574, - "ModStr5a": 3575, - "ModStr5b": 3576, - "ModStr5c": 3577, - "ModStr5d": 3578, - "ModStr5e": 3579, - "ModStr5f": 3580, - "ModStr5g": 3581, - "ModStr5h": 3582, - "ModStr5i": 3583, - "ModStr5j": 3584, - "ModStr5k": 3585, - "ModStr5l": 3586, - "ModStr5m": 3587, - "ModStr5n": 3588, - "ModStr5o": 3589, - "ModStr5p": 3590, - "ModStr5q": 3591, - "ModStr5r": 3592, - "ModStr5s": 3593, - "ModStr5t": 3594, - "ModStr5u": 3595, - "ModStr5v": 3596, - "ModStr5w": 3597, - "ModStr5x": 3598, - "ModStr5y": 3599, - "ModStr5z": 3600, - "ModStr6a": 3601, - "ModStr6b": 3602, - "ModStr6c": 3603, - "ModStr6d": 3604, - "ModStr6e": 3605, - "ModStr6f": 3606, - "ModStr6g": 3607, - "ModStr6h": 3608, - "ModStr6i": 3609, - "strModAllResistances": 3610, - "strModAllSkillLevels": 3611, - "strModFireDamage": 3612, - "strModFireDamageRange": 3613, - "strModColdDamage": 3614, - "strModColdDamageRange": 3615, - "strModLightningDamage": 3616, - "strModLightningDamageRange": 3617, - "strModMagicDamage": 3618, - "strModMagicDamageRange": 3619, - "strModPoisonDamage": 3620, - "strModPoisonDamageRange": 3621, - "strModMinDamage": 3622, - "strModMinDamageRange": 3623, - "strModEnhancedDamage": 3624, - "improved damage": 3625, - "improved to hit": 3626, - "improved armor class": 3627, - "improved durability": 3628, - "Quick Strike": 3629, - "strGemPlace1": 3630, - "strGemPlace2": 3631, - "gemeffect1": 3632, - "gemeffect2": 3633, - "gemeffect3": 3634, - "gemeffect4": 3635, - "gemeffect5": 3636, - "gemeffect6": 3637, - "gemeffect7": 3638, - "sysmsg1": 3639, - "sysmsg2": 3640, - "sysmsg3": 3641, - "sysmsg4": 3642, - "sysmsg3a": 3643, - "sysmsg4a": 3644, - "sysmsg5": 3645, - "sysmsg6": 3646, - "sysmsg7": 3647, - "sysmsg8": 3648, - "sysmsg9": 3649, - "sysmsg10": 3650, - "sysmsg11": 3651, - "sysmsg12": 3652, - "sysmsgPlayer": 3653, - "chatmsg1": 3654, - "chatmsg2": 3655, - "chatmsg3": 3657, - "strwhisperworked": 3658, - "syswork": 3659, - "ShrId0": 3660, - "ShrId1": 3661, - "ShrId2": 3662, - "ShrId3": 3663, - "ShrId4": 3664, - "ShrId5": 3665, - "ShrId6": 3666, - "ShrId7": 3667, - "ShrId8": 3668, - "ShrId9": 3669, - "ShrId10": 3670, - "ShrId11": 3671, - "ShrId12": 3672, - "ShrId13": 3673, - "ShrId14": 3674, - "ShrId15": 3675, - "ShrId16": 3676, - "ShrId17": 3677, - "ShrId18": 3678, - "ShrId19": 3679, - "ShrId20": 3680, - "ShrId21": 3681, - "ShrId22": 3682, - "ShrMsg0": 3683, - "ShrMsg1": 3684, - "ShrMsg2": 3685, - "ShrMsg3": 3686, - "ShrMsg4": 3687, - "ShrMsg5": 3688, - "ShrMsg6": 3689, - "ShrMsg7": 3690, - "ShrMsg8": 3691, - "ShrMsg9": 3692, - "ShrMsg10": 3693, - "ShrMsg11": 3694, - "ShrMsg12": 3695, - "ShrMsg13": 3696, - "ShrMsg14": 3697, - "ShrMsg15": 3698, - "ShrMsg16": 3699, - "ShrMsg17": 3700, - "ShrMsg18": 3701, - "ShrMsg19": 3702, - "ShrMsg20": 3703, - "ShrMsg21": 3704, - "ShrMsg22": 3705, - "strqi1": 3706, - "strqi2": 3707, - "stsa1q3alert": 3708, - "stsa1q4alert": 3709, - "stsa3q1alert": 3710, - "qstsa1qt": 3711, - "qstsa1qt0": 3712, - "qstsa1q0": 3713, - "qstsa1q1": 3714, - "qstsa1q2": 3715, - "qstsa1q3": 3716, - "qstsa1q4": 3717, - "qstsa1q5": 3718, - "qstsa1q6": 3719, - "strplaylast": 3720, - "newquestlog": 3721, - "qsts": 3722, - "noactivequest": 3723, - "qstsxxx": 3724, - "qstsnull": 3725, - "qstsComplete": 3726, - "qstsother": 3727, - "qstsprevious": 3728, - "qstsThankYouComeAgain": 3729, - "qstsThankYouComeAgainMulti": 3730, - "qstsThankYouComeAgainSingle": 3731, - "Qstsyouarenot8": 3732, - "qstsa1q3x": 3733, - "qstsa1q4x": 3734, - "qstsa1q11": 3735, - "qstsa1q12": 3736, - "qstsa1q13": 3737, - "qstsa1q14": 3738, - "qstsa1q140": 3739, - "qstsa1q15": 3740, - "qstsa1q21": 3741, - "qstsa1q22": 3742, - "qstsa1q23": 3743, - "qstsa1q41": 3744, - "qstsa1q42": 3745, - "qstsa1q43": 3746, - "qstsa1q44": 3747, - "qstsa1q45": 3748, - "qstsa1q46": 3749, - "qstsa1q46b": 3750, - "qstsa1q51": 3751, - "qstsa1q51a": 3752, - "qstsa1q51b": 3753, - "qstsa1q52": 3754, - "qstsa1q31": 3755, - "qstsa1q32": 3756, - "qstsa1q32b": 3757, - "qstsa1q61": 3758, - "qstsa1q62": 3759, - "qstsa1q62b": 3760, - "qstsa1q63": 3761, - "KeyNone": 3762, - "KeyLButton": 3763, - "KeyRButton": 3764, - "KeyCancel": 3765, - "KeyMButton": 3766, - "Key4Button": 3767, - "Key5Button": 3768, - "KeyWheelUp": 3769, - "KeyWheelDown": 3770, - "KeyKana": 3771, - "KeyJunja": 3772, - "KeyFinal": 3773, - "KeyKanji": 3774, - "KeyEscape": 3775, - "KeyConvert": 3776, - "KeyNonConvert": 3777, - "KeyAccept": 3778, - "KeyModeChange": 3779, - "KeyLeft": 3780, - "KeyUp": 3781, - "KeyRight": 3782, - "KeyDown": 3783, - "KeySelect": 3784, - "KeyExecute": 3785, - "KeyLWin": 3786, - "KeyRWin": 3787, - "KeyApps": 3788, - "KeyNumLock": 3789, - "KeyBack": 3790, - "KeyTab": 3791, - "KeyClear": 3792, - "KeyReturn": 3793, - "KeyShift": 3794, - "KeyControl": 3795, - "KeyMenu": 3796, - "KeyPause": 3797, - "KeyCapital": 3798, - "KeySpace": 3799, - "KeyPrior": 3800, - "KeyNext": 3801, - "KeyEnd": 3802, - "KeyHome": 3803, - "KeyPrint": 3804, - "KeySnapshot": 3805, - "KeyInsert": 3806, - "KeyDelete": 3807, - "KeyHelp": 3808, - "KeyNumPad0": 3809, - "KeyNumPad1": 3810, - "KeyNumPad2": 3811, - "KeyNumPad3": 3812, - "KeyNumPad4": 3813, - "KeyNumPad5": 3814, - "KeyNumPad6": 3815, - "KeyNumPad7": 3816, - "KeyNumPad8": 3817, - "KeyNumPad9": 3818, - "KeyMultiply": 3819, - "KeyAdd": 3820, - "KeySeparator": 3821, - "KeySubtract": 3822, - "KeyDecimal": 3823, - "KeyDivide": 3824, - "KeyF1": 3825, - "KeyF2": 3826, - "KeyF3": 3827, - "KeyF4": 3828, - "KeyF5": 3829, - "KeyF6": 3830, - "KeyF7": 3831, - "KeyF8": 3832, - "KeyF9": 3833, - "KeyF10": 3834, - "KeyF11": 3835, - "KeyF12": 3836, - "KeyF13": 3837, - "KeyF14": 3838, - "KeyF15": 3839, - "KeyF16": 3840, - "KeyF17": 3841, - "KeyF18": 3842, - "KeyF19": 3843, - "KeyF20": 3844, - "KeyF21": 3845, - "KeyF22": 3846, - "KeyF23": 3847, - "KeyF24": 3848, - "KeyScroll": 3849, - "KeySemicolon": 3850, - "KeyEqual": 3851, - "KeyComma": 3852, - "KeyMinus": 3853, - "KeyPeriod": 3854, - "KeySlash": 3855, - "KeyTilde": 3856, - "KeyLBracket": 3857, - "KeyBackslash": 3858, - "KeyRBracket": 3859, - "KeyApostrophe": 3860, - "ShorthandKeyMButton": 3861, - "ShorthandKey4Button": 3862, - "ShorthandKey5Button": 3863, - "ShorthandKeyWheelUp": 3864, - "ShorthandKeyWheelDown": 3865, - "ShorthandKeyKana": 3866, - "ShorthandKeyJunja": 3867, - "ShorthandKeyFinal": 3868, - "ShorthandKeyKanji": 3869, - "ShorthandKeyEscape": 3870, - "ShorthandKeyConvert": 3871, - "ShorthandKeyNonConvert": 3872, - "ShorthandKeyAccept": 3873, - "ShorthandKeyModeChange": 3874, - "ShorthandKeyLeft": 3875, - "ShorthandKeyRight": 3876, - "ShorthandKeyDown": 3877, - "ShorthandKeySelect": 3878, - "ShorthandKeyExecute": 3879, - "ShorthandKeyLeftWindows": 3880, - "ShorthandKeyRightWindows": 3881, - "ShorthandKeyApps": 3882, - "ShorthandKeyNumLock": 3883, - "ShorthandKeyBackspace": 3884, - "ShorthandKeyClear": 3885, - "ShorthandKeyEnter": 3886, - "ShorthandKeyShift": 3887, - "ShorthandKeyControl": 3888, - "ShorthandKeyPause": 3889, - "ShorthandKeyCapsLock": 3890, - "ShorthandKeySpace": 3891, - "ShorthandKeyPageUp": 3892, - "ShorthandKeyPageDown": 3893, - "ShorthandKeyHome": 3894, - "ShorthandKeyPrintScreen": 3895, - "ShorthandKeyInsert": 3896, - "ShorthandKeyDelete": 3897, - "ShorthandKeyHelp": 3898, - "ShorthandKeyNumPad0": 3899, - "ShorthandKeyNumPad1": 3900, - "ShorthandKeyNumPad2": 3901, - "ShorthandKeyNumPad3": 3902, - "ShorthandKeyNumPad4": 3903, - "ShorthandKeyNumPad5": 3904, - "ShorthandKeyNumPad6": 3905, - "ShorthandKeyNumPad7": 3906, - "ShorthandKeyNumPad8": 3907, - "ShorthandKeyNumPad9": 3908, - "ShorthandKeyNumPad*\tnp*": 3909, - "ShorthandKeyNumPad+\tnp+": 3910, - "ShorthandKeyNumPad-\tnp-": 3911, - "ShorthandKeyNumPad.\tnp.": 3912, - "ShorthandKeyNumPad/\tnp/": 3913, - "ShorthandKeyScroll": 3914, - "KeyMacOption": 3915, - "KeyMacCommand": 3916, - "KeyMacNumPad=\tNum Pad =": 3917, - "ShorthandKeyMacOption": 3918, - "ShorthandKeyMacCommand": 3919, - "ShorthandKeyMacNumPad=\tNP=": 3920, - "CfgFunction": 3921, - "CfgPrimaryKey": 3922, - "CfgSecondaryKey": 3923, - "CfgCharacter": 3924, - "CfgInventory": 3925, - "CfgParty": 3926, - "CfgMessageLog": 3927, - "CfgQuestLog": 3928, - "CfgChat": 3929, - "CfgAutoMap": 3930, - "CfgAutoMapCenter": 3931, - "CfgMiniMap": 3932, - "CfgHelp": 3933, - "CfgSkillTree": 3934, - "CfgSkillPick": 3935, - "CfgSkill1": 3936, - "CfgSkill2": 3937, - "CfgSkill3": 3938, - "CfgSkill4": 3939, - "CfgSkill5": 3940, - "CfgSkill6": 3941, - "CfgSkill7": 3942, - "CfgSkill8": 3943, - "Cfgskillup": 3944, - "Cfgskilldown": 3945, - "CfgBeltShow": 3946, - "CfgBelt1": 3947, - "CfgBelt2": 3948, - "CfgBelt3": 3949, - "CfgBelt4": 3950, - "CfgBelt5": 3951, - "CfgBelt6": 3952, - "CfgBelt7": 3953, - "CfgBelt8": 3954, - "CfgBelt9": 3955, - "CfgBelt10": 3956, - "CfgBelt11": 3957, - "CfgBelt12": 3958, - "CfgSay0": 3959, - "CfgSay1": 3960, - "CfgSay2": 3961, - "CfgSay3": 3962, - "CfgSay4": 3963, - "CfgSay5": 3964, - "CfgSay6": 3965, - "CfgRun": 3966, - "CfgRunLock": 3967, - "CfgStandStill": 3968, - "CfgShowItems": 3969, - "CfgClearScreen": 3970, - "CfgSnapshot": 3971, - "CfgDefault": 3972, - "CfgAccept": 3973, - "CfgCancel": 3974, - "strNoKeysAssigned": 3975, - "KeysAssigned": 3976, - "CantAssignMB": 3977, - "CantAssignMW": 3978, - "CantAssignKey": 3979, - "CfgClearKey": 3980, - "Cfgcleartextmsg": 3981, - "CfgTogglePortraits": 3982, - "CfgAutoMapFade": 3983, - "CfgAutoMapNames": 3984, - "CfgAutoMapParty": 3985, - "strlvlup": 3986, - "strnewskl": 3987, - "warpsheader": 3988, - "nowarps": 3989, - "waypointsheader": 3990, - "nowaypoints": 3991, - "max": 3992, - "MAX": 3993, - "colorcode": 3994, - "space": 3995, - "dash": 3996, - "colon": 3997, - "newline": 3998, - "pipe": 3999, - "slash": 4000, - "percent": 4001, - "plus": 4002, - "to": 4003, - "srostertitle": 4004, - "dwell": 4005, - "larva": 4006, - "Barbarian": 4007, - "Paladin": 4008, - "Necromancer": 4009, - "Sorceress": 4010, - "Amazon": 4011, - "druidstr \tDruid": 4012, - "assassinstr": 4013, - "Nest": 4014, - "NoParty": 4015, - "ItsMyParty": 4016, - "Upgrade": 4017, - "upgraderestrict": 4018, - "Use": 4019, - "NPCIdentify1": 4020, - "NPCIdentify2": 4021, - "strCannotDoThisToUnknown": 4022, - "Body Looted": 4023, - "Party1": 4024, - "Party2": 4025, - "Party3": 4026, - "Party4": 4027, - "Party5": 4028, - "Party6": 4029, - "Party7": 4030, - "Party8": 4031, - "Party9": 4032, - "strDropGoldHowMuch": 4033, - "strDropGoldInfo": 4034, - "strMsgLog": 4035, - "strBSArmor": 4036, - "strBSWeapons": 4037, - "strBSMagic": 4038, - "strBSMisc": 4039, - "strTrade": 4040, - "strTradeAccept": 4041, - "strTradeAgreeTo": 4042, - "strWaitingForOtherPlayer": 4043, - "strTradeBusy": 4044, - "strTradeTooFull": 4045, - "strTradeGoldHowMuch": 4046, - "strTradeTimeout": 4047, - "SysmsgPlayer1": 4048, - "strBankGoldDeposit": 4049, - "strBankGoldWithdraw": 4050, - "GoldMax": 4051, - "StrUI0": 4052, - "StrUI1": 4053, - "StrUI2": 4054, - "StrUI3": 4055, - "StrUI4": 4056, - "strchrlvl": 4057, - "strchrexp": 4058, - "strchrnxtlvl": 4059, - "strchrstr": 4060, - "strchrskm": 4061, - "strchrdex": 4062, - "strchratr": 4063, - "strchrdef": 4064, - "strchrrat": 4065, - "strchrvit": 4066, - "strchrstm": 4067, - "strchrlif": 4068, - "strchreng": 4069, - "strchrman": 4070, - "strchrfir": 4071, - "strchrcol": 4072, - "strchrlit": 4073, - "strchrpos": 4074, - "strchrstat": 4075, - "strchrrema": 4076, - "WeaponDescMace": 4077, - "WeaponDescAxe": 4078, - "WeaponDescSword": 4079, - "WeaponDescDagger": 4080, - "WeaponDescThrownPotion": 4081, - "WeaponDescJavelin": 4082, - "WeaponDescSpear": 4083, - "WeaponDescBow": 4084, - "WeaponDescStaff": 4085, - "WeaponDescPoleArm": 4086, - "WeaponDescCrossBow": 4087, - "WeaponAttackFastest": 4088, - "WeaponAttackVeryFast": 4089, - "WeaponAttackFast": 4090, - "WeaponAttackNormal": 4091, - "WeaponAttackSlow": 4092, - "WeaponAttackVerySlow": 4093, - "WeaponAttackSlowest": 4094, - "strNecromanerOnly": 4095, - "strPaladinOnly": 4096, - "strSorceressOnly": 4097, - "strMaceSpecialDamage": 4098, - "strGoldLabel": 4099, - "strParty1": 4100, - "strParty2": 4101, - "strParty3": 4102, - "strParty4": 4103, - "strParty5": 4104, - "strParty6": 4105, - "strParty7": 4106, - "strParty8": 4107, - "strParty9": 4108, - "strParty10": 4109, - "strParty11": 4110, - "strParty12": 4111, - "strParty13": 4112, - "strParty14": 4113, - "strParty15": 4114, - "strParty16": 4115, - "strParty17": 4116, - "strParty18": 4117, - "strParty19": 4118, - "strParty22": 4119, - "strParty24": 4120, - "strParty25": 4121, - "StrParty26": 4122, - "StrParty27": 4123, - "strGoldWithdraw": 4124, - "strGoldDrop": 4125, - "strGoldDeposit": 4126, - "strGoldTrade": 4127, - "strGoldInStash": 4128, - "strGoldTradepup": 4129, - "strUiMenu1": 4130, - "strUiBank": 4132, - "strUnknownTomb": 4133, - "strTradeOtherBox": 4134, - "strTradeBox": 4135, - "strFree": 4136, - "act1": 4137, - "act2": 4138, - "act3": 4139, - "act4": 4140, - "level": 4141, - "lowercasecancel": 4142, - "close": 4143, - "strClose": 4144, - "Lightning Spell": 4145, - "Fire Spell": 4146, - "Cold Spell": 4147, - "Yourparty": 4148, - "Inparty": 4149, - "Invite": 4150, - "Accept": 4151, - "Leave": 4152, - "Partyclose": 4153, - "partycharama": 4154, - "partycharsor": 4155, - "partycharbar": 4156, - "partycharnec": 4157, - "partycharpal": 4158, - "charavghit": 4159, - "charmonster": 4160, - "charmontohit1": 4161, - "charmontohit2": 4162, - "panelexp": 4163, - "panelstamina": 4164, - "panelhealth": 4165, - "panelmana": 4166, - "panelmini": 4167, - "panelcmini": 4168, - "minipanelchar": 4169, - "minipanelinv": 4170, - "minipaneltree": 4171, - "minipanelparty": 4172, - "minipanelautomap": 4173, - "minipanelmessage": 4174, - "minipanelquest": 4175, - "minipanelmenubtn": 4176, - "minipanelHelp": 4177, - "minipanelspecial": 4178, - "RunOn": 4179, - "RunOff": 4180, - "automapgame": 4181, - "automappw": 4182, - "automapdif": 4183, - "scrollbooktext": 4184, - "skilldesc1": 4185, - "skilldesc2": 4186, - "skilldesc3": 4187, - "skilldesc4": 4188, - "strpanel1": 4189, - "strpanel2": 4190, - "strpanel3": 4191, - "strpanel4": 4192, - "strpanel5": 4193, - "strpanel6": 4194, - "strpanel7": 4195, - "strpanel8": 4196, - "stashfull": 4197, - "Strhelp1": 4198, - "StrHelp2": 4199, - "StrHelp3": 4200, - "StrHelp4": 4201, - "StrHelp5": 4202, - "StrHelp6": 4203, - "StrHelp7": 4204, - "StrHelp8": 4205, - "StrHelp8a": 4206, - "StrHelp9": 4207, - "StrHelp10": 4208, - "StrHelp11": 4209, - "StrHelp12": 4210, - "StrHelp13": 4211, - "StrHelp14": 4212, - "StrHelp14a": 4213, - "StrHelp15": 4214, - "StrHelp16": 4215, - "StrHelp16a": 4216, - "StrHelp17": 4217, - "StrHelp18": 4218, - "StrHelp19": 4219, - "StrHelp20": 4220, - "StrHelp21": 4221, - "StrHelp22": 4222, - "strSklTree": 4223, - "StrSklTreea": 4224, - "StrSklTreeb": 4225, - "StrSklTreec": 4226, - "StrSklTree1": 4227, - "StrSklTree2": 4228, - "StrSklTree3": 4229, - "StrSklTree4": 4230, - "StrSklTree5": 4231, - "StrSklTree6": 4232, - "StrSklTree7": 4233, - "StrSklTree8": 4234, - "StrSklTree9": 4235, - "StrSklTree10": 4236, - "StrSklTree11": 4237, - "StrSklTree12": 4238, - "StrSklTree13": 4239, - "StrSklTree14": 4240, - "StrSklTree15": 4241, - "StrSklTree16": 4242, - "StrSklTree17": 4243, - "StrSklTree18": 4244, - "StrSklTree19": 4245, - "StrSklTree20": 4246, - "StrSklTree21": 4247, - "StrSklTree22": 4248, - "StrSklTree23": 4249, - "StrSklTree24": 4250, - "StrSklTree25": 4251, - "StrSkill0": 4252, - "StrSkill1": 4253, - "StrSkill2": 4254, - "StrSkill3": 4255, - "StrSkill4": 4256, - "StrSkill5": 4257, - "StrSkill6": 4258, - "StrSkill7": 4259, - "StrSkill8": 4260, - "StrSkill9": 4261, - "StrSkill10": 4262, - "StrSkill11": 4263, - "StrSkill12": 4264, - "StrSkill13": 4265, - "StrSkill14": 4266, - "StrSkill15": 4267, - "StrSkill16": 4268, - "StrSkill17": 4269, - "StrSkill18": 4270, - "StrSkill19": 4271, - "StrSkill20": 4272, - "StrSkill21": 4273, - "StrSkill22": 4274, - "StrSkill23": 4275, - "StrSkill24": 4276, - "StrSkill25": 4277, - "StrSkill26": 4278, - "StrSkill27": 4279, - "StrSkill28": 4280, - "StrSkill29": 4281, - "StrSkill30": 4282, - "StrSkill31": 4283, - "StrSkill32": 4284, - "StrSkill33": 4285, - "StrSkill34": 4286, - "StrSkill35": 4287, - "StrSkill36": 4288, - "StrSkill37": 4289, - "StrSkill38": 4290, - "StrSkill39": 4291, - "StrSkill40": 4292, - "StrSkill41": 4293, - "StrSkill42": 4294, - "StrSkill43": 4297, - "StrSkill44": 4298, - "StrSkill45": 4299, - "StrSkill46": 4300, - "StrSkill47": 4301, - "StrSkill48": 4302, - "StrSkill49": 4303, - "StrSkill50": 4304, - "StrSkill51": 4305, - "StrSkill52": 4306, - "StrSkill53": 4307, - "StrSkill54": 4308, - "StrSkill55": 4309, - "StrSkill56": 4310, - "StrSkill57": 4311, - "StrSkill58": 4312, - "StrSkill59": 4313, - "StrSkill60": 4314, - "StrSkill61": 4315, - "StrSkill62": 4316, - "StrSkill63": 4317, - "StrSkill64": 4318, - "StrSkill65": 4319, - "StrSkill66": 4320, - "StrSkill67": 4321, - "StrSkill68": 4322, - "StrSkill69": 4323, - "StrSkill70": 4324, - "StrSkill71": 4325, - "StrSkill72": 4326, - "StrSkill73": 4327, - "StrSkill74": 4328, - "StrSkill75": 4329, - "StrSkill76": 4330, - "StrSkill77": 4331, - "StrSkill78": 4332, - "StrSkill79": 4333, - "StrSkill80": 4334, - "StrSkill81": 4335, - "StrSkill82": 4336, - "StrSkill83": 4337, - "StrSkill84": 4338, - "StrSkill85": 4339, - "StrSkill86": 4340, - "StrSkill87": 4341, - "StrSkill88": 4342, - "StrSkill89": 4343, - "StrSkill90": 4344, - "StrSkill91": 4345, - "StrSkill92": 4346, - "StrSkill94": 4347, - "StrSkill95": 4348, - "StrSkill96": 4349, - "StrSkill97": 4350, - "StrSkill98": 4351, - "StrSkill99": 4352, - "StrSkill100": 4353, - "StrSkill101": 4354, - "StrSkill102": 4355, - "StrSkill103": 4356, - "StrSkill104": 4357, - "StrSkill105": 4358, - "StrSkill106": 4359, - "StrSkill107": 4360, - "StrSkill108": 4361, - "StrSkill109": 4362, - "StrSkill110": 4363, - "StrSkill111": 4364, - "StrSkill112": 4365, - "StrSkill113": 4366, - "StrSkill114": 4367, - "StrSkill115": 4368, - "StrSkill116": 4369, - "StrSkill117": 4370, - "StrSkill118": 4371, - "StrSkill119": 4372, - "skillname0": 4373, - "skillsd0": 4374, - "skillld0": 4375, - "skillan0": 4376, - "skillname1": 4377, - "skillsd1": 4378, - "skillld1": 4379, - "skillan1": 4380, - "skillname2": 4381, - "skillsd2": 4382, - "skillld2": 4383, - "skillan2": 4384, - "skillname3": 4385, - "skillsd3": 4386, - "skillld3": 4387, - "skillan3": 4388, - "skillname4": 4389, - "skillsd4": 4390, - "skillld4": 4391, - "skillan4": 4392, - "skillname5": 4393, - "skillsd5": 4394, - "skillld5": 4395, - "skillan5": 4396, - "skillname6": 4397, - "skillsd6": 4398, - "skillld6": 4399, - "skillan6": 4400, - "skillname7": 4401, - "skillsd7": 4402, - "skillld7": 4403, - "skillan7": 4404, - "skillname8": 4405, - "skillsd8": 4406, - "skillld8": 4407, - "skillan8": 4408, - "skillname9": 4409, - "skillsd9": 4410, - "skillld9": 4411, - "skillan9": 4412, - "skillname10": 4413, - "skillsd10": 4414, - "skillld10": 4415, - "skillan10": 4416, - "skillname11": 4417, - "skillsd11": 4418, - "skillld11": 4419, - "skillan11": 4420, - "skillname12": 4421, - "skillsd12": 4422, - "skillld12": 4423, - "skillan12": 4424, - "skillname13": 4425, - "skillsd13": 4426, - "skillld13": 4427, - "skillan13": 4428, - "skillname14": 4429, - "skillsd14": 4430, - "skillld14": 4431, - "skillan14": 4432, - "skillname15": 4433, - "skillsd15": 4434, - "skillld15": 4435, - "skillan15": 4436, - "skillname16": 4437, - "skillsd16": 4438, - "skillld16": 4439, - "skillan16": 4440, - "skillname17": 4441, - "skillsd17": 4442, - "skillld17": 4443, - "skillan17": 4444, - "skillname18": 4445, - "skillsd18": 4446, - "skillld18": 4447, - "skillan18": 4448, - "skillname19": 4449, - "skillsd19": 4450, - "skillld19": 4451, - "skillan19": 4452, - "skillname20": 4453, - "skillsd20": 4454, - "skillld20": 4455, - "skillan20": 4456, - "skillname21": 4457, - "skillsd21": 4458, - "skillld21": 4459, - "skillan21": 4460, - "skillname22": 4461, - "skillsd22": 4462, - "skillld22": 4463, - "skillan22": 4464, - "skillname23": 4465, - "skillsd23": 4466, - "skillld23": 4467, - "skillan23": 4468, - "skillname24": 4469, - "skillsd24": 4470, - "skillld24": 4471, - "skillan24": 4472, - "skillname25": 4473, - "skillsd25": 4474, - "skillld25": 4475, - "skillan25": 4476, - "skillname26": 4477, - "skillsd26": 4478, - "skillld26": 4479, - "skillan26": 4480, - "skillname27": 4481, - "skillsd27": 4482, - "skillld27": 4483, - "skillan27": 4484, - "skillname28": 4485, - "skillsd28": 4486, - "skillld28": 4487, - "skillan28": 4488, - "skillname29": 4489, - "skillsd29": 4490, - "skillld29": 4491, - "skillan29": 4492, - "skillname30": 4493, - "skillsd30": 4494, - "skillld30": 4495, - "skillan30": 4496, - "skillname31": 4497, - "skillsd31": 4498, - "skillld31": 4499, - "skillan31": 4500, - "skillname32": 4501, - "skillsd32": 4502, - "skillld32": 4503, - "skillan32": 4504, - "skillname33": 4505, - "skillsd33": 4506, - "skillld33": 4507, - "skillan33": 4508, - "skillname34": 4509, - "skillsd34": 4510, - "skillld34": 4511, - "skillan34": 4512, - "skillname35": 4513, - "skillsd35": 4514, - "skillld35": 4515, - "skillan35": 4516, - "skillname36": 4517, - "skillsd36": 4518, - "skillld36": 4519, - "skillan36": 4520, - "skillname37": 4521, - "skillsd37": 4522, - "skillld37": 4523, - "skillan37": 4524, - "skillname38": 4525, - "skillsd38": 4526, - "skillld38": 4527, - "skillan38": 4528, - "skillname39": 4529, - "skillsd39": 4530, - "skillld39": 4531, - "skillan39": 4532, - "skillname40": 4533, - "skillsd40": 4534, - "skillld40": 4535, - "skillan40": 4536, - "skillname41": 4537, - "skillsd41": 4538, - "skillld41": 4539, - "skillan41": 4540, - "skillname42": 4541, - "skillsd42": 4542, - "skillld42": 4543, - "skillan42": 4544, - "skillname43": 4545, - "skillsd43": 4546, - "skillld43": 4547, - "skillan43": 4548, - "skillname44": 4549, - "skillsd44": 4550, - "skillld44": 4551, - "skillan44": 4552, - "skillname45": 4553, - "skillsd45": 4554, - "skillld45": 4555, - "skillan45": 4556, - "skillname46": 4557, - "skillsd46": 4558, - "skillld46": 4559, - "skillan46": 4560, - "skillname47": 4561, - "skillsd47": 4562, - "skillld47": 4563, - "skillan47": 4564, - "skillname48": 4565, - "skillsd48": 4566, - "skillld48": 4567, - "skillan48": 4568, - "skillname49": 4569, - "skillsd49": 4570, - "skillld49": 4571, - "skillan49": 4572, - "skillname50": 4573, - "skillsd50": 4574, - "skillld50": 4575, - "skillan50": 4576, - "skillname51": 4577, - "skillsd51": 4578, - "skillld51": 4579, - "skillan51": 4580, - "skillname52": 4581, - "skillsd52": 4582, - "skillld52": 4583, - "skillan52": 4584, - "skillname53": 4585, - "skillsd53": 4586, - "skillld53": 4587, - "skillan53": 4588, - "skillname54": 4589, - "skillsd54": 4590, - "skillld54": 4591, - "skillan54": 4592, - "skillname55": 4593, - "skillsd55": 4594, - "skillld55": 4595, - "skillan55": 4596, - "skillname56": 4597, - "skillsd56": 4598, - "skillld56": 4599, - "skillan56": 4600, - "skillname57": 4601, - "skillsd57": 4602, - "skillld57": 4603, - "skillan57": 4604, - "skillname58": 4605, - "skillsd58": 4606, - "skillld58": 4607, - "skillan58": 4608, - "skillname59": 4609, - "skillsd59": 4610, - "skillld59": 4611, - "skillan59": 4612, - "skillname60": 4613, - "skillsd60": 4614, - "skillld60": 4615, - "skillan60": 4616, - "skillsname61": 4617, - "skillsd61": 4618, - "skillld61": 4619, - "skillan61": 4620, - "skillname62": 4621, - "skillsd62": 4622, - "skillld62": 4623, - "skillan62": 4624, - "skillname63": 4625, - "skillsd63": 4626, - "skillld63": 4627, - "skillan63": 4628, - "skillname64": 4629, - "skillsd64": 4630, - "skillld64": 4631, - "skillan64": 4632, - "skillname65": 4633, - "skillsd65": 4634, - "skillld65": 4635, - "skillan65": 4636, - "skillname66": 4637, - "skillsd66": 4638, - "skillld66": 4639, - "skillan66": 4640, - "skillname67": 4641, - "skillsd67": 4642, - "skillld67": 4643, - "skillan67": 4644, - "skillname68": 4645, - "skillsd68": 4646, - "skillld68": 4647, - "skillan68": 4648, - "skillname69": 4649, - "skillsd69": 4650, - "skillld69": 4651, - "skillan69": 4652, - "skillname70": 4653, - "skillsd70": 4654, - "skillld70": 4655, - "skillan70": 4656, - "skillname71": 4657, - "skillsd71": 4658, - "skillld71": 4659, - "skillan71": 4660, - "skillname72": 4661, - "skillsd72": 4662, - "skillld72": 4663, - "skillan72": 4664, - "skillname73": 4665, - "skillsd73": 4666, - "skillld73": 4667, - "skillan73": 4668, - "skillname74": 4669, - "skillsd74": 4670, - "skillld74": 4671, - "skillan74": 4672, - "skillname75": 4673, - "skillsd75": 4674, - "skillld75": 4675, - "skillan75": 4676, - "skillname76": 4677, - "skillsd76": 4678, - "skillld76": 4679, - "skillan76": 4680, - "skillname77": 4681, - "skillsd77": 4682, - "skillld77": 4683, - "skillan77": 4684, - "skillname78": 4685, - "skillsd78": 4686, - "skillld78": 4687, - "skillan78": 4688, - "skillname79": 4689, - "skillsd79": 4690, - "skillld79": 4691, - "skillan79": 4692, - "skillname80": 4693, - "skillsd80": 4694, - "skillld80": 4695, - "skillan80": 4696, - "skillname81": 4697, - "skillsd81": 4698, - "skillld81": 4699, - "skillan81": 4700, - "skillname82": 4701, - "skillsd82": 4702, - "skillld82": 4703, - "skillan82": 4704, - "skillname83": 4705, - "skillsd83": 4706, - "skillld83": 4707, - "skillan83": 4708, - "skillname84": 4709, - "skillsd84": 4710, - "skillld84": 4711, - "skillan84": 4712, - "skillname85": 4713, - "skillsd85": 4714, - "skillld85": 4715, - "skillan85": 4716, - "skillname86": 4717, - "skillsd86": 4718, - "skillld86": 4719, - "skillan86": 4720, - "skillname87": 4721, - "skillsd87": 4722, - "skillld87": 4723, - "skillan87": 4724, - "skillname88": 4725, - "skillsd88": 4726, - "skillld88": 4727, - "skillan88": 4728, - "skillname89": 4729, - "skillsd89": 4730, - "skillld89": 4731, - "skillan89": 4732, - "skillname90": 4733, - "skillsd90": 4734, - "skillld90": 4735, - "skillan90": 4736, - "skillname91": 4737, - "skillsd91": 4738, - "skillld91": 4739, - "skillan91": 4740, - "skillname92": 4741, - "skillsd92": 4742, - "skillld92": 4743, - "skillan92": 4744, - "skillname93": 4745, - "skillsd93": 4746, - "skillld93": 4747, - "skillan93": 4748, - "skillname94": 4749, - "skillsd94": 4750, - "skillld94": 4751, - "skillan94": 4752, - "skillname95": 4753, - "skillsd95": 4754, - "skillld95": 4755, - "skillan95": 4756, - "skillname96": 4757, - "skillsd96": 4758, - "skillld96": 4759, - "skillan96": 4760, - "skillname97": 4761, - "skillsd97": 4762, - "skillld97": 4763, - "skillan97": 4764, - "skillname98": 4765, - "skillsd98": 4766, - "skillld98": 4767, - "skillan98": 4768, - "skillname99": 4769, - "skillsd99": 4770, - "skillld99": 4771, - "skillan99": 4772, - "skillname100": 4773, - "skillsd100": 4774, - "skillld100": 4775, - "skillan100": 4776, - "skillname101": 4777, - "skillsd101": 4778, - "skillld101": 4779, - "skillan101": 4780, - "skillname102": 4781, - "skillsd102": 4782, - "skillld102": 4783, - "skillan102": 4784, - "skillname103": 4785, - "skillsd103": 4786, - "skillld103": 4787, - "skillan103": 4788, - "skillname104": 4789, - "skillsd104": 4790, - "skillld104": 4791, - "skillan104": 4792, - "skillname105": 4793, - "skillsd105": 4794, - "skillld105": 4795, - "skillan105": 4796, - "skillname106": 4797, - "skillsd106": 4798, - "skillld106": 4799, - "skillan106": 4800, - "skillname107": 4801, - "skillsd107": 4802, - "skillld107": 4803, - "skillan107": 4804, - "skillname108": 4805, - "skillsd108": 4806, - "skillld108": 4807, - "skillan108": 4808, - "skillname109": 4809, - "skillsd109": 4810, - "skillld109": 4811, - "skillan109": 4812, - "skillname110": 4813, - "skillsd110": 4814, - "skillld110": 4815, - "skillan110": 4816, - "skillname111": 4817, - "skillsd111": 4818, - "skillld111": 4819, - "skillan111": 4820, - "skillname112": 4821, - "skillsd112": 4822, - "skillld112": 4823, - "skillan112": 4824, - "skillname113": 4825, - "skillsd113": 4826, - "skillld113": 4827, - "skillan113": 4828, - "skillname114": 4829, - "skillsd114": 4830, - "skillld114": 4831, - "skillan114": 4832, - "skillname115": 4833, - "skillsd115": 4834, - "skillld115": 4835, - "skillan115": 4836, - "skillname116": 4837, - "skillsd116": 4838, - "skillld116": 4839, - "skillan116": 4840, - "skillname117": 4841, - "skillsd117": 4842, - "skillld117": 4843, - "skillan117": 4844, - "skillname118": 4845, - "skillsd118": 4846, - "skillld118": 4847, - "skillan118": 4848, - "skillname119": 4849, - "skillsd119": 4850, - "skillld119": 4851, - "skillan119": 4852, - "skillname120": 4853, - "skillsd120": 4854, - "skillld120": 4855, - "skillan120": 4856, - "skillname121": 4857, - "skillsd121": 4858, - "skillld121": 4859, - "skillan121": 4860, - "skillname122": 4861, - "skillsd122": 4862, - "skillld122": 4863, - "skillan122": 4864, - "skillname123": 4865, - "skillsd123": 4866, - "skillld123": 4867, - "skillan123": 4868, - "skillname124": 4869, - "skillsd124": 4870, - "skillld124": 4871, - "skillan124": 4872, - "skillname125": 4873, - "skillsd125": 4874, - "skillld125": 4875, - "skillan125": 4876, - "skillname126": 4877, - "skillsd126": 4878, - "skillld126": 4879, - "skillan126": 4880, - "skillname127": 4881, - "skillsd127": 4882, - "skillld127": 4883, - "skillan127": 4884, - "skillname128": 4885, - "skillsd128": 4886, - "skillld128": 4887, - "skillan128": 4888, - "skillname129": 4889, - "skillsd129": 4890, - "skillld129": 4891, - "skillan129": 4892, - "skillname130": 4893, - "skillsd130": 4894, - "skillld130": 4895, - "skillan130": 4896, - "skillname131": 4897, - "skillsd131": 4898, - "skillld131": 4899, - "skillan131": 4900, - "skillname132": 4901, - "skillsd132": 4902, - "skillld132": 4903, - "skillan132": 4904, - "skillname133": 4905, - "skillsd133": 4906, - "skillld133": 4907, - "skillan133": 4908, - "skillname134": 4909, - "skillsd134": 4910, - "skillld134": 4911, - "skillan134": 4912, - "skillname135": 4913, - "skillsd135": 4914, - "skillld135": 4915, - "skillan135": 4916, - "skillname136": 4917, - "skillsd136": 4918, - "skillld136": 4919, - "skillan136": 4920, - "skillname137": 4921, - "skillsd137": 4922, - "skillld137": 4923, - "skillan137": 4924, - "skillname138": 4925, - "skillsd138": 4926, - "skillld138": 4927, - "skillan138": 4928, - "skillname139": 4929, - "skillsd139": 4930, - "skillld139": 4931, - "skillan139": 4932, - "skillname140": 4933, - "skillsd140": 4934, - "skillld140": 4935, - "skillan140": 4936, - "skillname141": 4937, - "skillsd141": 4938, - "skillld141": 4939, - "skillan141": 4940, - "skillname142": 4941, - "skillsd142": 4942, - "skillld142": 4943, - "skillan142": 4944, - "skillname143": 4945, - "skillsd143": 4946, - "skillld143": 4947, - "skillan143": 4948, - "skillname144": 4949, - "skillsd144": 4950, - "skillld144": 4951, - "skillan144": 4952, - "skillname145": 4953, - "skillsd145": 4954, - "skillld145": 4955, - "skillan145": 4956, - "skillname146": 4957, - "skillsd146": 4958, - "skillld146": 4959, - "skillan146": 4960, - "skillname147": 4961, - "skillsd147": 4962, - "skillld147": 4963, - "skillan147": 4964, - "skillname148": 4965, - "skillsd148": 4966, - "skillld148": 4967, - "skillan148": 4968, - "skillname149": 4969, - "skillsd149": 4970, - "skillld149": 4971, - "skillan149": 4972, - "skillname150": 4973, - "skillsd150": 4974, - "skillld150": 4975, - "skillan150": 4976, - "skillname151": 4977, - "skillsd151": 4978, - "skillld151": 4979, - "skillan151": 4980, - "skillname152": 4981, - "skillsd152": 4982, - "skillld152": 4983, - "skillan152": 4984, - "skillname153": 4985, - "skillsd153": 4986, - "skillld153": 4987, - "skillan153": 4988, - "skillname154": 4989, - "skillsd154": 4990, - "skillld154": 4991, - "skillan154": 4992, - "skillname155": 4993, - "skillsd155": 4994, - "skillld155": 4995, - "skillan155": 4996, - "skillname217": 4997, - "skillsd217": 4998, - "skillld217": 4999, - "skillan217": 5000, - "skillname218": 5001, - "skillsd218": 5002, - "skillld218": 5003, - "skillan218": 5004, - "skillname219": 5005, - "skillsd219": 5006, - "skillld219": 5007, - "skillan219": 5008, - "skillname220": 5009, - "skillsd220": 5010, - "skillld220": 5011, - "skillan220": 5012, - "strMephistoDoorLocked": 5013, - "strTitleFeminine": 5014, - "strTitleMasculine": 5015, - "strChatHardcore": 5016, - "strChatLevel": 5017, - "Tristram": 5018, - "Catacombs Level 4": 5019, - "Catacombs Level 3": 5020, - "Catacombs Level 2": 5021, - "Catacombs Level 1": 5022, - "Cathedral": 5023, - "Inner Cloister": 5024, - "Jail Level 3": 5025, - "Jail Level 2": 5026, - "Jail Level 1": 5027, - "Barracks": 5028, - "Outer Cloister": 5029, - "Monastery Gate": 5030, - "Tower Cellar Level 5": 5031, - "Tower Cellar Level 4": 5032, - "Tower Cellar Level 3": 5033, - "Tower Cellar Level 2": 5034, - "Tower Cellar Level 1": 5035, - "Forgotten Tower": 5036, - "Mausoleum": 5037, - "Crypt": 5038, - "Burial Grounds": 5039, - "Pit Level 2": 5040, - "Hole Level 2": 5041, - "Underground Passage Level 2": 5042, - "Cave Level 2": 5043, - "Pit Level 1": 5044, - "Hole Level 1": 5045, - "Underground Passage Level 1": 5046, - "Cave Level 1": 5047, - "Den of Evil": 5048, - "Tamoe Highland": 5049, - "Black Marsh": 5050, - "Dark Wood": 5051, - "Stony Field": 5052, - "Cold Plains": 5053, - "Blood Moor": 5054, - "Rogue Encampment": 5055, - "To Tristram": 5056, - "To The Catacombs Level 4": 5057, - "To The Catacombs Level 3": 5058, - "To The Catacombs Level 2": 5059, - "To The Catacombs Level 1": 5060, - "To The Cathedral": 5061, - "To The Inner Cloister": 5062, - "To The Jail Level 3": 5063, - "To The Jail Level 2": 5064, - "To The Jail Level 1": 5065, - "To The Barracks": 5066, - "To The Outer Cloister": 5067, - "To The Monastery Gate": 5068, - "To The Tower Cellar Level 5": 5069, - "To The Tower Cellar Level 4": 5070, - "To The Tower Cellar Level 3": 5071, - "To The Tower Cellar Level 2": 5072, - "To The Tower Cellar Level 1": 5073, - "To The Forgotten Tower": 5074, - "To The Mausoleum": 5075, - "To The Crypt": 5076, - "To The Burial Grounds": 5077, - "To The Pit Level 2": 5078, - "To The Hole Level 2": 5079, - "To Underground Passage Level 2": 5080, - "To The Cave Level 2": 5081, - "To The Pit Level 1": 5082, - "To The Hole Level 1": 5083, - "To Underground Passage Level 1": 5084, - "To The Cave Level 1": 5085, - "To The Den of Evil": 5086, - "To The Tamoe Highland": 5087, - "To The Black Marsh": 5088, - "To The Dark Wood": 5089, - "To The Stony Field": 5090, - "To The Cold Plains": 5091, - "To The Blood Moor": 5092, - "To The Rogue Encampment": 5093, - "Deathmessage": 5094, - "Deathmessnight": 5095, - "Harddeathmessage": 5096, - "LordofTerrordied": 5097, - "Killdiablo1": 5098, - "KillDiablo2": 5099, - "KillDiablo3": 5100, - "x": 22741, - "X": 22746, - "Gem Activated": 5334, - "Gem Deactivated": 5335, - "Perfect Gem Activated": 5336, - "dummy": 5382, - "Dummy": 5383, - "not used": 5384, - "unused": 5385, - "Not used": 5386, - "convertsto": 5387, - "strNotInBeta": 5388, - "strLevelLoadFailed": 5389, - "Endthispuppy": 5390, - "A4Q2ExpansionSuccessTyrael": 20000, - "A4Q2ExpansionSuccessCain": 20001, - "AncientsAct5IntroGossip1": 20002, - "CainAct5IntroGossip1": 20003, - "CainAct5Gossip1": 20004, - "CainAct5Gossip2": 20005, - "CainAct5Gossip3": 20006, - "CainAct5Gossip4": 20007, - "CainAct5Gossip5": 20008, - "CainAct5Gossip6": 20009, - "CainAct5Gossip7": 20010, - "CainAct5Gossip8": 20011, - "CainAct5Gossip9": 20012, - "CainAct5Gossip10": 20013, - "AnyaAct5IntroGossip1": 20014, - "AnyaGossip1": 20015, - "AnyaGossip2": 20016, - "AnyaGossip3": 20017, - "AnyaGossip4": 20018, - "AnyaGossip5": 20019, - "AnyaGossip6": 20020, - "AnyaGossip7": 20021, - "AnyaGossip8": 20022, - "AnyaGossip9": 20023, - "AnyaGossip10": 20024, - "LarzukAct5IntroGossip1": 20025, - "LarzukAct5IntroAmaGossip1": 20026, - "LarzukGossip1": 20027, - "LarzukGossip2": 20028, - "LarzukGossip3": 20029, - "LarzukGossip4": 20030, - "LarzukGossip5": 20031, - "LarzukGossip6": 20032, - "LarzukGossip7": 20033, - "LarzukGossip8": 20034, - "LarzukGossip9": 20035, - "LarzukGossip10": 20036, - "MalahAct5IntroGossip1": 20037, - "MalahAct5IntroSorGossip1": 20038, - "MalahAct5IntroBarGossip1": 20039, - "MalahGossip1": 20040, - "MalahGossip2": 20041, - "MalahGossip3": 20042, - "MalahGossip4": 20043, - "MalahGossip5": 20044, - "MalahGossip6": 20045, - "MalahGossip7": 20046, - "MalahGossip8": 20047, - "MalahGossip9": 20048, - "MalahGossip10": 20049, - "MalahGossip11": 20050, - "MalahGossip12": 20051, - "MalahGossip13": 20052, - "NihlathakAct5IntroGossip1": 20053, - "NihlathakAct5IntroAssGossip1": 20054, - "NihlathakAct5IntroNecGossip1": 20055, - "NihlathakGossip1": 20056, - "NihlathakGossip2": 20057, - "NihlathakGossip3": 20058, - "NihlathakGossip4": 20059, - "NihlathakGossip5": 20060, - "NihlathakGossip6": 20061, - "NihlathakGossip7": 20062, - "NihlathakGossip8": 20063, - "NihlathakGossip9": 20064, - "QualKehkAct5IntroGossip1": 20065, - "QualKehkAct5IntroPalGossip1": 20066, - "QualKehkAct5IntroDruGossip1": 20067, - "QualKehkGossip1": 20068, - "QualKehkGossip2": 20069, - "QualKehkGossip3": 20070, - "QualKehkGossip4": 20071, - "QualKehkGossip5": 20072, - "QualKehkGossip6": 20073, - "QualKehkGossip7": 20074, - "QualKehkGossip8": 20075, - "QualKehkGossip9": 20076, - "A5Q1InitLarzuk": 20077, - "A5Q1AfterInitLarzuk": 20078, - "A5Q1AfterInitCain": 20079, - "A5Q1AfterInitAnya": 20080, - "A5Q1AfterInitMalah": 20081, - "A5Q1AfterInitNihlathak": 20082, - "A5Q1AfterInitQualKehk": 20083, - "A5Q1EarlyReturnLarzuk": 20084, - "A5Q1EarlyReturnCain": 20085, - "A5Q1EarlyReturnAnya": 20086, - "A5Q1EarlyReturnMalah": 20087, - "A5Q1EarlyReturnNihlathak": 20088, - "A5Q1EarlyReturnQualKehk": 20089, - "A5Q1SuccessfulLarzuk": 20090, - "A5Q1SuccessfulCain": 20091, - "A5Q1SuccessfulAnya": 20092, - "A5Q1SuccessfulMalah": 20093, - "A5Q1SuccessfulNihlathak": 20094, - "A5Q1SuccessfulQualKehk": 20095, - "A5Q2InitQualKehk": 20096, - "A5Q2AfterInitQualKehk": 20097, - "A5Q2AfterInitCain": 20098, - "A5Q2AfterInitAnya": 20099, - "A5Q2AfterInitLarzuk": 20100, - "A5Q2AfterInitMalah": 20101, - "A5Q2AfterInitNihlathak": 20102, - "A5Q2EarlyReturnQualKehk": 20103, - "A5Q2EarlyReturnQualKehkMan": 20104, - "A5Q2EarlyReturnCain": 20105, - "A5Q2EarlyReturnAnya": 20106, - "A5Q2EarlyReturnLarzuk": 20107, - "A5Q2EarlyReturnMalah": 20108, - "A5Q2EarlyReturnNihlathak": 20109, - "A5Q2SuccessfulQualKehk": 20110, - "A5Q2SuccessfulCain": 20111, - "A5Q2SuccessfulAnya": 20112, - "A5Q2SuccessfulLarzuk": 20113, - "A5Q2SuccessfulMalah": 20114, - "A5Q2SuccessfulNihlathak": 20115, - "A5Q3InitMalah": 20116, - "A5Q3AfterInitMalah": 20117, - "A5Q3AfterInitCain": 20118, - "A5Q3AfterInitLarzuk": 20119, - "A5Q3AfterInitNihlathak": 20120, - "A5Q3AfterInitQualKehk": 20121, - "A5Q3EarlyReturnMalah": 20122, - "A5Q3EarlyReturnCain": 20123, - "A5Q3EarlyReturnLarzuk": 20124, - "A5Q3EarlyReturnNihlathak": 20125, - "A5Q3EarlyReturnQualKehk": 20126, - "A5Q3FoundAnyaMalah": 20127, - "A5Q3FoundAnyaCain": 20128, - "A5Q3FoundAnyaLarzuk": 20129, - "A5Q3FoundAnyaQualKehk": 20130, - "A5Q3FoundAnyaAnya": 20131, - "A5Q3SuccessfulMalah": 20132, - "A5Q3SuccessfulCain": 20133, - "A5Q3SuccessfulLarzuk": 20134, - "A5Q3SuccessfulQualKehk": 20135, - "A5Q3SuccessfulAnya": 20136, - "A5Q4InitAnya": 20137, - "A5Q4AfterInitAnya": 20138, - "A5Q4AfterInitCain": 20139, - "A5Q4AfterInitMalah": 20140, - "A5Q4AfterInitLarzuk": 20141, - "A5Q4AfterInitQualKehk": 20142, - "A5Q4EarlyReturnAnya": 20143, - "A5Q4EarlyReturnCain": 20144, - "A5Q4EarlyReturnLarzuk": 20145, - "A5Q4EarlyReturnMalah": 20146, - "A5Q4EarlyReturnQualKehk": 20147, - "A5Q4SuccessfulAnya": 20148, - "A5Q4SuccessfulCain": 20149, - "A5Q4SuccessfulLarzuk": 20150, - "A5Q4SuccessfulMalah": 20151, - "A5Q4SuccessfulQualKehk": 20152, - "A5Q5InitQualKehk": 20153, - "A5Q5AfterInitQualKehk": 20154, - "A5Q5AfterInitCain": 20155, - "A5Q5AfterInitAnya": 20156, - "A5Q5AfterInitLarzuk": 20157, - "A5Q5AfterInitMalah": 20158, - "A5Q5EarlyReturnQualKehk": 20159, - "A5Q5EarlyReturnCain": 20160, - "A5Q5EarlyReturnAnya": 20161, - "A5Q5EarlyReturnLarzuk": 20162, - "A5Q5EarlyReturnMalah": 20163, - "A5Q5SuccessfulQualKehk": 20164, - "A5Q5SuccessfulCain": 20165, - "A5Q5SuccessfulAnya": 20166, - "A5Q5SuccessfulLarzuk": 20167, - "A5Q5SuccessfulMalah": 20168, - "A5Q6InitAncients": 20169, - "A5Q6EarlyReturnCain": 20170, - "A5Q6EarlyReturnLarzuk": 20171, - "A5Q6EarlyReturnMalah": 20172, - "A5Q6EarlyReturnAnya": 20173, - "A5Q6EarlyReturnQualKehk": 20174, - "A5Q6SuccessfulTyrael": 20175, - "A5Q6SuccessfulAnya": 20176, - "A5Q6SuccessfulCain": 20177, - "A5Q6SuccessfulLarzuk": 20178, - "A5Q6SuccessfulMalah": 20179, - "A5Q6SuccessfulQualKehk": 20180, - "ktr": 20181, - "wrb": 20182, - "ces": 20183, - "clw": 20184, - "btl": 20185, - "skr": 20186, - "9ar": 20187, - "9wb": 20188, - "9xf": 20189, - "9cs": 20190, - "9lw": 20191, - "9tw": 20192, - "9qr": 20193, - "7ar": 20194, - "7wb": 20195, - "7xf": 20196, - "7cs": 20197, - "7lw": 20198, - "7tw": 20199, - "7qr": 20200, - "7ha": 20201, - "7ax": 20202, - "72a": 20203, - "7mp": 20204, - "7wa": 20205, - "7la": 20206, - "7ba": 20207, - "7bt": 20208, - "7ga": 20209, - "7gi": 20210, - "7wn": 20211, - "7yw": 20212, - "7bw": 20213, - "7gw": 20214, - "7cl": 20215, - "7sc": 20216, - "7qs": 20217, - "7ws": 20218, - "7sp": 20219, - "7ma": 20220, - "7mt": 20221, - "7fl": 20222, - "7wh": 20223, - "7m7": 20224, - "7gm": 20225, - "7ss": 20226, - "7sm": 20227, - "7sb": 20228, - "7fc": 20229, - "7cr": 20230, - "7bs": 20231, - "7ls": 20232, - "7wd": 20233, - "72h": 20234, - "7cm": 20235, - "7gs": 20236, - "7b7": 20237, - "7fb": 20238, - "7gd": 20239, - "7dg": 20240, - "7di": 20241, - "7kr": 20242, - "7bl": 20243, - "7tk": 20244, - "7ta": 20245, - "7bk": 20246, - "7b8": 20247, - "7ja": 20248, - "7pi": 20249, - "7s7": 20250, - "7gl": 20251, - "7ts": 20252, - "7sr": 20253, - "7tr": 20254, - "7br": 20255, - "7st": 20256, - "7p7": 20257, - "7o7": 20258, - "7vo": 20259, - "7s8": 20260, - "7pa": 20261, - "7h7": 20262, - "7wc": 20263, - "6ss": 20264, - "6ls": 20265, - "6cs": 20266, - "6bs": 20267, - "6ws": 20268, - "6sb": 20269, - "6hb": 20270, - "6lb": 20271, - "6cb": 20272, - "6s7": 20273, - "6l7": 20274, - "6sw": 20275, - "6lw": 20276, - "6lx": 20277, - "6mx": 20278, - "6hx": 20279, - "6rx": 20280, - "am1": 20292, - "am2": 20293, - "am3": 20294, - "am4": 20295, - "am5": 20296, - "ob6": 20297, - "ob7": 20298, - "ob8": 20299, - "ob9": 20300, - "oba": 20301, - "am6": 20302, - "am7": 20303, - "am8": 20304, - "am9": 20305, - "ama": 20306, - "obb": 20307, - "obc": 20308, - "obd": 20309, - "obe": 20310, - "obf": 20311, - "amb": 20312, - "amc": 20313, - "amd": 20314, - "ame": 20315, - "amf": 20316, - "ba1": 20322, - "ba2": 20323, - "ba3": 20324, - "ba4": 20325, - "ba5": 20326, - "pa1": 20327, - "pa2": 20328, - "pa3": 20329, - "pa4": 20330, - "pa5": 20331, - "ci0": 20337, - "ci1": 20338, - "ci2": 20339, - "ci3": 20340, - "uap": 20341, - "ukp": 20342, - "ulm": 20343, - "uhl": 20344, - "uhm": 20345, - "urn": 20346, - "usk": 20347, - "uui": 20348, - "uea": 20349, - "ula": 20350, - "utu": 20351, - "ung": 20352, - "ucl": 20353, - "uhn": 20354, - "urs": 20355, - "upl": 20356, - "ult": 20357, - "uld": 20358, - "uth": 20359, - "uul": 20360, - "uar": 20361, - "utp": 20362, - "uuc": 20363, - "uml": 20364, - "urg": 20365, - "uit": 20366, - "uow": 20367, - "uts": 20368, - "ulg": 20369, - "uvg": 20370, - "umg": 20371, - "utg": 20372, - "uhg": 20373, - "ulb": 20374, - "uvb": 20375, - "umb": 20376, - "utb": 20377, - "uhb": 20378, - "ulc": 20379, - "uvc": 20380, - "umc": 20381, - "utc": 20382, - "uhc": 20383, - "uh9": 20384, - "ush": 20385, - "upk": 20386, - "dr9": 20387, - "dr7": 20388, - "dr8": 20389, - "dr6": 20390, - "dra": 20391, - "ba6": 20392, - "ba7": 20393, - "ba8": 20394, - "ba9": 20395, - "baa": 20396, - "pa6": 20397, - "pa7": 20398, - "pa8": 20399, - "pa9": 20400, - "paa": 20401, - "ne6": 20402, - "ne7": 20403, - "ne8": 20404, - "ne9": 20405, - "nea": 20406, - "dre": 20407, - "drc": 20408, - "drd": 20409, - "drb": 20410, - "drf": 20411, - "bab": 20412, - "bac": 20413, - "bad": 20414, - "bae": 20415, - "baf": 20416, - "pab": 20417, - "pac": 20418, - "pae": 20419, - "paf": 20420, - "neb": 20421, - "nec": 20422, - "ned": 20423, - "nee": 20424, - "nef": 20425, - "jew": 20433, - "cm1": 20435, - "cm2": 20436, - "cm3": 20437, - "Charmdes": 20438, - "ice": 20439, - "r33": 20440, - "r32": 20441, - "r31": 20442, - "r30": 20443, - "r29": 20444, - "r28": 20445, - "r27": 20446, - "r26": 20447, - "r25": 20448, - "r24": 20449, - "r23": 20450, - "r22": 20451, - "r21": 20452, - "r20": 20453, - "r19": 20454, - "r18": 20455, - "r17": 20456, - "r16": 20457, - "r15": 20458, - "r14": 20459, - "r13": 20460, - "r12": 20461, - "r11": 20462, - "r10": 20463, - "r09": 20464, - "r08": 20465, - "r07": 20466, - "r06": 20467, - "r05": 20468, - "r04": 20469, - "r03": 20470, - "r02": 20471, - "r01": 20472, - "r33L": 20473, - "r32L": 20474, - "r31L": 20475, - "r30L": 20476, - "r29L": 20477, - "r28L": 20478, - "r27L": 20479, - "r26L": 20480, - "r25L": 20481, - "r24L": 20482, - "r23L": 20483, - "r22L": 20484, - "r21L": 20485, - "r20L": 20486, - "r19L": 20487, - "r18L": 20488, - "r17L": 20489, - "r16L": 20490, - "r15L": 20491, - "r14L": 20492, - "r13L": 20493, - "r12L": 20494, - "r11L": 20495, - "r10L": 20496, - "r09L": 20497, - "r08L": 20498, - "r07L": 20499, - "r06L": 20500, - "r05L": 20501, - "r04L": 20502, - "r03L": 20503, - "r02L": 20504, - "r01L": 20505, - "RuneQuote": 20506, - "Runeword1": 20507, - "Runeword2": 20508, - "Runeword3": 20509, - "Runeword4": 20510, - "Runeword5": 20511, - "Runeword6": 20512, - "Runeword7": 20513, - "Runeword8": 20514, - "Runeword9": 20515, - "Runeword10": 20516, - "Runeword11": 20517, - "Runeword12": 20518, - "Runeword13": 20519, - "Runeword14": 20520, - "Runeword15": 20521, - "Runeword16": 20522, - "Runeword17": 20523, - "Runeword18": 20524, - "Runeword19": 20525, - "Runeword20": 20526, - "Runeword21": 20527, - "Runeword22": 20528, - "Runeword23": 20529, - "Runeword24": 20530, - "Runeword25": 20531, - "Runeword26": 20532, - "Runeword27": 20533, - "Runeword28": 20534, - "Runeword29": 20535, - "Runeword30": 20536, - "Runeword31": 20537, - "Runeword32": 20538, - "Runeword33": 20539, - "Runeword34": 20540, - "Runeword35": 20541, - "Runeword36": 20542, - "Runeword37": 20543, - "Runeword38": 20544, - "Runeword39": 20545, - "Runeword40": 20546, - "Runeword41": 20547, - "Runeword42": 20548, - "Runeword43": 20549, - "Runeword44": 20550, - "Runeword45": 20551, - "Runeword46": 20552, - "Runeword47": 20553, - "Runeword48": 20554, - "Runeword49": 20555, - "Runeword50": 20556, - "Runeword51": 20557, - "Runeword52": 20558, - "Runeword53": 20559, - "Runeword54": 20560, - "Runeword55": 20561, - "Runeword56": 20562, - "Runeword57": 20563, - "Runeword58": 20564, - "Runeword59": 20565, - "Runeword60": 20566, - "Runeword61": 20567, - "Runeword62": 20568, - "Runeword63": 20569, - "Runeword64": 20570, - "Runeword65": 20571, - "Runeword66": 20572, - "Runeword67": 20573, - "Runeword68": 20574, - "Runeword69": 20575, - "Runeword70": 20576, - "Runeword71": 20577, - "Runeword72": 20578, - "Runeword73": 20579, - "Runeword74": 20580, - "Runeword75": 20581, - "Runeword76": 20582, - "Runeword77": 20583, - "Runeword78": 20584, - "Runeword79": 20585, - "Runeword81": 20586, - "Runeword82": 20587, - "Runeword83": 20588, - "Runeword84": 20589, - "Runeword85": 20590, - "Runeword86": 20591, - "Runeword87": 20592, - "Runeword88": 20593, - "Runeword89": 20594, - "Runeword90": 20595, - "Runeword91": 20596, - "Runeword92": 20597, - "Runeword93": 20598, - "Runeword94": 20599, - "Runeword95": 20600, - "Runeword96": 20601, - "Runeword97": 20602, - "Runeword98": 20603, - "Runeword99": 20604, - "Runeword100": 20605, - "Runeword101": 20606, - "Runeword102": 20607, - "Runeword103": 20608, - "Runeword104": 20609, - "Runeword105": 20610, - "Runeword106": 20611, - "Runeword107": 20612, - "Runeword108": 20613, - "Runeword109": 20614, - "Runeword110": 20615, - "Runeword111": 20616, - "Runeword112": 20617, - "Runeword113": 20618, - "Runeword114": 20619, - "Runeword115": 20620, - "Runeword116": 20621, - "Runeword117": 20622, - "Runeword118": 20623, - "Runeword119": 20624, - "Runeword120": 20625, - "Runeword121": 20626, - "Runeword122": 20627, - "Runeword123": 20628, - "Runeword124": 20629, - "Runeword125": 20630, - "Runeword126": 20631, - "Runeword127": 20632, - "Runeword128": 20633, - "Runeword129": 20634, - "Runeword130": 20635, - "Runeword131": 20636, - "Runeword132": 20637, - "Runeword133": 20638, - "Runeword134": 20639, - "Runeword135": 20640, - "Runeword136": 20641, - "Runeword137": 20642, - "Runeword138": 20643, - "Runeword139": 20644, - "Runeword140": 20645, - "Runeword141": 20646, - "Runeword142": 20647, - "Runeword143": 20648, - "Runeword144": 20649, - "Runeword145": 20650, - "Runeword146": 20651, - "Runeword147": 20652, - "Runeword148": 20653, - "Runeword149": 20654, - "Runeword150": 20655, - "Runeword151": 20656, - "Runeword152": 20657, - "Runeword153": 20658, - "Runeword154": 20659, - "Runeword155": 20660, - "Runeword156": 20661, - "Runeword157": 20662, - "Runeword158": 20663, - "Runeword159": 20664, - "Runeword160": 20665, - "Runeword161": 20666, - "Runeword162": 20667, - "Runeword163": 20668, - "Runeword164": 20669, - "Runeword165": 20670, - "Runeword166": 20671, - "Runeword167": 20672, - "Runeword168": 20673, - "Runeword169": 20674, - "Runeword170": 20675, - "spe": 20676, - "scz": 20677, - "sol": 20678, - "qll": 20679, - "fng": 20680, - "flg": 20681, - "tal": 20682, - "hrn": 20683, - "eyz": 20684, - "jaw": 20685, - "brz": 20686, - "hrt": 20687, - "Stout": 20688, - "Antimagic": 20689, - "Null": 20690, - "Godly": 20691, - "Ivory": 20692, - "Eburin": 20693, - "Blanched": 20694, - "Stalwart": 20695, - "Burly": 20696, - "Dense": 20697, - "Thin": 20698, - "Compact": 20699, - "Witch-hunter's": 20700, - "Magekiller's": 20701, - "Hierophant's": 20702, - "Shaman's": 20703, - "Pestilent": 20704, - "Toxic": 20705, - "Corosive": 20706, - "Envenomed": 20707, - "Septic": 20708, - "Shocking": 20709, - "Arcing": 20710, - "Buzzing": 20711, - "Static": 20712, - "Scorching": 20713, - "Flaming": 20714, - "Smoking": 20715, - "Smoldering": 20716, - "Ember": 20717, - "Hibernal": 20718, - "Boreal": 20719, - "Shivering": 20720, - "Snowflake": 20721, - "Mnemonic": 20722, - "Visionary": 20723, - "Eagleeye": 20724, - "Hawkeye": 20725, - "Falconeye": 20726, - "Sparroweye": 20727, - "Robineye": 20728, - "Paradox": 20729, - "Shouting": 20730, - "Yelling": 20731, - "Calling": 20732, - "Loud": 20733, - "Trump": 20734, - "Joker's": 20735, - "Jester's": 20736, - "Jack's": 20737, - "Knave's": 20738, - "Paleocene": 20739, - "Eocene": 20740, - "Oligocene": 20741, - "Miocene": 20742, - "Kenshi's": 20743, - "Sensei's": 20744, - "Shogukusha's": 20745, - "Psychic": 20746, - "Mentalist's": 20747, - "Cunning": 20748, - "Trickster's": 20749, - "Entrapping": 20750, - "Gaea's": 20751, - "Terra's": 20752, - "Nature's": 20753, - "Communal": 20754, - "Feral": 20755, - "Spiritual": 20756, - "Keeper's": 20757, - "Caretaker's": 20758, - "Trainer's": 20759, - "Veteran's": 20760, - "Expert's": 20761, - "Furious": 20762, - "Raging": 20763, - "Echoing": 20764, - "Resonant": 20765, - "Sounding": 20766, - "Guardian's": 20767, - "Warder's": 20768, - "Preserver's": 20769, - "Marshal's": 20770, - "Commander's": 20771, - "Captain's": 20772, - "Rose Branded": 20773, - "Hawk Branded": 20774, - "Lion Branded": 20775, - "Golemlord's": 20776, - "Vodoun": 20777, - "Graverobber's": 20778, - "Venomous": 20779, - "Noxious": 20780, - "Fungal": 20781, - "Accursed": 20782, - "Blighting": 20783, - "Hexing": 20784, - "Glacial": 20785, - "Freezing": 20786, - "Chilling": 20787, - "Powered": 20788, - "Charged": 20789, - "Sparking": 20790, - "Volcanic": 20791, - "Blazing": 20792, - "Burning": 20793, - "Lancer's": 20794, - "Spearmaiden's": 20795, - "Harpoonist's": 20796, - "Athlete's": 20797, - "Gymnast's": 20798, - "Acrobat's": 20799, - "Bowyer's": 20800, - "Diamond": 20801, - "Celestial": 20802, - "Elysian": 20803, - "Astral": 20804, - "Unearthly": 20805, - "Arcadian": 20806, - "Jeweler's": 20807, - "Artificer's": 20808, - "Mechanist's": 20809, - "Aureolin": 20810, - "Victorious": 20811, - "Ambergris": 20812, - "Camphor": 20813, - "Lapis Lazuli": 20814, - "Chromatic": 20815, - "Scintillating": 20816, - "Turquoise": 20817, - "Jacinth": 20818, - "Zircon": 20819, - "Bahamut's": 20820, - "Great Wyrm's": 20821, - "Felicitous": 20822, - "Lucky": 20823, - "Wailing": 20824, - "Screaming": 20825, - "Grandmaster's": 20826, - "Master's": 20827, - "Argent": 20828, - "Tin": 20829, - "Nickel": 20830, - "Maroon": 20831, - "Chestnut": 20832, - "Vigorous": 20833, - "Brown": 20834, - "Dun": 20835, - "Realgar": 20836, - "Rusty": 20837, - "Cinnabar": 20838, - "Vermillion": 20839, - "Carmine": 20840, - "Carbuncle": 20841, - "Serrated": 20842, - "Scarlet": 20843, - "Bloody": 20844, - "Sanguinary": 20845, - "Pearl": 20846, - "Divine": 20847, - "Hallowed": 20848, - "Sacred": 20849, - "Pure": 20850, - "Consecrated": 20851, - "Assamic": 20852, - "Frantic": 20853, - "Hellatial": 20854, - "Quixotic": 20855, - "Smiting": 20856, - "Steller": 20857, - "Stinging": 20858, - "Singing": 20859, - "Timeless": 20860, - "Original": 20861, - "Corporal": 20862, - "Lawful": 20863, - "Chaotic": 20864, - "Fierce": 20865, - "Ferocious": 20866, - "Perpetual": 20867, - "Continuous": 20868, - "Laden": 20869, - "Pernicious": 20870, - "Harmful": 20871, - "Evil": 20872, - "Insidious": 20873, - "Malicious": 20874, - "Spiteful": 20875, - "Precocious": 20876, - "Majestic": 20877, - "Sanguine": 20878, - "Monumental": 20879, - "Irresistible": 20880, - "Festering": 20881, - "Musty": 20882, - "Dusty": 20883, - "Decaying": 20884, - "Rotting": 20885, - "Infectious": 20886, - "Foggy": 20887, - "Cloudy": 20888, - "Hazy": 20889, - "Punishing": 20890, - "Obsidian": 20891, - "Royal": 20892, - "Frigid": 20893, - "Moldy": 20894, - "Gaudy": 20895, - "Impecable": 20896, - "Soulless": 20897, - "Heated": 20898, - "Lasting": 20899, - "Scorched": 20900, - "Marred": 20901, - "Lilac": 20902, - "Rose": 20903, - "Shimmering": 20904, - "Wicked": 20906, - "Strange": 20907, - "Repulsive": 20908, - "Reclusive": 20909, - "Rude": 20911, - "Hermetic": 20912, - "Rainbow": 20913, - "Colorful": 20914, - "Stinky": 20915, - "Gritty": 20916, - "of Warming": 20917, - "of Stoicism": 20918, - "of the Dynamo": 20919, - "of Grounding": 20920, - "of Insulation": 20921, - "of Resistance": 20922, - "of Faith": 20923, - "of Fire Quenching": 20924, - "of Amianthus": 20925, - "of Incombustibility": 20926, - "of Coolness": 20927, - "of Anima": 20928, - "of Life Everlasting": 20929, - "of Sunlight": 20930, - "of Frozen Orb": 20931, - "of Hydra Shield": 20932, - "of Chilling Armor": 20933, - "of Blizzard": 20934, - "of Energy Shield": 20935, - "of Thunder Storm": 20936, - "of Meteor": 20937, - "of Glacial Spike": 20938, - "of Teleport Shield": 20939, - "of Chain Lightning": 20940, - "of Enchant": 20941, - "of Fire Wall": 20942, - "of Shiver Armor": 20943, - "of Nova Shield": 20944, - "of Nova": 20945, - "of Fire Ball": 20946, - "of Blaze": 20947, - "of Ice Blast": 20948, - "of Frost Shield": 20949, - "of Telekinesis": 20950, - "of Static Field": 20951, - "of Frozen Armor": 20952, - "of Icebolt": 20953, - "of Charged Shield": 20954, - "of Firebolts": 20955, - "of the Elements": 20956, - "of the Cobra": 20957, - "of the Efreeti": 20958, - "of the Phoenix": 20959, - "of the Yeti": 20960, - "of Grace and Power": 20961, - "of Grace": 20962, - "of Power": 20963, - "of the Elephant": 20964, - "of Memory": 20965, - "of the Kraken1": 20966, - "of Propogation": 20967, - "of Replenishing": 20968, - "of Ages": 20969, - "of Fast Repair": 20970, - "of Self-Repair": 20971, - "of Acceleration": 20972, - "of Traveling": 20973, - "of Virility": 20974, - "of Atlus": 20975, - "of Freedom": 20976, - "of the Lamprey": 20977, - "of Hope": 20978, - "of Spirit": 20979, - "of Vita": 20980, - "of Substinence": 20981, - "of the Whale": 20982, - "of the Squid": 20983, - "of the Colossus1": 20984, - "of Knowledge": 20985, - "of Enlightenment": 20986, - "of Prosperity": 20987, - "of Good Luck": 20988, - "of Luck": 20989, - "of Avarice": 20990, - "of Honor": 20991, - "of Revivification": 20992, - "of Truth": 20993, - "of Daring": 20994, - "of Nirvana": 20995, - "of Envy": 20996, - "of Anthrax": 20997, - "of Bliss": 20998, - "of Joy": 20999, - "of Transcendence": 21000, - "of Wrath": 21001, - "of Ire": 21002, - "of Evisceration": 21003, - "of Butchery": 21004, - "of Ennui": 21005, - "of Storms": 21006, - "of Passion": 21007, - "of Incineration": 21008, - "of Frigidity": 21009, - "of Winter": 21010, - "of the Icicle": 21011, - "of Fervor": 21012, - "of Malice": 21013, - "of Swords": 21014, - "of Razors": 21015, - "of Desire": 21016, - "of the Sirocco": 21017, - "of the Dunes": 21018, - "of Thawing": 21019, - "Of the Choir": 21020, - "Of the Sniper": 21021, - "Of the Stiletto": 21022, - "Of Bile": 21023, - "Of Blitzen": 21024, - "Of Cremation": 21025, - "Of Darkness": 21026, - "Of Disease": 21027, - "Of Remorse": 21028, - "Of Terror": 21029, - "Of the Sky": 21030, - "Of Valhalla": 21031, - "Of Waste": 21032, - "Of Nobility": 21033, - "Of Karma": 21034, - "Of Grounding": 21035, - "Of the River": 21036, - "Of the Lake": 21037, - "Of the Ocean": 21038, - "Of the Bayou": 21039, - "Of the Stream": 21040, - "Of the Lady": 21041, - "Of the Maiden": 21042, - "Of the Virgin": 21043, - "Of the Hag": 21044, - "Of the Witch": 21045, - "Of Judgement": 21046, - "Of Illusion": 21047, - "Of Elusion": 21048, - "Of Combat": 21049, - "Of Attrition": 21050, - "Of Abrasion": 21051, - "Of Erosion": 21052, - "Of Searing": 21053, - "Of Stone": 21054, - "Of Stature": 21055, - "Of Fortication": 21056, - "Of Quickening": 21057, - "Of Dispatch": 21058, - "Of Daring": 21059, - "Of Dread": 21060, - "Of Suffering": 21061, - "Of Doom": 21062, - "Of Vengence": 21063, - "Of Redemption": 21064, - "Of Luck": 21065, - "Of the Avenger": 21066, - "Of the Specter": 21067, - "Of the Ghost": 21068, - "Of the Infantry": 21069, - "Of the Mosquito": 21070, - "Of the Gnat": 21071, - "Of the Fly": 21072, - "Of the Plague": 21073, - "Of Twilight": 21074, - "Of Dusk": 21075, - "Of Dawn": 21076, - "Of the Imbecile": 21077, - "Of the Idiot": 21078, - "Of the Retard": 21079, - "Of the Jujube": 21080, - "Of the Obscenity": 21081, - "Of Quota": 21082, - "Of the Maggot": 21083, - "Of Horror": 21084, - "Of Baddass": 21085, - "Of the Beast": 21086, - "Of Cruelty": 21087, - "Of Badness": 21088, - "Of the Horde": 21089, - "Of the Forest": 21090, - "Of the Lilly": 21091, - "Of the Grassy Gnoll": 21092, - "Of the Stars": 21093, - "Of the Moon": 21094, - "Of Love": 21095, - "Of the Unicorn": 21096, - "Of the Walrus": 21097, - "Of the Earth": 21098, - "Of Vines": 21099, - "Of Honor": 21100, - "Of Tribute": 21101, - "Of Credit": 21102, - "Of Admiration": 21103, - "Of Sweetness": 21104, - "Of Beauty": 21105, - "Of Pilfering": 21106, - "of Damage Amplification": 21107, - "of Hurricane": 21108, - "of Armageddon": 21109, - "of Tornado": 21110, - "of Volcano": 21111, - "of Twister": 21112, - "of Cyclone Armor": 21113, - "of Eruption": 21114, - "of Molten Boulders": 21115, - "of Firestorms": 21116, - "of Battle Command": 21117, - "of War Cry": 21118, - "of Grim Ward": 21119, - "of Battle Orders": 21120, - "of Battle Cry": 21121, - "of Concentration": 21122, - "of Item Finding": 21123, - "of Stunning": 21124, - "of Shouting": 21125, - "of Taunting": 21126, - "of Potion Finding": 21127, - "of Howling": 21128, - "of Fist of the Heavens": 21129, - "of Holy Shield": 21130, - "of Conversion": 21131, - "of Blessed Hammers": 21132, - "of Vengeance": 21133, - "of Charging": 21134, - "of Zeal": 21135, - "of Holy Bolts": 21136, - "of Sacrifice": 21137, - "of Fire Golem Summoning": 21138, - "of Bone Spirits": 21139, - "of Poison Novas": 21140, - "of Lower Resistance": 21141, - "of Iron Golem Creation": 21142, - "of Bone Imprisonment": 21143, - "of Decrepification": 21144, - "of Attraction": 21145, - "of Blood Golem Summoning": 21146, - "of Bone Spears": 21147, - "of Poison Explosion": 21148, - "of Life Tap": 21149, - "of Confusion": 21150, - "of Raise Skeletal Mages": 21151, - "of Bone Walls": 21152, - "of Terror": 21153, - "of Iron Maiden": 21154, - "of Clay Golem Summoning": 21155, - "of Corpse Explosions": 21156, - "of Poison Dagger": 21157, - "of Weaken": 21158, - "of Dim Vision": 21159, - "of Raise Skeletons": 21160, - "of Bone Armor": 21161, - "of Teeth": 21162, - "of Amplify Damage": 21163, - "of Frozen Orbs": 21164, - "of Hydras": 21165, - "of Blizzards": 21166, - "of Meteors": 21167, - "of Glacial Spikes": 21168, - "of Teleportation": 21169, - "of Enchantment": 21170, - "of Fire Walls": 21171, - "of Novas": 21172, - "of Fire Balls": 21173, - "of Blazing": 21174, - "of Ice Blasts": 21175, - "of Frost Novas": 21176, - "of Ice Bolts": 21177, - "of Charged Bolts": 21178, - "of Fire Bolts": 21179, - "of Lightning Fury": 21180, - "of Lightning Spear": 21181, - "of Freezing Arrows": 21182, - "of Fending": 21183, - "of Immolating Arrows": 21184, - "of Plague Javelin": 21185, - "of Charged Spear": 21186, - "of Guided Arrows": 21187, - "of Ice Arrows": 21188, - "of Lightning Javelin": 21189, - "of Impaling Spear": 21190, - "of Slow Missiles": 21191, - "of Exploding Arrows": 21192, - "of Poison Javelin": 21193, - "of Power Spear": 21194, - "of Multiple Shot": 21195, - "of Cold Arrows": 21196, - "of Jabbing": 21197, - "of Inner Sight": 21198, - "of Fire Arrows": 21199, - "of Magic Arrows": 21200, - "Of self-repair": 21201, - "of Dawn": 21202, - "of Inertia": 21203, - "of Joyfulness": 21204, - "ModStre8a": 21205, - "ModStre8b": 21206, - "ModStre8c": 21207, - "ModStre8d": 21208, - "ModStre8e": 21209, - "ModStre8f": 21210, - "ModStre8g": 21211, - "ModStre8h": 21212, - "ModStre8i": 21213, - "ModStre8j": 21214, - "ModStre8k": 21215, - "ModStre8l": 21216, - "ModStre8m": 21217, - "ModStre8n": 21218, - "ModStre8o": 21219, - "ModStre8p": 21220, - "ModStre8q": 21221, - "ModStre8r": 21222, - "ModStre8s": 21223, - "ModStre8t": 21224, - "ModStre8u": 21225, - "ModStre8v": 21226, - "ModStre8w": 21227, - "ModStre8x": 21228, - "ModStre8y": 21229, - "ModStre8z": 21230, - "ModStre9a": 21231, - "ModStre9b": 21232, - "ModStre9c": 21233, - "ModStre9d": 21234, - "ModStre9e": 21235, - "ModStre9f": 21236, - "ModStre9g": 21237, - "ModStre9h": 21238, - "ModStre9i": 21239, - "ModStre9s": 21240, - "ModStre9t": 21241, - "ModStre9u": 21242, - "ModStre9v": 21243, - "ModStre9w": 21244, - "ModStre9x": 21245, - "ModStre9y": 21246, - "ModStre9z": 21247, - "ModStre10a": 21248, - "ModStre10b": 21249, - "ModStre10c": 21250, - "ModStre10d": 21251, - "ModStre10e": 21252, - "ModStre10f": 21253, - "ModStre10g": 21254, - "ModStre10h": 21255, - "ModStre10i": 21256, - "ModStre10j": 21257, - "WeaponDescOrb": 21259, - "ItemexpED": 21260, - "StrGemX1": 21261, - "StrGemX2": 21262, - "StrGemX3": 21263, - "StrGemX4": 21264, - "GemeffectX11": 21265, - "GemeffectX12": 21266, - "GemeffectX13": 21267, - "GemeffectX21": 21268, - "GemeffectX22": 21269, - "GemeffectX23": 21270, - "GemeffectX31": 21271, - "GemeffectX32": 21272, - "GemeffectX33": 21273, - "GemeffectX41": 21274, - "GemeffectX42": 21275, - "GemeffectX43": 21276, - "GemeffectX51": 21277, - "GemeffectX52": 21278, - "GemeffectX53": 21279, - "GemeffectX61": 21280, - "GemeffectX62": 21281, - "GemeffectX63": 21282, - "GemeffectX71": 21283, - "GemeffectX72": 21284, - "GemeffectX73": 21285, - "Coldkill": 21286, - "Butchers Cleaver": 21287, - "Butcher's Pupil": 21288, - "Islestrike": 21289, - "Pompe's Wrath": 21290, - "Guardian Naga": 21291, - "Warlord's Trust": 21292, - "Spellsteel": 21293, - "Stormrider": 21294, - "Boneslayer Blade": 21295, - "The Minotaur": 21296, - "Suicide Branch": 21297, - "Cairn Shard": 21298, - "Arm of King Leoric": 21299, - "Blackhand Key": 21300, - "Dark Clan Crusher": 21301, - "Drulan's Tongue": 21302, - "Zakrum's Hand": 21303, - "The Fetid Sprinkler": 21304, - "Hand of Blessed Light": 21305, - "Fleshrender": 21306, - "Sureshrill Frost": 21307, - "Moonfall": 21308, - "Baezils Vortex": 21309, - "Earthshaker": 21310, - "Bloodtree Stump": 21311, - "The Gavel of Pain": 21312, - "Bloodletter": 21313, - "Coldsteal Eye": 21314, - "Hexfire": 21315, - "Blade of Ali Baba": 21316, - "Riftslash": 21317, - "Headstriker": 21318, - "Plague Bearer": 21319, - "The Atlantien": 21320, - "Crainte Vomir": 21321, - "Bing Sz Wang": 21322, - "The Vile Husk": 21323, - "Cloudcrack": 21324, - "Todesfaelle Flamme": 21325, - "Swordguard": 21326, - "Spineripper": 21327, - "Heart Carver": 21328, - "Blackbog's Sharp": 21329, - "Stormspike": 21330, - "The Impaler": 21331, - "Kelpie Snare": 21332, - "Soulfeast Tine": 21333, - "Hone Sundan": 21334, - "Spire of Honor": 21335, - "The Meat Scraper": 21336, - "Blackleach Blade": 21337, - "Athena's Wrath": 21338, - "Pierre Tombale Couant": 21339, - "Husoldal Evo": 21340, - "Grim's Burning Dead": 21341, - "Ribcracker": 21342, - "Chromatic Ire": 21343, - "Warpspear": 21344, - "Skullcollector": 21345, - "Skystrike": 21346, - "Kuko Shakaku": 21347, - "Endlessshail": 21348, - "Whichwild String": 21349, - "Godstrike Arch": 21350, - "Langer Briser": 21351, - "Pus Spiter": 21352, - "Buriza-Do Kyanon": 21353, - "Vampiregaze": 21354, - "String of Ears": 21355, - "Gorerider": 21356, - "Lavagout": 21357, - "Venom Grip": 21358, - "Visceratuant": 21359, - "Guardian Angle": 21360, - "Shaftstop": 21361, - "Skin of the Vipermagi": 21362, - "Blackhorn": 21363, - "Valkiry Wing": 21364, - "Peasent Crown": 21365, - "Demon Machine": 21366, - "Magewrath": 21367, - "Cliffkiller": 21368, - "Riphook": 21369, - "Razorswitch": 21370, - "Meatscrape": 21371, - "Coldsteel Eye": 21372, - "Pitblood Thirst": 21373, - "Gaya Wand": 21374, - "Ondal's Wisdom": 21375, - "Geronimo's Fury": 21376, - "Charsi's Favor": 21377, - "Doppleganger's Shadow": 21378, - "Deathbit": 21379, - "Warshrike": 21380, - "Gutsiphon": 21381, - "Razoredge": 21382, - "Stonerattle": 21383, - "Marrowgrinder": 21384, - "Gore Ripper": 21385, - "Bush Wacker": 21386, - "Demonlimb": 21387, - "Steelshade": 21388, - "Tomb Reaver": 21389, - "Death's Web": 21390, - "Gaia's Wrath": 21391, - "Khalim's Vengance": 21392, - "Angel's Song": 21393, - "The Reedeemer": 21394, - "Fleshbone": 21395, - "Odium": 21396, - "Blood Comet": 21397, - "Bonehew": 21398, - "Steelrend": 21399, - "Stone Crusher": 21400, - "Bul-Kathos' Might": 21401, - "Arioc's Needle": 21402, - "Shadowdancer": 21403, - "Indiego's Fancy": 21404, - "Aladdin's Eviserator": 21405, - "Tyrael's Mercy": 21406, - "Souldrain": 21407, - "Runemaster": 21408, - "Deathcleaver": 21409, - "Executioner's Justice": 21410, - "Wallace's Tear": 21411, - "Leviathan": 21412, - "The Wanderer's Blade": 21413, - "Qual'Kek's Enforcer": 21414, - "Dawnbringer": 21415, - "Dragontooth": 21416, - "Wisp": 21417, - "Gargoyle's Bite": 21418, - "Lacerator": 21419, - "Mang Song's Lesson": 21420, - "Viperfork": 21421, - "Blood Chalice": 21422, - "El Espiritu": 21423, - "The Long Rod": 21424, - "Demonhorn's Edge": 21425, - "The Ensanguinator": 21426, - "The Reaper's Toll": 21427, - "Spiritkeeper": 21428, - "Hellrack": 21429, - "Alma Negra": 21430, - "Darkforge Spawn": 21431, - "Rockhew": 21432, - "Sankenkur's Resurrection": 21433, - "Erion's Bonehandle": 21434, - "The Archon Magus": 21435, - "Widow maker": 21436, - "Catgut": 21437, - "Ghostflame": 21438, - "Shadowkiller": 21439, - "Bling Bling": 21440, - "Nebucaneezer's Storm": 21441, - "Griffon's Eye": 21442, - "Eaglewind": 21443, - "Windhammer": 21444, - "Thunderstroke": 21445, - "Giantmaimer": 21446, - "Demon's Arch": 21447, - "The Scalper": 21448, - "Bloodmoon": 21449, - "Djinnslayer": 21450, - "Cranebeak": 21451, - "Iansang's Frenzy": 21452, - "Warhound": 21453, - "Gulletwound": 21454, - "Headhunter's Glory": 21455, - "Mordoc's marauder": 21456, - "Talberd's Law": 21457, - "Amodeus's Manipulator": 21458, - "Darksoul": 21459, - "The Black Adder": 21460, - "Earthshifter": 21461, - "Nature's Peace": 21462, - "Horazon's Chalice": 21463, - "Seraph's Hymn": 21464, - "Zakarum's Salvation": 21465, - "Fleshripper": 21466, - "Stonerage": 21467, - "Blood Rain": 21468, - "Horizon's Tornado": 21469, - "Nord's Tenderizer": 21470, - "Wrath of Cain": 21471, - "Siren's call": 21472, - "Jadetalon": 21473, - "Wraithfang": 21474, - "Blademaster": 21475, - "Cerebus": 21476, - "Archangel's Deliverance": 21477, - "Sinblade": 21478, - "Runeslayer": 21479, - "Excalibur": 21480, - "Fuego Del Sol": 21481, - "Stoneraven": 21482, - "El Infierno": 21483, - "Moonrend": 21484, - "Larzuk's Champion": 21485, - "Nightsummon": 21486, - "Bonescapel": 21487, - "Rabbit Slayer": 21488, - "Pagan's Athame": 21489, - "The Swashbuckler": 21490, - "Kang's Virtue": 21491, - "Snaketongue": 21492, - "Lifechoke": 21493, - "Ethereal edge": 21494, - "Palo Grande": 21495, - "Carnageleaver": 21496, - "Ghostleach": 21497, - "Soulreaper": 21498, - "Samual's Caretaker": 21499, - "Hell's Whisper": 21500, - "The Harvester": 21501, - "Raiden's Crutch": 21502, - "The TreeEnt": 21503, - "Stormwillow": 21504, - "Moonshadow": 21505, - "Strongoak": 21506, - "Demonweb": 21507, - "Bloodraven's Charge": 21508, - "Shadefalcon": 21509, - "Robin's Yolk": 21510, - "Glimmershred": 21511, - "Wraithflight": 21512, - "Lestron's Mark": 21513, - "Banshee's Wail": 21514, - "Windstrike": 21515, - "Medusa's Gaze": 21516, - "Titanfist": 21517, - "Hadeshorn": 21518, - "Rockstopper": 21519, - "Stealskull": 21520, - "Darksight Helm": 21521, - "Crown of Thieves": 21522, - "Blackhorn's Face": 21523, - "The Spirit Shroud": 21524, - "Skin of the Flayed One": 21525, - "Ironpelt": 21526, - "Spiritforge": 21527, - "Crow Caw": 21528, - "Duriel's Shell": 21529, - "Skullder's Ire": 21530, - "Toothrow": 21531, - "Atma's Wail": 21532, - "Black Hades": 21533, - "Corpsemourn": 21534, - "Que-hegan's Wisdom": 21535, - "Moser's Blessed Circle": 21536, - "Stormchaser": 21537, - "Tiamat's Rebuke": 21538, - "Gerke's Sanctuary": 21539, - "Radimant's Sphere": 21540, - "Gravepalm": 21541, - "Ghoulhide": 21542, - "Hellmouth": 21543, - "Infernostride": 21544, - "Waterwalk": 21545, - "Silkweave": 21546, - "Wartraveler": 21547, - "Razortail": 21548, - "Gloomstrap": 21549, - "Snowclash": 21550, - "Thudergod's Vigor": 21551, - "Lidless Wall": 21552, - "Lanceguard": 21553, - "Squire's Cover": 21554, - "Boneflame": 21555, - "Steelpillar": 21556, - "Nightwing's Veil": 21557, - "Hightower's Watch": 21558, - "Crown of Ages": 21559, - "Andariel's Visage": 21560, - "Darkfear": 21561, - "Dragonscale": 21562, - "Steel Carapice": 21563, - "Ashrera's Wired Frame": 21564, - "Rainbow Facet": 21565, - "Ravenlore": 21566, - "Boneshade": 21567, - "Nethercrow": 21568, - "Hellwarden's Husk": 21569, - "Flamebellow": 21570, - "Fathom": 21571, - "Wolfhowl": 21572, - "Spirit Ward": 21573, - "Kira's Guardian": 21574, - "Orumus' Robes": 21575, - "Gheed's Fortune": 21576, - "The Vicar": 21577, - "Stormlash": 21578, - "Halaberd's Reign": 21579, - "Parkersor's Calm": 21580, - "Warriv's Warder": 21581, - "Spike Thorn": 21582, - "Dracul's Grasp": 21583, - "Frostwind": 21584, - "Templar's Might": 21585, - "Eschuta's temper": 21620, - "Firelizard's Talons": 21587, - "Sandstorm Trek": 21588, - "Marrowwalk": 21589, - "Heaven's Light": 21590, - "Merman's Speed": 21591, - "Arachnid Mesh": 21592, - "Nosferatu's Coil": 21593, - "Metalgird": 21594, - "Verdugo's Hearty Cord": 21595, - "Sigurd's Staunch": 21596, - "Carrion Wind": 21597, - "Giantskull": 21598, - "Ironward": 21599, - "Gillian's Brazier": 21600, - "Drakeflame": 21601, - "Dust Storm": 21602, - "Skulltred": 21603, - "Alma's Reflection": 21604, - "Drulan's Tounge": 21605, - "Sacred Charge": 21606, - "Bul-Kathos": 21607, - "Saracen's Chance": 21608, - "Highlord's Wrath": 21609, - "Raven Frost": 21610, - "Dwarf Star": 21611, - "Atma's Scarab": 21612, - "Mara's Kaleidoscope": 21613, - "Crescent Moon": 21614, - "The Rising Sun": 21615, - "The Cat's Eye": 21616, - "Bul Katho's Wedding Band": 21617, - "Rings": 21618, - "Metalgrid": 21619, - "Stormshield": 21621, - "Blackoak Shield": 21622, - "Ormus' Robes": 21623, - "Arkaine's Valor": 21624, - "The Gladiator's Bane": 21625, - "Veil of Steel": 21626, - "Harlequin Crest": 21627, - "Lance Guard": 21628, - "Kerke's Sanctuary": 21629, - "Mosers Blessed Circle": 21630, - "Que-Hegan's Wisdon": 21631, - "Guardian Angel": 21632, - "Skin of the Flayerd One": 21633, - "Armor": 21634, - "Windforce": 21635, - "Eaglehorn": 21636, - "Gimmershred": 21637, - "Widowmaker": 21638, - "Stormspire": 21639, - "Naj's Puzzler": 21640, - "Ethereal Edge": 21641, - "Wizardspike": 21642, - "The Grandfather": 21643, - "Doombringer": 21644, - "Tyrael's Might": 21645, - "Lightsabre": 21646, - "The Cranium Basher": 21647, - "Schaefer's Hammer": 21648, - "Baranar's Star": 21649, - "Deaths's Web": 21650, - "Messerschmidt's Reaver": 21651, - "Hellslayer": 21652, - "Endlesshail": 21653, - "The Atlantian": 21654, - "Riftlash": 21655, - "Baezil's Vortex": 21656, - "Zakarum's Hand": 21657, - "Carin Shard": 21658, - "The Minataur": 21659, - "Trang-Oul's Avatar": 21660, - "Trang-Oul's Guise": 21661, - "Trang-Oul's Wing": 21662, - "Trang-Oul's Mask": 21663, - "Trang-Oul's Scales": 21664, - "Trang-Oul's Claws": 21665, - "Trang-Oul's Girth": 21666, - "Natalya's Odium": 21667, - "Natalya's Totem": 21668, - "Natalya's Mark": 21669, - "Natalya's Shadow": 21670, - "Natalya's Soul": 21671, - "Griswold's Legacy": 21672, - "Griswolds's Redemption": 21673, - "Griswold's Honor": 21674, - "Griswold's Heart": 21675, - "Griswold's Valor": 21676, - "Tang's Imperial Robes": 21677, - "Tang's Fore-Fathers": 21678, - "Tang's Rule": 21679, - "Tang's Throne": 21680, - "Tang's Battle Standard": 21681, - "Ogun's Fierce Visage": 21682, - "Ogun's Shadow": 21683, - "Ogun's Lash": 21684, - "Ogun's Vengeance": 21685, - "Bul-Kathos' Warden": 21686, - "Bul-Kathos' Children": 21687, - "Bul-Kathos' Sacred Charge": 21688, - "Bul-Kathos' Tribal Guardian": 21689, - "Bul-Kathos' Custodian": 21690, - "Flowkrad's Howl": 21691, - "Flowkrad's Grin": 21692, - "Flowkrad's Fur": 21693, - "Flowkrad's Paws": 21694, - "Flowkrad's Sinew": 21695, - "Aldur's Watchtower": 21696, - "Aldur's Stony Gaze": 21697, - "Aldur's Deception": 21698, - "Aldur's Guantlet": 21699, - "Aldur's Advance": 21700, - "M'avina's Battle Hymn": 21701, - "M'avina's True Sight": 21702, - "M'avina's Embrace": 21703, - "M'avina's Icy Clutch": 21704, - "M'avina's Tenet": 21705, - "M'avina's Caster": 21706, - "Sazabi's Grand Tribute": 21707, - "Sazabi's Cobalt Redeemer": 21708, - "Sazabi's Ghost Liberator": 21709, - "Sazabi's Mental Sheath": 21710, - "Hwanin's Majesty": 21711, - "Hwanin's Justice": 21712, - "Hwanin's Splendor": 21713, - "Hwanin's Refuge": 21714, - "Hwanin's Cordon": 21715, - "The Disciple": 21716, - "Telling of Beads": 21717, - "Laying of Hands": 21718, - "Rite of Passage": 21719, - "Spiritual Custodian": 21720, - "Credendum": 21721, - "Cow King's Leathers": 21722, - "Cow King's Horns": 21723, - "Cow King's Hide": 21724, - "Cow King's Hoofs": 21725, - "Aragon's Masterpiece": 21726, - "Aragon's Sunfire": 21727, - "Aragon's Icy Stare": 21728, - "Aragon's Storm Cloud": 21729, - "Orphan's Call": 21730, - "Guillaume's Face": 21731, - "Willhelm's Pride": 21732, - "Magnus' Skin": 21733, - "Wihtstan's Guard": 21734, - "Titan's Revenge": 21735, - "Shakabra's Crux": 21736, - "Lycander's Aim": 21737, - "Shadow's Touch": 21738, - "The Prowler": 21739, - "Mortal Crescent": 21740, - "Cutthroat": 21741, - "Sarmichian Justice": 21742, - "Annihilus": 21743, - "Arreat's Face": 21744, - "The Harbinger": 21745, - "Doomseer": 21746, - "Howling Visage": 21747, - "Terra": 21748, - "Syrian": 21749, - "Jalal's Mane": 21750, - "Malignant": 21751, - "Apothecary's Tote": 21752, - "Apocrypha": 21753, - "Foci of Visjerei": 21754, - "Homunculus": 21755, - "Aurora's Guard": 21756, - "Crest of Morn": 21757, - "Herald of Zakarum": 21758, - "Akarat's Protector": 21759, - "Ancient Eye": 21760, - "Globe of Visjerei": 21761, - "The Oculus": 21762, - "Phoenix Egg": 21763, - "Xenos": 21764, - "Nagas": 21765, - "Wyvern's Head": 21766, - "Sightless Veil": 21767, - "ChampionFormatX": 21768, - "EskillKickSing": 21769, - "EskillKickPlur": 21770, - "EskillPetLife": 21771, - "EskillWolfDef": 21772, - "EskillPassiveFeral": 21773, - "Eskillperhit12": 21774, - "Eskillincasehit": 21775, - "Eskillincasemastery": 21776, - "Eskillincaseraven": 21777, - "pad": 21779, - "axf": 21780, - "Eskillkickdamage": 21781, - "ModStre10k": 21782, - "ModStre10L": 21783, - "Class Specific": 21784, - "fana": 21785, - "qsta5q14": 21786, - "qstsa5q42a": 21787, - "qstsa5q31a": 21788, - "qstsa5q21a": 21789, - "qstsa5q43a": 21790, - "qstsa5q62a": 21791, - "qstsa5q61a": 21792, - "act1X": 21797, - "act2X": 21798, - "act3X": 21799, - "act4X": 21800, - "strepilogueX": 21801, - "act5X": 21802, - "strlastcinematic": 21803, - "CfgSay7": 21804, - "0sc": 21805, - "tr2": 21806, - "of Lightning Strike": 21807, - "of Plague Jab": 21808, - "of Charged Strike": 21809, - "of Impaling Strike": 21810, - "of Poison Jab": 21811, - "of Power Strike": 21812, - "of the Colossus": 21813, - "of the Kraken": 21814, - "Tal Rasha's Wrappings": 21815, - "Tal Rasha's Fire-Spun Cloth": 21816, - "Tal Rasha's Adjudication": 21817, - "Tal Rasha's Howling Wind": 21818, - "Tal Rasha's Lidless Eye": 21819, - "Tal Rasha's Horadric Crest": 21820, - "Hwanin's Seal": 21821, - "Heaven's Brethren": 21822, - "Dangoon's Teaching": 21823, - "Ondal's Almighty": 21824, - "Heaven's Taebaek": 21825, - "Haemosu's Adament": 21826, - "Lycander's Flank": 21827, - "Constricting Ring": 21828, - "Ginther's Rift": 21829, - "Naj's Ancient Set": 21830, - "Naj's Light Plate": 21831, - "Naj's Circlet": 21832, - "Sander's Superstition": 21833, - "Sander's Taboo": 21834, - "Sander's Basis": 21835, - "Sander's Derby": 21836, - "Sander's Court Jester": 21837, - "Ghost Liberator": 21838, - "Wilhelm's Pride": 21839, - "Immortal King's Stone Crusher": 21840, - "Immortal King's Pillar": 21841, - "Immortal King's Forge": 21842, - "Immortal King's Detail": 21843, - "Immortal King's Soul Cage \tImmortal King's Soul Cage": 21844, - "Immortal King's Will": 21845, - "Immortal King": 21846, - "Aldur's Gauntlet": 21847, - "Ancient Statue 3": 21848, - "Ancient Statue 2": 21849, - "Ancient Statue 1": 21850, - "Baal Subject 1": 21851, - "Baal Subject 2": 21852, - "Baal Subject 3": 21853, - "Baal Subject 4": 21854, - "Baal Subject 5": 21855, - "Baal Subject 6": 21856, - "Baal Subject 6a": 21857, - "Baal Subject 6b": 21858, - "Baal Crab Clone": 21859, - "Baal Crab to Stairs": 21860, - "BaalColdMage": 21861, - "Baal Subject Mummy": 21862, - "Baal Tentacle": 21863, - "Baals Minion": 21864, - "Hell1": 21865, - "Hell2": 21866, - "Hell3": 21867, - "To Hell1": 21868, - "To Hell2": 21869, - "To Hell3": 21870, - "Lord of Destruction": 21871, - "EskillPerBlade": 21873, - "ExInsertSockets": 21874, - "McAuley's Superstition": 21875, - "McAuley's Taboo": 21876, - "McAuley's Riprap": 21877, - "McAuley's Paragon": 21878, - "McAuley's Folly": 21879, - "qstsa5q62b": 21881, - "of the Plague": 21883, - "Go South": 21884, - "ItemExpansiveChancX": 21885, - "ItemExpansiveChanc1": 21886, - "ItemExpansiveChanc2": 21887, - "ItemExpcharmdesc": 21888, - "StrMercEx12": 21889, - "StrMercEx14": 21890, - "StrMercEx15": 21891, - "Eskillelementaldmg": 21892, - "Playersubtitles29": 21893, - "Playersubtitles30": 21894, - "LeaveCampDru": 21895, - "LeaveCampAss": 21896, - "EnterDOEAss": 21897, - "EnterDOEDru": 21898, - "EnterBurialAss": 21899, - "EnterBurialDru": 21900, - "EnterMonasteryAss": 21901, - "EnterMonasteryDru": 21902, - "EnterForgottenTAss": 21903, - "EnterForgottenTDru": 21904, - "EnterJailAss": 21905, - "EnterJailDru": 21906, - "EnterCatacombsAss": 21907, - "EnterCatacombsDru": 21908, - "CompletingDOEAss": 21909, - "CompletingDOEDru": 21910, - "CompletingBurialAss": 21911, - "CompletingBurialDru": 21912, - "FindingInifusAss": 21913, - "FindingInifusDru": 21914, - "FindingCairnAss": 21915, - "FindingCairnDru": 21916, - "FindingTristramAss": 21917, - "FindingTristramDru": 21918, - "RescueCainAss": 21919, - "RescueCainDru": 21920, - "HoradricMalusAss": 21921, - "HoradricMalusDru": 21922, - "CompletingAndarielAss": 21925, - "CompletingAndarielDru": 21926, - "EnteringRadamentAss": 21927, - "EnteringRadamentDru": 21928, - "CompletingRadamentAss": 21929, - "CompletingRadamentDru": 21930, - "BeginTaintedSunAss": 21931, - "BeginTaintedSunDru": 21932, - "EnteringClawViperAss": 21933, - "EnteringClawViperDru": 21934, - "CompletingTaintedSunAss": 21935, - "CompletingTaintedSunDru": 21936, - "EnteringArcaneAss": 21937, - "EnteringArcaneDru": 21938, - "FindingSummonerAss": 21939, - "FindingSummonerDru": 21940, - "CompletingSummonerAss": 21941, - "CompletingSummonerDru": 21942, - "FindingdecoyTombAss": 21943, - "FindingdecoyTombDru": 21944, - "FindingTrueTombAss": 21945, - "FindingTrueTombDru": 21946, - "CompletingTombAss": 21947, - "CompletingTombDru": 21948, - "FindingLamEsenAss": 21949, - "FindingLamEsenDru": 21950, - "CompletingLamEsenAss": 21952, - "CompletingLamEsenDru": 21953, - "FindingBeneathCityAss": 21954, - "FindingBeneathCityDru": 21955, - "FindingDrainLeverAss": 21956, - "FindingDrainLeverDru": 21957, - "CompletingBeneathCityAss": 21958, - "CompletingBeneathCityDru": 21959, - "CompletingBladeAss": 21960, - "CompletingBladeDru": 21961, - "FindingJadeFigAss": 21962, - "FindingJadeFigDru": 21963, - "FindingTempleAss": 21964, - "FindingTempleDru": 21965, - "CompletingTempleAss": 21966, - "CompletingTempleDru": 21967, - "FindingGuardianTowerAss": 21968, - "FindingGuardianTowerDru": 21969, - "CompletingGuardianTowerAss": 21971, - "FreezingIzualAss": 21973, - "FreezingIzualDru": 21974, - "KillingdDiabloSor": 21975, - "KillingdDiabloBar": 21976, - "KillingdDiabloNec": 21977, - "KillingdDiabloPal": 21978, - "KillingdDiabloAms": 21979, - "KillingdDiabloAss": 21980, - "KillingdDiabloDru": 21981, - "LeavingTownAct5Sor": 21982, - "LeavingTownAct5Bar": 21983, - "LeavingTownAct5Nec": 21984, - "LeavingTownAct5Pal": 21985, - "LeavingTownAct5Ams": 21986, - "LeavingTownAct5Ass": 21987, - "LeavingTownAct5Dru": 21988, - "CompletingStopSiegeSor": 21989, - "CompletingStopSiegeBar": 21990, - "CompletingStopSiegeNec": 21991, - "CompletingStopSiegePal": 21992, - "CompletingStopSiegeAms": 21993, - "CompletingStopSiegeAss": 21994, - "CompletingStopSiegeDru": 21995, - "RescueQual-KehkAct5Sor": 21996, - "RescueQual-KehkAct5Bar": 21997, - "RescueQual-KehkAct5Nec": 21998, - "RescueQual-KehkAct5Pal": 21999, - "RescueQual-KehkAct5Ams": 22000, - "RescueQual-KehkAct5Ass": 22001, - "RescueQual-KehkAct5Dru": 22002, - "EnteringNihlathakAct5Sor": 22003, - "EnteringNihlathakAct5Bar": 22004, - "EnteringNihlathakAct5Nec": 22005, - "EnteringNihlathakAct5Pal": 22006, - "EnteringNihlathakAct5Ams": 22007, - "EnteringNihlathakAct5Ass": 22008, - "EnteringNihlathakAct5Dru": 22009, - "CompletingNihlathakAct5Sor": 22010, - "CompletingNihlathakAct5Bar": 22011, - "CompletingNihlathakAct5Nec": 22012, - "CompletingNihlathakAct5Pal": 22013, - "CompletingNihlathakAct5Ams": 22014, - "CompletingNihlathakAct5Ass": 22015, - "CompletingNihlathakAct5Dru": 22016, - "EnteringTopMountAct5Sor": 22017, - "EnteringTopMountAct5Bar": 22018, - "EnteringTopMountAct5Nec": 22019, - "EnteringTopMountAct5Pal": 22020, - "EnteringTopMountAct5Ams": 22021, - "EnteringTopMountAct5Ass": 22022, - "EnteringTopMountAct5Dru": 22023, - "EnteringWorldstoneAct5Sor": 22024, - "EnteringWorldstoneAct5Bar": 22025, - "EnteringWorldstoneAct5Nec": 22026, - "EnteringWorldstoneAct5Pal": 22027, - "EnteringWorldstoneAct5Ams": 22028, - "EnteringWorldstoneAct5Ass": 22029, - "EnteringWorldstoneAct5Dru": 22030, - "CompletingDefeatBaalAct5Sor": 22031, - "CompletingDefeatBaalAct5Bar": 22032, - "CompletingDefeatBaalAct5Nec": 22033, - "CompletingDefeatBaalAct5Pal": 22034, - "CompletingDefeatBaalAct5Ams": 22035, - "CompletingDefeatBaalAct5Ass": 22036, - "CompletingDefeatBaalAct5Dru": 22037, - "Skillname222": 22038, - "Skillsd222": 22039, - "Skillld222": 22040, - "Skillan222": 22041, - "Skillname223": 22046, - "Skillsd223": 22047, - "Skillld223": 22048, - "Skillan223": 22049, - "Skillname225": 22050, - "Skillsd225": 22051, - "Skillld225": 22052, - "Skillan225": 22053, - "Skillname226": 22054, - "Skillsd226": 22055, - "Skillld226": 22056, - "Skillan226": 22057, - "Skillname227": 22058, - "Skillsd227": 22059, - "Skillld227": 22060, - "Skillan227": 22061, - "Skillname228": 22062, - "Skillsd228": 22063, - "Skillld228": 22064, - "Skillan228": 22065, - "Skillname229": 22066, - "Skillsd229": 22067, - "Skillld229": 22068, - "Skillan229": 22069, - "Skillname230": 22070, - "Skillsd230": 22071, - "Skillld230": 22072, - "Skillan230": 22073, - "Skillname231": 22074, - "Skillsd231": 22075, - "Skillld231": 22076, - "Skillan231": 22077, - "Skillname232": 22078, - "Skillsd232": 22079, - "Skillld232": 22080, - "Skillan232": 22081, - "Skillname233": 22082, - "Skillsd233": 22083, - "Skillld233": 22084, - "Skillan233": 22085, - "Skillname234": 22086, - "Skillsd234": 22087, - "Skillld234": 22088, - "Skillan234": 22089, - "Skillname235": 22090, - "Skillsd235": 22091, - "Skillld235": 22092, - "Skillan235": 22093, - "Skillname236": 22094, - "Skillsd236": 22095, - "Skillld236": 22096, - "Skillan236": 22097, - "Skillname237": 22098, - "Skillsd237": 22099, - "Skillld237": 22100, - "Skillan237": 22101, - "Skillname238": 22102, - "Skillsd238": 22103, - "Skillld238": 22104, - "Skillan238": 22105, - "Skillname239": 22106, - "Skillsd239": 22107, - "Skillld239": 22108, - "Skillan239": 22109, - "Skillname240": 22110, - "Skillsd240": 22111, - "Skillld240": 22112, - "Skillan240": 22113, - "Skillname241": 22114, - "Skillsd241": 22115, - "Skillld241": 22116, - "Skillan241": 22117, - "Skillname242": 22118, - "Skillsd242": 22119, - "Skillld242": 22120, - "Skillan242": 22121, - "Skillname243": 22122, - "Skillsd243": 22123, - "Skillld243": 22124, - "Skillan243": 22125, - "Skillname244": 22126, - "Skillsd244": 22127, - "Skillld244": 22128, - "Skillan244": 22129, - "Skillname245": 22130, - "Skillsd245": 22131, - "Skillld245": 22132, - "Skillan245": 22133, - "Skillname246": 22134, - "Skillsd246": 22135, - "Skillld246": 22136, - "Skillan246": 22137, - "Skillname247": 22138, - "Skillsd247": 22139, - "Skillld247": 22140, - "Skillan247": 22141, - "Skillname248": 22142, - "Skillsd248": 22143, - "Skillld248": 22144, - "Skillan248": 22145, - "Skillname249": 22146, - "Skillsd249": 22147, - "Skillld249": 22148, - "Skillan249": 22149, - "Skillname250": 22150, - "Skillsd250": 22151, - "Skillld250": 22152, - "Skillan250": 22153, - "Skillname251": 22154, - "Skillsd251": 22155, - "Skillld251": 22156, - "Skillan251": 22157, - "Skillname252": 22158, - "Skillsd252": 22159, - "Skillld252": 22160, - "Skillan252": 22161, - "Skillname253": 22162, - "Skillsd253": 22163, - "Skillld253": 22164, - "Skillan253": 22165, - "Skillname254": 22166, - "Skillsd254": 22167, - "Skillld254": 22168, - "Skillan254": 22169, - "Skillname255": 22170, - "Skillsd255": 22171, - "Skillld255": 22172, - "Skillan255": 22173, - "Skillname256": 22174, - "Skillsd256": 22175, - "Skillld256": 22176, - "Skillan256": 22177, - "Skillname257": 22178, - "Skillsd257": 22179, - "Skillld257": 22180, - "Skillan257": 22181, - "Skillname258": 22182, - "Skillsd258": 22183, - "Skillld258": 22184, - "Skillan258": 22185, - "Skillname259": 22186, - "Skillsd259": 22187, - "Skillld259": 22188, - "Skillan259": 22189, - "Skillname260": 22190, - "Skillsd260": 22191, - "Skillld260": 22192, - "Skillan260": 22193, - "Skillname261": 22194, - "Skillsd261": 22195, - "Skillld261": 22196, - "Skillan261": 22197, - "Skillname262": 22198, - "Skillsd262": 22199, - "Skillld262": 22200, - "Skillan262": 22201, - "Skillname263": 22202, - "Skillsd263": 22203, - "Skillld263": 22204, - "Skillan263": 22205, - "Skillname264": 22206, - "Skillsd264": 22207, - "Skillld264": 22208, - "Skillan264": 22209, - "Skillname265": 22210, - "Skillsd265": 22211, - "Skillld265": 22212, - "Skillan265": 22213, - "Skillname266": 22214, - "Skillsd266": 22215, - "Skillld266": 22216, - "Skillan266": 22217, - "Skillname267": 22218, - "Skillsd267": 22219, - "Skillld267": 22220, - "Skillan267": 22221, - "Skillname268": 22222, - "Skillsd268": 22223, - "Skillld268": 22224, - "Skillan268": 22225, - "Skillname269": 22226, - "Skillsd269": 22227, - "Skillld269": 22228, - "Skillan269": 22229, - "Skillname270": 22230, - "Skillsd270": 22231, - "Skillld270": 22232, - "Skillan270": 22233, - "Skillname271": 22234, - "Skillsd271": 22235, - "Skillld271": 22236, - "Skillan271": 22237, - "Skillname272": 22238, - "Skillsd272": 22239, - "Skillld272": 22240, - "Skillan272": 22241, - "Skillname273": 22242, - "Skillsd273": 22243, - "Skillld273": 22244, - "Skillan273": 22245, - "Skillname274": 22246, - "Skillsd274": 22247, - "Skillld274": 22248, - "Skillan274": 22249, - "Skillname275": 22250, - "Skillsd275": 22251, - "Skillld275": 22252, - "Skillan275": 22253, - "Skillname276": 22254, - "Skillsd276": 22255, - "Skillld276": 22256, - "Skillan276": 22257, - "Skillname277": 22258, - "Skillsd277": 22259, - "Skillld277": 22260, - "Skillan277": 22261, - "Skillname278": 22262, - "Skillsd278": 22263, - "Skillld278": 22264, - "Skillan278": 22265, - "Skillname279": 22266, - "Skillsd279": 22267, - "Skillld279": 22268, - "Skillan279": 22269, - "Skillname280": 22270, - "Skillsd280": 22271, - "Skillld280": 22272, - "Skillan280": 22273, - "Skillname281": 22274, - "Skillsd281": 22275, - "Skillld281": 22276, - "Skillan281": 22277, - "ESkillPerKick": 22286, - "EskillLifeSteal": 22287, - "Eskillchancetostun": 22288, - "Eskillchancetoafflict": 22289, - "Eskillpowerup1": 22290, - "Eskillpowerup2": 22291, - "Eskillpowerup3": 22292, - "Eskillpowerupadd": 22293, - "Eskillsinishup": 22294, - "Eskillpudlife": 22295, - "Eskillpudmana": 22296, - "Eskillpudburning": 22297, - "Eskillpuddgmper": 22298, - "Eskilllowerresis": 22299, - "Eskilltomeleeattacks": 22300, - "EskillManaSteal": 22301, - "Eskillferalpets": 22302, - "Eskillpercentatt": 22303, - "Eskillpercentlif": 22304, - "Eskillpercentdmg": 22305, - "Eskillfinishmove": 22306, - "Eskillmanarecov": 22307, - "Eskillphoenix1": 22308, - "Eskillphoenix2": 22309, - "Eskillphoenix3": 22310, - "Eskillthunder1": 22311, - "Eskillthunder2": 22312, - "Eskillthunder3": 22313, - "Eskillfistsoffire1": 22314, - "Eskillfistsoffire2": 22315, - "Eskillfistsoffire3": 22316, - "Eskillbladesofice1": 22317, - "Eskillbladesofice2": 22318, - "Eskillbladesofice3": 22319, - "strUI5": 22320, - "strUI6": 22321, - "strUI7": 22322, - "strUI8": 22323, - "strUI9": 22324, - "strUI10": 22325, - "strUI11": 22326, - "strUI12": 22327, - "strUI13": 22328, - "strUI14": 22329, - "UIFenirsui": 22330, - "UiRescuedBarUI": 22331, - "UiShadowUI": 22332, - "StrUI18": 22333, - "Spike Generator": 22334, - "Charged Bolt Sentry": 22335, - "Lightning Sentry": 22336, - "Blade Creeper": 22337, - "Invis Pet": 22338, - "Druid Hawk": 22339, - "Druid Wolf": 22340, - "Druid Totem": 22341, - "Druid Fenris": 22342, - "Druid Spirit Wolf": 22343, - "Druid Bear": 22344, - "Druid Plague Poppy": 22345, - "Druid Cycle of Life": 22346, - "Vine Creature": 22347, - "Eagleexp": 22348, - "Wolf": 22349, - "Bear": 22350, - "Siege Door": 22351, - "Siege Beast": 22358, - "Hell Temptress": 22389, - "Blood Temptress": 22390, - "Blood Witch": 22394, - "Hell Witch": 22395, - "CatapultN": 22411, - "CatapultS": 22412, - "CatapultE": 22413, - "CatapultW": 22414, - "Frozen Horror1": 22415, - "Frozen Horror2": 22416, - "Frozen Horror3": 22417, - "Frozen Horror4": 22418, - "Frozen Horror5": 22419, - "Blood Lord1": 22420, - "Blood Lord2": 22421, - "Blood Lord3": 22422, - "Blood Lord4": 22423, - "Blood Lord5": 22424, - "Catapult Spotter N": 22425, - "Catapult Spotter S": 22426, - "Catapult Spotter E": 22427, - "Catapult Spotter W": 22428, - "Catapult Spotter Siege": 22429, - "CatapultSiege": 22430, - "Barricade Wall Right": 22431, - "Barricade Wall Left": 22432, - "Barricade Door": 22433, - "Barricade Tower": 22434, - "Siege Boss": 22435, // shenk the overseer - "Evil hut": 22436, - "Death Mauler1": 22437, - "Death Mauler2": 22438, - "Death Mauler3": 22439, - "Death Mauler4": 22440, - "Death Mauler5": 22441, - "SnowYeti1": 22442, - "SnowYeti2": 22443, - "SnowYeti3": 22444, - "SnowYeti4": 22445, - "Baal Throne": 22446, - "Baal Crab": 22447, - "Baal Taunt": 22448, - "Putrid Defiler1": 22449, - "Putrid Defiler2": 22450, - "Putrid Defiler3": 22451, - "Putrid Defiler4": 22452, - "Putrid Defiler5": 22453, - "Pain Worm1": 22454, - "Pain Worm2": 22455, - "Pain Worm3": 22456, - "Pain Worm4": 22457, - "Pain Worm5": 22458, - "WolfRider5": 22459, - "WolfRider4": 22460, - "WolfRider3": 22461, - "WolfRider2": 22462, - "WolfRider1": 22463, - "Oak Sage": 22464, - "Heart of Wolverine": 22465, - "Spirit of Barbs": 22466, - "Shadow Warrior": 22467, - "Death Sentry": 22468, - "Inferno Sentry": 22469, - "Shadow Master": 22470, - "Wake of Destruction": 22471, - "Ghostly": 22472, - "Fanatic": 22473, - "Possessed": 22474, - "Berserk": 22475, - "Larzuk": 22476, - "Drehya": 22477, - "Malah": 22478, - "Nihlathak Town": 22479, - "Qual-Kehk": 22480, - "Act 5 Townguard": 22481, - "Act 5 Combatant": 22482, - "Nihlathak": 22483, - "POW": 22484, - "Moe": 22485, - "Curly": 22486, - "Larry": 22487, - "Ancient Barbarian 3": 22488, - "Ancient Barbarian 2": 22489, - "Ancient Barbarian 1": 22490, - "Blaze Ripper": 22491, - "Magma Torquer": 22492, - "Sharp Tooth Sayer": 22493, - "Vinvear Molech": 22494, - "Anodized Elite": 22495, - "Snapchip Shatter": 22496, - "Pindleskin": 22497, - "Threash Socket": 22498, - "Eyeback Unleashed": 22499, - "Megaflow Rectifier": 22500, // eldritch the rectifier - "Dac Farren": 22501, - "Bonesaw Breaker": 22502, - "Axe Dweller": 22503, - "Frozenstein": 22504, - "strDruidOnly": 22505, - "strAssassinOnly": 22506, - "strAmazonOnly": 22507, - "strBarbarianOnly": 22508, - "StrSklTree26": 22509, - "StrSklTree27": 22510, - "StrSklTree28": 22511, - "StrSklTree29": 22512, - "StrSklTree30": 22513, - "StrSklTree31": 22514, - "StrSklTree32": 22515, - "StrSklTree33": 22516, - "StrSklTree34": 22517, - "chestr": 22520, - "barrel wilderness": 22521, - "woodchestL": 22522, - "burialchestL": 22523, - "burialchestR": 22524, - "ChestL": 22527, - "ChestSL": 22528, - "ChestSR": 22529, - "woodchestR": 22530, - "chestR": 22531, - "burningbodies": 22532, - "burningpit": 22533, - "tribal flag": 22534, - "flag widlerness": 22535, - "eflg": 22536, - "chan": 22537, - "jar": 22538, - "jar2": 22539, - "jar3": 22540, - "swingingheads": 22541, - "pole": 22542, - "animatedskullsandrocks": 22543, - "hellgate": 22544, - "gate": 22545, - "banner1": 22546, - "banner2": 22547, - "mrpole": 22548, - "pene": 22549, - "debris": 22550, - "woodchest2R": 22551, - "woodchest2L": 22552, - "object1": 22553, - "magic shrine2": 22554, - "torch2": 22555, - "torch1": 22556, - "tomb3": 22557, - "tomb2": 22558, - "tomb1": 22559, - "ttor": 22560, - "icecave_torch2": 22561, - "icecave_torch1": 22562, - "clientsmoke": 22563, - "deadbarbarian": 22564, - "deadbarbarian18": 22565, - "uncle f#%* comedy central(c)\tMoe": 22566, - "cagedwussie1": 22567, - "icecaveshrine2": 22568, - "icecavejar4": 22569, - "icecavejar3": 22570, - "icecavejar2": 22571, - "icecavejar1": 22572, - "evilurn": 22573, - "secret object": 22574, - "Altar": 22575, - "Ldeathpole": 22576, - "deathpole": 22577, - "explodingchest": 22578, - "banner 2": 22579, - "banner 1": 22580, - "pileofskullsandrocks": 22581, - "animated skulland rockpile": 22582, - "jar1": 22583, - "etorch2": 22584, - "ettr": 22585, - "ecfra": 22586, - "etorch1": 22587, - "healthshrine": 22588, - "explodingbarrel": 22589, - "flag wilderness": 22590, - "object": 22591, - "Shrine2wilderness": 22592, - "Shrine3wilderness": 22593, - "pyox": 22594, - "ptox": 22595, - "Siege Control": 22596, - "mrjar": 22597, - "object2": 22598, - "mrbox": 22599, - "tomb3L": 22600, - "tomb2L": 22601, - "tomb1L": 22602, - "red light": 22603, - "groundtombL": 22604, - "groundtomb": 22605, - "deadperson": 22606, - "candles": 22607, - "sbub": 22608, - "ubub": 22609, - "deadperson2": 22610, - "Prison Door": 22611, - "ancientsaltar": 22612, - "hiddenstash": 22613, - "eweaponrackL": 22614, - "eweaponrackR": 22615, - "earmorstandL": 22616, - "earmorstandR": 22617, - "qstsa5q1": 22618, - "qsta5q11": 22619, - "qsta5q12": 22620, - "qsta5q13": 22621, - "qstsa5q2": 22622, - "qstsa5q21": 22623, - "qstsa5q22": 22624, - "qstsa5q23": 22625, - "qstsa5q24": 22626, - "qstsa5q3": 22627, - "qstsa5q31": 22628, - "qstsa5q32": 22629, - "qstsa5q33": 22630, - "qstsa5q34": 22631, - "qstsa5q35": 22632, - "qstsa5q4": 22633, - "qstsa5q41": 22634, - "qstsa5q42": 22635, - "qstsa5q43": 22636, - "qstsa5q5": 22637, - "qstsa5q51": 22638, - "qstsa5q52": 22639, - "qstsa5q53": 22640, - "qstsa5q6": 22641, - "qstsa5q61": 22642, - "qstsa5q62": 22643, - "qstsa5q63": 22644, - "qstsa5q64": 22645, - "Harrogath": 22646, - "Bloody Foothills": 22647, - "Rigid Highlands": 22648, - "Arreat Plateau": 22649, - "Crystalized Cavern Level 1": 22650, - "Cellar of Pity": 22651, - "Crystalized Cavern Level 2": 22652, - "Echo Chamber": 22653, - "Tundra Wastelands": 22654, - "Glacial Caves Level 1": 22655, - "Glacial Caves Level 2": 22656, - "Rocky Summit": 22657, - "Nihlathaks Temple": 22658, - "Halls of Anguish": 22659, - "Halls of Death's Calling": 22660, - "Halls of Tormented Insanity": 22661, - "Halls of Vaught": 22662, - "The Worldstone Keep Level 1": 22663, - "The Worldstone Keep Level 2": 22664, - "The Worldstone Keep Level 3": 22665, - "The Worldstone Chamber": 22666, - "Throne of Destruction": 22667, - "To Harrogath": 22668, - "To The Bloody Foothills": 22669, - "To The Rigid Highlands": 22670, - "To The Arreat Plateau": 22671, - "To The Crystalized Cavern Level 1": 22672, - "To The Cellar of Pity": 22673, - "To The Crystalized Cavern Level 2": 22674, - "To The Echo Chamber": 22675, - "To The Tundra Wastelands": 22676, - "To The Glacier Caves Level 1": 22677, - "To The Glacier Caves Level 2": 22678, - "To The Rocky Summit": 22679, - "To Nihlathaks Temple": 22680, - "To The Halls of Anguish": 22681, - "To The Halls of Death's Calling": 22682, - "To The Halls of Tormented Insanity": 22683, - "To The Halls of Vaught": 22684, - "To The Worldstone Keep Level 1": 22685, - "To The Worldstone Keep Level 2": 22686, - "To The Worldstone Keep Level 3": 22687, - "To The Worldstone Chamber": 22688, - "To The Throne of Destruction": 22689, - "hireiconinfo1": 22690, - "hireiconinfo2": 22691, - "hiredismiss": 22692, - "hiredismisshire": 22693, - "hirerehire": 22694, - "hireresurrect": 22695, - "hireresurrect2": 22696, - "hirechat1": 22697, - "hirechat2": 22698, - "hirechat3": 22699, - "hirepraise1": 22700, - "hirepraise2": 22701, - "hiredanger1": 22702, - "hiredanger2": 22703, - "hiredanger3": 22704, - "hiredanger4": 22705, - "hiredanger5": 22706, - "hiredanger6": 22707, - "hirefeelstronger2": 22708, - "hirehelp1": 22709, - "hirehelp2": 22710, - "hirehelp3": 22711, - "hirehelp4": 22712, - "hiregreets1": 22713, - "hiregreets2": 22714, - "hiregreets3": 22715, - "hiregreets4": 22716, - "CfgSkill9": 22717, - "CfgSkill10": 22718, - "CfgSkill11": 22719, - "CfgSkill12": 22720, - "CfgSkill13": 22721, - "CfgSkill14": 22722, - "CfgSkill15": 22723, - "CfgSkill16": 22724, - "CfgToggleminimap": 22725, - "Cfgswapweapons": 22726, - "Cfghireling": 22727, - "MiniPanelHireinv": 22728, - "MiniPanelHire": 22729, - "Go North": 22737, - "Travel To Harrogath": 22738, - "Rename Instruct": 22747, - "Addsocketsui": 22748, - "Personalizeui": 22749, - "Addsocketsui2": 22750, - "MercX101": 22751, - "MercX102": 22752, - "MercX103": 22753, - "MercX104": 22754, - "MercX105": 22755, - "MercX106": 22756, - "MercX107": 22757, - "MercX108": 22758, - "MercX109": 22759, - "MercX110": 22760, - "MercX111": 22761, - "MercX112": 22762, - "MercX113": 22763, - "MercX114": 22764, - "MercX115": 22765, - "MercX116": 22766, - "MercX117": 22767, - "MercX118": 22768, - "MercX119": 22769, - "MercX120": 22770, - "MercX121": 22771, - "MercX122": 22772, - "MercX123": 22773, - "MercX124": 22774, - "MercX125": 22775, - "MercX126": 22776, - "MercX127": 22777, - "MercX128": 22778, - "MercX129": 22779, - "MercX130": 22780, - "MercX131": 22781, - "MercX132": 22782, - "MercX133": 22783, - "MercX134": 22784, - "MercX135": 22785, - "MercX136": 22786, - "MercX137": 22787, - "MercX138": 22788, - "MercX139": 22789, - "MercX140": 22790, - "MercX141": 22791, - "MercX142": 22792, - "MercX143": 22793, - "MercX144": 22794, - "MercX145": 22795, - "MercX146": 22796, - "MercX147": 22797, - "MercX148": 22798, - "MercX149": 22799, - "MercX150": 22800, - "MercX151": 22801, - "MercX152": 22802, - "MercX153": 22803, - "MercX154": 22804, - "MercX155": 22805, - "MercX156": 22806, - "MercX157": 22807, - "MercX158": 22808, - "MercX159": 22809, - "MercX160": 22810, - "MercX161": 22811, - "MercX162": 22812, - "MercX163": 22813, - "MercX164": 22814, - "MercX165": 22815, - "MercX166": 22816, - "MercX167": 22817 -}; diff --git a/d2bs/kolbot/sdk/Unit.d.ts b/d2bs/kolbot/sdk/Unit.d.ts deleted file mode 100644 index d80988e13..000000000 --- a/d2bs/kolbot/sdk/Unit.d.ts +++ /dev/null @@ -1,59 +0,0 @@ -/************************************* - * Unit description * - * Needs expansion * - *************************************/ -type ItemType = 4; -declare class Item extends Unit { - public type: ItemType; - getFlags() :number; - getFlag(flag: number) :boolean; - shop():boolean; - getItemCost() :number; -} - -type UnitType = 0 | 1 | 2 | 3 | 4 | 5; -type MonsterType = 1; - -declare class Monster extends Unit{ - public type: MonsterType; - getEnchant(type: number):boolean; -} - -declare class Merc extends Monster{ - -} -declare class Unit { - type : UnitType; - getNext() : Unit|false; - cancel() : void; - repair() : boolean; - useMenu() : boolean; - interact() : boolean; - interact(area: number) : boolean; - getItem(classId?: number,mode?: number, unitId?: number) : Unit|false; - getItem(name?: string,mode?: number, unitId?: number) : Unit|false; - getItems() : Item[]|false; - getMerc() : Merc ; - getMercHP() : number| false; - - // me.getSkill(0-4); // - getSkill(type: 0|1|2|3|4) : number; - getSkill(skillId: number,type: 0 | 1, item?: Item) : number; - - getParent() : Unit|false ; - overhead(msg: string) : void ; - - getStat(index: number,subid?: number):number[]|number|false ; - getState(index: number,subid?: number):number[]|number|false ; - - setSkill() ; - move(x: number, y: number) ; - getQuest(quest:number, subid: number) ; - getMinionCount() : number ; -} - -declare class me { - revive() : void; - getRepairCost() :number; -} - diff --git a/d2bs/kolbot/sdk/globals.d.ts b/d2bs/kolbot/sdk/globals.d.ts index 17bc20b6c..72793729a 100644 --- a/d2bs/kolbot/sdk/globals.d.ts +++ b/d2bs/kolbot/sdk/globals.d.ts @@ -1,262 +1,1381 @@ -declare type PathNode = { x: number, y: number } +// @ts-nocheck +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +declare global { + interface Error { + fileName: string; + lineNumber: number; + } + + interface ArrayConstructor { + /** + * Creates a new Array instance with a variable number of elements passed as arguments. + * + * @param {...T[]} items The elements to include in the array. + * ```ts + * const arr = Array.of(1, 2, 3, 4, 5); + * ``` + * @returns {Array} A new array with the provided elements. + */ + of(...items: T[]): T[]; + } + + interface Array { + includes(searchElement: T): boolean; + find(predicate: (value: T, index: number, obj: Int8Array) => boolean, thisArg?: any): T | undefined; + first(): T | undefined; + last(): T | undefined; + at(index: number): T | undefined; + findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number; + intersection(other: T[]): T[]; + difference(other: T[]): T[]; + symmetricDifference(other: T[]): T[]; + flat(depth?: number): T[]; + compactMap(callback: (value: T, index: number, obj: T[]) => any, thisArg?: any): any[]; + filterHighDistance(step: number): any[] + isEqual(t: T[]): boolean + remove(val: T): T[] + random(): T; + /** + * Creates a new array by sorting the elements of the original array. + * + * @param {(function(a: any, b: any): number) | undefined} compareFn Function used to determine the order of the elements. + * It is expected to return a negative value if the first argument is less than the second argument, zero if they're equal, and a positive + * value otherwise. If omitted, the elements are sorted in ascending, ASCII character order. + * ```ts + * [11,2,22,1].toSorted((a, b) => a - b) + * ``` + * @returns {Array} A new array with the sorted elements, leaving the orignal intact. + */ + toSorted(compareFn?: ((a: T, b: T) => number) | undefined): T[]; + /** + * Creates a new array with the elements of the original array in reversed order. + * Without mutating the original array. + * + * @returns {Array} A new array with the reversed elements. + */ + toReversed(): T[]; + /** + * Creates a new array by removing and/or adding elements from/to the original array. + * + * @param {number} start The index at which to start changing the array. + * @param {number} deleteCount The number of elements to remove starting from the `start` index. + * @param {...T[]} items The elements to add to the array. + * @returns {Array} A new array with the removed elements and optionally added elements. + */ + toSpliced(start: number, deleteCount?: number, ...items: T[]): T[]; + /** + * @description The with() method of Array instances is the copying version of using the bracket notation to change the value of a given index. + * It returns a new array with the element at the given index replaced with the given value. + * @param {number} index - Zero-based index at which to change the array, converted to an integer. + * @param {*} value - Any value to be assigned to the given index. + * @returns {Array} A new array with the element at index replaced with value. + * @throws {RangeError} If index >= array.length or index < -array.length. + */ + with(index: number, value: T): T[]; + } + + interface String { + lcsGraph(compareToThis: string): { a: string, b: string, graph: Uint16Array[]} + diffCount(a:string): number; + startsWith(a: string): boolean; + capitalize(downCase: boolean): string; + format(...pairs: Array): string; + padStart(targetLength: number, padString: string): string; + padEnd(targetLength: number, padString: string): string; + at(index: number): string | undefined; + unshift(str: string): string; + } + + interface StringConstructor { + static isEqual(str1: string, str2: string, caseSensitive?: boolean): boolean; + } + + interface ObjectConstructor { + assign(target: T, source: U): T & U; + assign(target: T, source1: U, source2: V): T & U & V; + assign(target: T, source1: U, source2: V, source3: W): T & U & V & W; + assign(target: object, ...sources: any[]): any; + values(source: object): any[]; + entries(source: object): any[][]; + is(o1: any, o2: any): boolean; + hasOwn(obj: object, prop: string): boolean; + } + + interface Object { + readonly distance: number; + path: PathNode[] | undefined; + + setPrototypeOf(obj: object, proto: object); + } + + interface Set { + union(other: Set): Set; + intersection(other: Set): Set; + difference(other: Set): Set; + symmetricDifference(other: Set): Set; + } + + interface Date { + dateStamp(): string; + } + + class ScriptError extends Error { + } + + type Act = 1 | 2 | 3 | 4 | 5; + type actType = { initialized: boolean, spot: { [data: string]: [number, number] } }; + type potType = 'hp' | 'mp' | 'rv'; + + class Hook { + color: number; + visible: boolean; + + /** + * The horizontal alignment + * - 0 - Left + * - 1 - Right + * - 2 - Center + */ + align: number; + + /** + * The z-order of the Hook (what it covers up and is covered by). + */ + zorder: number; + + /** + * How much of the controls underneath the Hook should show through. + */ + opacity: number; + + /** + * Whether the Hook is in automap coordinate space (true) or screen coordinate space (false). + */ + automap: boolean; + + remove(): void; + } + + class Line extends Hook { + constructor(x: number, y: number, x2: number, y2: number, color: number, visible: boolean, automap: boolean, ClickHandler?: Function, HoverHandler?: Function); + /** + * The first x coordinate of the Line. + */ + x: number; + + /** + * The first y coordinate of the Line. + */ + y: number; + + /** + * The end x coordinate of the Line. + */ + x2: number; + + /** + * The end y coordinate of the Line. + */ + y2: number; + } + + class Text extends Hook { + constructor( + text: string, + x: number, + y: number, + color: number, + font: number, + align: number, + automap: boolean, + ClickHandler?: Function, + HoverHandler?: Function + ); + text: string; + /** + * The x coordinate (left) of the Text. + */ + x: number; + + /** + * The y coordinate (top) of the Text. + */ + y: number; + } + + class Box extends Hook { + constructor(x: number, y: number, xsize: number, ysize: number, color: number, opacity: number, align: number, automap: boolean, ClickHandler?: Function, HoverHandler?: Function); + /** + * The x coordinate (left) of the Box. + */ + x: number; + + /** + * The y coordinate (top) of the Box. + */ + y: number; + + /** + * The xsize (width) of the Box. + */ + xsize: number; + + /** + * The ysize (height) of the Box. + */ + ysize: number; + } + + class Frame extends Box { + } + + /** + * @todo Figure out what each of these actually returns to properly document them + */ + class FileClass { + readable: boolean; + writable: boolean; + seekable: boolean; + mode: number; + binaryMode: boolean; + length: number; + path: string; + position: number; + eof: boolean; + accessed: number; + created: number; + modified: number; + autoflush: boolean; + + static open(path: string, mode: number): File; + close(): File; + reopen(): File; + read(count: number): string[]; + read(count: number): ArrayBuffer[]; + readLine(): string; + readAllLines(): string[]; + readAll(): string; + write(): void; + seek(n: number): any; + seek(n: number, isLines: boolean, fromStart: boolean): any; + flush(): void; + reset(): void; + end(): void; + } + const FILE_READ: 0; + const FILE_WRITE: 1; + const FILE_APPEND: 2; + + const FileTools: { + readText(filename: string) + writeText(filename: string, data: string) + appendText(filename: string, data: string) + exists(filename: string): boolean; + remove(filename: string): boolean; + } + + function getCollision(area: number, x: number, y: number, x2: number, y2: number) + + function getDistance(unit: PathNode, other: PathNode): number; + function getDistance(unit: PathNode, x: number, y: number): number; + + /************************************* + * Unit description * + * Needs expansion * + *************************************/ + + type UnitType = 0 | 1 | 2 | 3 | 4 | 5; + interface Unit { + readonly type: UnitType; + readonly classid: number; + readonly mode: number; + readonly name: string; + readonly act: any; + readonly gid: number; + readonly x: number; + readonly y: number; + readonly area: number; + readonly hp: number; + readonly hpmax: number; + readonly mp: number; + readonly mpmax: number; + readonly stamina: number; + readonly staminamax: number; + readonly charlvl: number; + readonly owner: number; + readonly ownertype: number; + readonly uniqueid: number; + } + + class Unit { + readonly attackable: boolean; + readonly dead: boolean; + readonly islocked: boolean; + readonly distance: number; + + readonly targetx: number; + readonly targety: number; + readonly idle: boolean; + readonly isPlayer: boolean; + readonly isNPC: boolean; + readonly isMonster: boolean; + readonly attackable: boolean; + readonly rawStrength: number; + readonly rawDexterity: number; + readonly fireRes: number; + readonly coldRes: number; + readonly lightRes: number; + readonly poisonRes: number; + readonly hpPercent: number; + readonly prettyPrint: string; + + // D2BS built in + getNext(): Unit | false; + interact(): boolean; + interact(area: number): boolean; + getItem(classId?: number, mode?: number, unitId?: number): ItemUnit | false; + getItem(name?: string, mode?: number, unitId?: number): ItemUnit | false; + getItems(...args: any[]): ItemUnit[] | false; + getMerc(): MercUnit; + getMercHP(): number | false; + /** + * @param type - + * - `me.getSkill(0)` : Name of skill on right hand + * - `me.getSkill(1)` : Name of skill on left hand + * - `me.getSkill(2)` : ID of skill on right hand + * - `me.getSkill(3)` : ID of skill on left hand + * - `me.getSkill(4)` : Array of all skills in format [skillId, hardPoints, softPoints, ...repeat] + */ + getSkill(type: 0 | 1 | 2 | 3 | 4): number | number[]; + getSkill(skillId: number, type: 0 | 1, item?: ItemUnit): number; + getStat(index: number, subid?: number, extra?: number): number; + getState(index: number, subid?: number): boolean; + getQuest(quest: number, subid: number): number + getParent(): Unit | string; + getMinionCount(): number; + + // additions from kolbot + getStatEx(one: number, sub?: number): number; + getItemsEx(classId?: number, mode?: number, unitId?: number): ItemUnit[]; + getItemsEx(name?: string, mode?: number, unitId?: number): ItemUnit[]; + inArea(area: number): boolean; + checkForMobs(givenSettings: { + range?: number; + count?: number; + coll?: number; + spectype: number + }): boolean + } + + type PlayerType = 0; + class Player extends Unit { + public type: PlayerType; + } + + type MonsterType = 1; + interface Monster extends Unit { + } + + class Monster extends Unit { + public type: MonsterType; + readonly isChampion: boolean; + readonly isUnique: boolean; + readonly isMinion: boolean; + readonly isSuperUnique: boolean; + readonly isSpecial: boolean; + readonly isWalking: boolean; + readonly isRunning: boolean; + readonly isMoving: boolean; + readonly isChilled: boolean; + readonly isFrozen: boolean; + readonly currentVelocity: number; + readonly isPrimeEvil: boolean; + readonly isBoss: boolean; + readonly isGhost: boolean; + readonly isDoll: boolean; + readonly isMonsterObject: boolean; + readonly isMonsterEgg: boolean; + readonly isMonsterNest: boolean; + readonly isBaalTentacle: boolean; + readonly isShaman: boolean; + readonly isUnraveler: boolean; + readonly isFallen: boolean; + readonly isBeetle: boolean; + readonly extraStrong: boolean; + readonly extraFast: boolean; + readonly cursed: boolean; + readonly magicResistant: boolean; + readonly fireEnchanted: boolean; + readonly lightningEnchanted: boolean; + readonly coldEnchanted: boolean; + readonly manaBurn: boolean; + readonly teleportation: boolean; + readonly spectralHit: boolean; + readonly stoneSkin: boolean; + readonly multiShot: boolean; + readonly charlvl: number; + readonly spectype: number; + readonly curseable: boolean; + readonly scareable: boolean; + readonly attacking: boolean; + readonly fireRes: number; + readonly coldRes: number; + readonly lightRes: number; + readonly poisonRes: number; + resPenalty: number; + + getEnchant(type: number): boolean; + hasEnchant(...enchants: number): boolean + } + + class NPCUnit extends Unit { + public type: MonsterType; + readonly itemcount: number; + + openMenu(): boolean; + useMenu(): boolean; + startTrade: (mode: any) => (any | boolean); + } + + class MercUnit extends Monster { + equip(destination: number | undefined, item: ItemUnit) + } + + interface ObjectUnit extends Unit { + } + + type ObjectType = 2; + class ObjectUnit extends Unit { + public type: ObjectType; + objtype: number; + openUnit(): boolean; + useUnit(targetArea?: number): boolean; + } + + type MissileType = 3; + class Missile extends Unit { + public readonly type: MissileType; + hits(position: PathNode): boolean; + } + + type ItemType = 4; + interface ItemUnit extends Unit { + castChargedSkill(skillId: number, target?: Unit): boolean; + castChargedSkill(skillId: number, x: number, y: number): boolean; + } + + class ItemUnit extends Unit { + // todo define item modes + public readonly type: ItemType; + readonly code: string; + readonly prefixes: string[]; + readonly suffixes: string[]; + readonly prefixnum: number; + readonly suffixnum: number; + readonly prefixenums: number[]; + readonly suffixnums: number[]; + readonly fname: string; + readonly quality: number; + readonly node: number; + readonly location: number; + readonly sizex: number; + readonly sizey: number; + readonly itemType: number; + readonly bodylocation: number; + readonly ilvl: number; + readonly lvlreq: number; + readonly gfx: number; + readonly description: string; + + // additional, not from d2bs + readonly identified: boolean; + readonly isEquipped: boolean + readonly dexreq: number; + readonly strreq: number; + readonly charclass: number; + readonly isInInventory: boolean; + readonly isInStash: boolean; + readonly isInCube: boolean; + readonly isInStorage: boolean; + readonly isInBelt: boolean; + readonly isOnMain: boolean; + readonly isOnSwap: boolean; + readonly runeword: boolean; + readonly questItem: boolean; + readonly ethereal: boolean; + readonly twoHanded: boolean + readonly oneOrTwoHanded: boolean; + readonly strictlyTwoHanded: boolean; + readonly sellable: boolean; + readonly lowQuality: boolean; + readonly normal: boolean; + readonly superior: boolean; + readonly magic: boolean; + readonly set: boolean; + readonly rare: boolean; + readonly unique: boolean; + readonly crafted: boolean; + readonly sockets: number; + readonly onGroundOrDropping: boolean; + readonly isShield: boolean; + readonly isAnni: boolean; + readonly isTorch: boolean; + readonly isGheeds: boolean; + readonly durabilityPercent: number; + readonly isCharm: boolean; + readonly gold: number; + + getColor(): number; + getBodyLoc(): number[]; + getFlags(): number; + getFlag(flag: number): boolean; + // shop(mode: ShopModes): boolean; + getItemCost(type?: 0 | 1 | 2): number; + sell(): boolean; + drop(): boolean; + equip(slot?: number): boolean; + buy(shift?: boolean, gamble?: boolean): boolean; + sellOrDrop():void + toCursor():boolean + use(): boolean; + } + + type TileType = 5; + class Tile extends Unit { + public type: TileType; + useUnit(targetArea?: number): boolean; + } + + type GetOwnedSettings = { + itemType?: number, + classid?: number, + mode?: number, + quality?: number, + sockets?: number, + location?: number, + ethereal?: boolean, + cb?: (item: ItemUnit) => boolean, + }; + + interface MeType extends Unit { + public type: PlayerType; + readonly account: string; + readonly charname: string; + readonly diff: 0 | 1 | 2; + readonly maxdiff: 0 | 1 | 2; + readonly gamestarttime: number; + readonly gametype: 0 | 1; + readonly itemoncursor: boolean; + readonly ladder: number; + readonly ping: number; + readonly fps: number; + readonly locale: number; + readonly playertype: 0|1; + readonly realm: string; + readonly realmshort: string; + readonly mercrevivecost: number; + chickenhp: number; + chickenmp: number; + quitonhostile: boolean; + readonly gameReady: boolean; + readonly profile: string; + readonly pid: number; + readonly charflags: number; + readonly screensize: number; + readonly windowtitle: string; + readonly ingame: boolean; + quitonerror: boolean; + maxgametime: number; + readonly gamepassword: string; + readonly gamestarttime: number; + readonly gamename: string; + readonly gameserverip: string; + readonly itemcount: number; + readonly classid: 0 | 1 | 2 | 3 | 4 | 5 | 6; + readonly weaponswitch: 0|1; + readonly gameReady: boolean; + blockMouse: boolean; + blockKeys: boolean; + runwalk: number; + automap: boolean; + + readonly expansion: boolean; + readonly classic: boolean; + readonly softcore: boolean; + readonly hardcore: boolean; + readonly normal: boolean; + readonly nightmare: boolean; + readonly hell: boolean; + readonly sorceress: boolean; + readonly amazon: boolean; + readonly necromancer: boolean; + readonly paladin: boolean; + readonly barbarian: boolean; + readonly assassin: boolean; + readonly druid: boolean; + readonly hpPercent: number; + readonly mpPercent: number; + readonly gold: number; + readonly inTown: boolean; + readonly highestAct: 1 | 2 | 3 | 4 | 5; + readonly staminaPercent: number; + readonly staminaDrainPerSec: number; + readonly staminaTimeLeft: number; + readonly staminaMaxDuration: number; + readonly inShop: boolean; + readonly skillDelay: boolean; + readonly highestAct: 1 | 2 | 3 | 4 | 5; + readonly highestQuestDone: number; + readonly den: boolean; + readonly bloodraven: boolean; + readonly smith: boolean; + readonly imbue: boolean; + readonly cain: boolean; + readonly tristram: boolean; + readonly countess: boolean; + readonly andariel: boolean; + readonly radament: boolean; + readonly horadricstaff: boolean; + readonly summoner: boolean; + readonly duriel: boolean; + readonly goldenbird: boolean; + readonly lamessen: boolean; + readonly gidbinn: boolean; + readonly blackendTemple: boolean; + readonly travincal: boolean; + readonly mephisto: boolean; + readonly izual: boolean; + readonly hellforge: boolean; + readonly diablo: boolean; + readonly shenk: boolean; + readonly larzuk: boolean; + readonly savebarby: boolean; + readonly barbrescue: boolean; + readonly anya: boolean; + readonly ancients: boolean; + readonly baal: boolean; + readonly cows: boolean; + readonly respec: boolean; + readonly diffCompleted: boolean; + wirtsleg: ItemUnit; + cube: ItemUnit; + shaft: ItemUnit; + amulet: ItemUnit; + staff: ItemUnit; + completestaff: ItemUnit; + eye: ItemUnit; + brain: ItemUnit; + heart: ItemUnit; + khalimswill: ItemUnit; + khalimsflail: ItemUnit; + malahspotion: ItemUnit; + scrollofresistance: ItemUnit; + readonly walking: boolean; + readonly running: boolean; + readonly deadOrInSequence: boolean; + readonly moving: boolean; + readonly FCR: number; + readonly FHR: number; + readonly FBR: number; + readonly IAS: number; + readonly shapeshifted: boolean; + readonly attacking: boolean; + /** + * @description max gold capacity (cLvl * 10000) + */ + readonly maxgold: number; + waypoints: boolean[]; + /** + * @private + * Don't use directly, use `me.shitList` + */ + _shitList: Set; + shitList: Set; + + // d2bs functions + overhead(msg: string): void; + repair(): boolean; + revive(): void; + move(x: number, y: number): boolean; + setSkill(): boolean; + cancel(number?: number): boolean; + getRepairCost(): number; + + // additions from kolbot + // #setters + walk(): void; + run(): void; + switchToPrimary(): boolean; + switchWeapons(slot: 0 | 1): boolean; + + // #getters + getPingDelay(): number; + getTpTool(): ItemUnit | null; + getIdTool(): ItemUnit | null; + getTome(id: number): ItemUnit | null; + getUnids(): ItemUnit[]; + getWeaponQuantity(weaponLoc: number): number; + getItemsForRepair(repairPercent: number, chargedItems: boolean): ItemUnit[]; + castingFrames(skillId: number, fcr?: number, charClass?: number): number; + castingDuration(skillId: number, fcr?: number, charClass?: number): number; + getOwned(itemInfo: ItemUnit | GetOwnedSettings): ItemUnit[]; + + // #checkers? + needBeltPots(): boolean; + needBufferPots(): boolean; + needPotions(): boolean; + needHealing(): boolean; + needKeys(): boolean; + needRepair(): string[]; + needMerc(): boolean; + needStash(): boolean; + needHealing(): boolean; + checkScrolls(id: number): number; + checkKeys(): number; + checkShard(): boolean; + canTpToTown(): boolean; + haveWaypoint(area: number): boolean; + accessToAct(act: number): boolean; + inArea(area: number): boolean; + haveSome(arg0: { name: number; equipped: boolean; }[]): any; + findItem(id?: number | string, mode?: number, location?: number, quality?: number): ItemUnit | boolean; + findItems(id?: number | string, mode?: number, location?: number): ItemUnit[]; + checkItem(itemInfo: { + classid?: number; + itemtype?: number; + quality?: number; + runeword?: boolean; + ethereal?: boolean; + name?: string | number; + equipped?: boolean | number; + }): {have: boolean; item: ItemUnit | null}; + usingShield(): boolean; + checkQuest(questId: number, state: number): boolean; + + // #actions + cleanUpInvoPotions(beltSize?: number): boolean; + equip(destination: number | undefined, item: ItemUnit); + cancelUIFlags(): boolean; + fieldID(): boolean; + castChargedSkill(skillId: number, target?: Unit): boolean; + castChargedSkill(skillId: number, x: number, y: number): boolean; + clearBelt(): boolean; + } + + const me: MeType + interface PathNode { + x: number; + y: number; + /** + * Distance from 'me' to this node + */ + readonly distance: number; + /** + * Distance from 'unit' to this node + * @param unit + */ + distanceTo(unit: Unit): number; + /** + * Walk Distance from 'me' to this node + */ + getWalkDistance(): number; + /** + * Walk Distance from 'node' to this node + * @param node + */ + getWalkDistanceTo(node: PathNode, area?: number): number; + mobCount(givenSettings: { range?: number, coll?: number, type?: number, ignoreClassids?: number[] }): number; + } + + function getUnit(type: 4, name?: string, mode?: number, unitId?: number): ItemUnit + function getUnit(type: 4, classId?: number, mode?: number, unitId?: number): ItemUnit + function getUnit(type: 1, name?: string, mode?: number, unitId?: number): Monster + function getUnit(type: 1, classId?: number, mode?: number, unitId?: number): Monster + function getUnit(type?: number, name?: string, mode?: number, unitId?: number): Unit + function getUnit(type?: number, classId?: number, mode?: number, unitId?: number): Unit + + function getPath(area: number, fromX: number, fromY: number, toX: number, toY: number, reductionType: 0 | 1 | 2, radius: number): PathNode[] | false + function getCollision(area: number, x: number, y: number) + function getMercHP(): number + function getCursorType(type: 1 | 3 | 6): boolean + function getCursorType(): number + function getSkillByName(name: string): number + function getSkillById(id: number): string + function getLocaleString(id: number): string + + // Never seen in the wild, not sure about arguments + function getTextSize(name: string, size: number) + function getThreadPriority(): number + function getUIFlag(flag: number): boolean + function getTradeInfo(mode: 0 | 1 | 2): boolean + function getWaypoint(id: number, noCache?: boolean): boolean + + class Script { + running: boolean; + name: string; + type: boolean; + threadid: number; + memory: number; + + getNext(): Script; + pause(): boolean; + resume(): boolean; + join(): void; + stop(): boolean; + send(): void; + } + + function getScript(name?: string | boolean): Script | false + function getScripts(): Script | false + + class Room { + area: number; + level: number; + number: number; + correcttomb: number; + x: number; + y: number; + xsize: number; + ysize: number; -declare function getUnit(type?: number, name?: string, mode?: number, unitId?: number) -declare function getUnit(type?: number, classId?: number, mode?: number, unitId?: number) - -declare function getPath(area: number, fromX: number, fromY: number, toX: number, toY: number, reductionType: 0 | 1, radius: number): PathNode | false - -declare function getCollision(area: number, x: number, y: number) - -declare function getMercHP(): number - -declare function getCursorType(type: 1 | 3 | 6): boolean - -declare function getSkillByName(name: string): number - -declare function getSkillById(id: number): string - -declare function getLocaleString(id: number) - -// Never seen in the wild, not sure about arguments -declare function getTextSize(name: string, size: number) - -declare function getThreadPriority(): number - -declare function getUIFlag(flag: number): boolean - -declare function getTradeInfo(mode: 0 | 1 | 2): boolean - -declare function getWaypoint(id: number): boolean - -declare class Script { - getNext(): Script -} - -declare function getScript(name?: string): Script | false - -declare function getScripts(): Script | false - -declare class Room { getNext(): Room | false; -} - -declare function getRoom(area: number, x: number, y: number): Room | false -declare function getRoom(x: number, y: number): Room | false -declare function getRoom(area: number): Room | false -declare function getRoom(): Room | false + getNearby(): Room[]; + isInRoom(unit: PathNode):boolean + isInRoom(x:number, y:number):boolean + } + + function getRoom(area: number, x: number, y: number): Room | false + function getRoom(x: number, y: number): Room | false + function getRoom(area: number): Room | false + function getRoom(): Room | false + + class Party { + x: number; + y: number; + area: number; + gid: number; + life: number; + partyflag: number; + partyid: number; + name: string; + classid: number; + level: number; + inTown: any; -declare class Party { getNext(): Party | false; -} - -declare function getParty(): Party | false - -declare class PresetUnit { - getNext(): PresetUnit | false -} - -declare function getPresetUnit(): PresetUnit | false + } + + function getParty(unit?: Unit): Party | false + + class PresetUnit { + id: number; + x: number; + y: number; + roomx: number; + roomy: number; + level: number; + readonly distance: number; + + getNext(): PresetUnit | false; + realCoords(): { id: number, area: number, x: number, y: number }; + } + + type PresetObject = { + area: number, + id: number, + type: number, + x: number, + y: number, + } + + function getPresetUnit(area?: number, objType?: number, classid?: number): PresetUnit | false + function getPresetUnit(area?: number, objType?: 2, classid?: number): PresetUnit | false + function getPresetUnits(area?: number, objType?: number, classid?: number): PresetUnit[] | false + + interface Exit extends Object { + x: number, + y: number, + type: number, + target: number, + tileid: number, + level: number, + } + + class Area { + name: string; + x: number; + xsize: number; + y: number; + ysize: number; + id: number; + exits: Exit[]; -declare function getPresetUnits(): PresetUnit[] | false - -declare class Area { getNext(): Area | false; + } + + function getArea(id?: number): Area | false + function getBaseStat(table: string, row: number, column: string | number): number | string + function getBaseStat(row: number, column: string): number | string + + /** + * @todo get a better understanding of Control + */ + class Control { + /** + * The text of the control + */ + text: string; + + /** + * The x coordinate of the control + */ + x: number; + + /** + * The y coordinate of the control + */ + y: number; + + /** + * The xsize (width) of the control + */ + xsize: number; + + /** + * The ysize (height) of the control + */ + ysize: number; + + /** + * The state of the control + * - Disabled - 4 + * @todo figure out the rest + */ + state: number; + + /** + * Return whether or not the Control holds a password (starred out text). + */ + password: boolean; + + /** + * The type of control + * - 1 - TextBox + * - 2 - Image + * - 3 - Image2 + * - 4 - LabelBox + * - 5 - ScrollBar + * - 6 - Button + * - 7 - List + * - 8 - Timer + * - 9 - Smack + * - 10 - ProgressBar + * - 11 - Popup + * - 12 - AccountList + */ + type: number; + cursorpos: any; + selectstart: any; + selectend: any; + disabled: number; + + getNext(): Control | undefined; + click(x?: number, y?: number): void; + setText(text: string): void; + getText(): string[]; + } + + type Profile = { + type: number, + ip: number, + username: string, + gateway: string, + character: string, + difficulty: number, + maxloginTime: number, + maxCharacterSelectTime: number, + } + function Profile(): Profile; + + class SQLite { + constructor(database: string, isNew: boolean); + execute(query: string): boolean; + query(query: string): SQLiteQuery; + lastRowId: number; + close(): void; + } + + class SQLiteQuery { + next(): boolean; + ready: boolean; + getColumnValue(index: number): any; + } + + function getControl(type?: number, x?: number, y?: number, xsize?: number, ysize?: number): Control | false + function getControls(type?: number, x?: number, y?: number, xsize?: number, ysize?: number): Control[] + function getPlayerFlag(meGid: number, otherGid: number, type: number): boolean + function getTickCount(): number + function getInteractedNPC(): NPCUnit | false + function getIsTalkingNPC(): boolean + function getDialogLines(): { handler() }[] | false + function print(what: string): void + function stringToEUC(arg: any): [] + function utf8ToEuc(arg: any): [] + function delay(ms: number): void + function load(file: string): boolean + function isIncluded(file: string): boolean + function include(file: string): boolean + function stacktrace(): true + function rand(from: number, to: number): number + function copy(what: string): void + function paste(): string + + function sendCopyData(noIdea: null, handle: number | string, mode: number, data: string): void; + + function sendDDE() + function keystate() + + type eventName = 'gamepacket' | 'scriptmsg' | 'copydata' | 'keyup' | 'keydown' | 'itemaction' | 'chatmsg'; + + function addEventListener(eventType: 'gamepacket', callback: ((bytes: ArrayBufferLike) => boolean)): void + function addEventListener(eventType: 'scriptmsg', callback: ((data: string | object | number) => void)): void + function addEventListener(eventType: 'copydata', callback: ((mode: number, msg: string) => void)): void + function addEventListener(eventType: 'itemaction', callback: ((gid: number, mode?: number, code?: string, global?: true) => void)): void + function addEventListener(eventType: 'keyup' | 'keydown', callback: ((key: number|string) => void)): void + function addEventListener(eventType: 'chatmsg', callback: ((nick: string, msg: string) => void)): void + function addEventListener(eventType: eventName, callback: ((...args: any) => void)): void + + function removeEventListener(eventType: 'gamepacket', callback: ((bytes: ArrayBufferLike) => boolean)): void + function removeEventListener(eventType: 'scriptmsg', callback: ((data: string | object | number) => void)): void + function removeEventListener(eventType: 'copydata', callback: ((mode: number, msg: string) => void)): void + function removeEventListener(eventType: 'itemaction', callback: ((gid: number, mode?: number, code?: string, global?: true) => void)): void + function removeEventListener(eventType: 'keyup' | 'keydown', callback: ((key: number) => void)): void + function removeEventListener(eventType: 'chatmsg', callback: ((nick: string, msg: string) => void)): void + function removeEventListener(eventType: eventName, callback: ((...args: any) => void)): void + + function clearEvent() + function clearAllEvents() + function js_strict() + function version(): number + function scriptBroadcast(what: string | object): void + function sqlite_version() + function sqlite_memusage() + + type directory = { + getFiles(): string[]; + getFolders(): string[]; + create(what?: string): boolean; + }; + function dopen(what?: string): directory | false; + function debugLog(text: string): void + function showConsole(): void + function hideConsole(): void + + // out of game functions + function login(name?: string): void + function selectCharacter() + function createGame() + function joinGame() + function addProfile() + function getLocation():number + function loadMpq() + + // game functions that don't have anything to do with gathering data + function submitItem(): void + function getMouseCoords() + function copyUnit(unit: S): S + function clickMap(type: 0 | 1 | 2 | 3, shift: 0 | 1, x: number, y: number) + function acceptTrade() + function tradeOk() + function beep(id?: number) + + function clickItem(where: 0 | 1 | 2, bodyLocation: number) + function clickItem(where: 0 | 1 | 2, item: ItemUnit) + function clickItem(where: 0 | 1 | 2, x: number, y: number) + function clickItem(where: 0 | 1 | 2, x: number, y: number, location: number) + + function getDistance(a: Unit, b: Unit): number + function getDistance(a: Unit, toX: number, toY: number): number + function getDistance(fromX: number, fromY: number, b: Unit): number + function getDistance(fromX: number, fromY: number, toX: number, toY: number): number + + function gold(amount: number, changeType?: 0 | 1 | 2 | 3 | 4): void + function checkCollision(a: Unit, b: Unit, type: number): boolean + function playSound(num: number): void + function quit(): never + function quitGame(): never + function say(what: string, force?: boolean): void + /** + * Use when you want to force something to be said in chat and don't know if LocalChat is being used + * @param what + */ + function _say(what: string): void + function clickParty(player: Party, type: 0 | 1 | 2 | 3 | 4) + function weaponSwitch(): void + function transmute(): void + function useStatPoint(type: number): void + function useSkillPoint(type: number): void + function takeScreenshot(): void + function moveNPC(npc: Monster, x: number, y: number): void + + function getPacket(buffer: ArrayBuffer): void + function getPacket(...args: { size: number, data: number }[]): void + + function sendPacket(buffer: ArrayBuffer): void + function sendPacket(...number: number[]): void + + function getIP(): string + function sendKey(key: number): void + function revealLevel(unknown: true): void + + // hash functions + function md5(str: string): string + function sha1(str: string): string + function sha256(str: string): string + function sha384(str: string): string + function sha512(str: string): string + function md5_file(str: string): string + function sha1_file(str: string): string + function sha256_file(str: string): string + function sha384_file(str: string): string + function sha512_file(str: string): string + + interface Console { + static log(...whatever: any[]): void + static debug(...whatever: any[]): void + static warn(...whatever: any[]): void + static error(...whatever: any[]): void + static time(name: string): void; + static timeEnd(name: string): void; + static trace(): void; + static info(start: boolean, msg: string, timer: string): void; + } + const console: Console; + + class File { + public readonly readable: boolean; + public readonly writeable: boolean; + public readonly seekable: boolean; + public readonly mode: number; + public readonly binaryMode: boolean; + public readonly length: number; + public readonly path: string; + public position: number; + public readonly eof: boolean; + public readonly accessed: number; + public readonly created: number; + public readonly modified: number; + public autoflush: boolean; + + public static open(filePath: string, mode?: number): File; + public static read(count: number): string; + public static read(count: number): Uint8Array; + public close(): File; + public reopen(): File; + public readLine(): string; + public readAllLines(): string[]; + public readAll(): string; + public write(...args: any[]): File; + public seek(n: number): File; + public seek(n: number, isLines: boolean, fromStart: boolean): File; + public flush(): File; + public reset(): File; + public end(): File; + } + + function includeIfNotIncluded(file?: string): boolean; + function includeCoreLibs(obj: { exclude: string[] }): boolean; + function includeSystemLibs(): boolean; + function clone(obj: Date | any[] | object): ThisParameterType; + function copyObj(from: object): object; + + interface StarterConfig { + MinGameTime: number, + MaxGameTime?: number, + PingQuitDelay: number, + CreateGameDelay: number, + ResetCount: number + CharacterDifference: number, + MaxPlayerCount: number, + StopOnDeadHardcore: boolean, + + JoinChannel: string, + FirstJoinMessage: string, + ChatActionsDelay: number, + AnnounceGames: boolean, + AnnounceMessage: string, + AfterGameMessage: string, + + InvalidPasswordDelay: number, // Minutes to wait after getting Invalid Password message + VersionErrorDelay: number, // Seconds to wait after 'unable to identify version' message + SwitchKeyDelay: number, // Seconds to wait before switching a used/banned key or after realm down + CrashDelay: number, // Seconds to wait after a d2 window crash + FTJDelay: number, // Seconds to wait after failing to create a game + RealmDownDelay: number, // Minutes to wait after getting Realm Down message + UnableToConnectDelay: number, // Minutes to wait after Unable To Connect message + TCPIPNoHostDelay: number, // Seconds to wait after Cannot Connect To Server message + CDKeyInUseDelay: number, // Minutes to wait before connecting again if CD-Key is in use. + ConnectingTimeout: number, // Seconds to wait before cancelling the 'Connecting...' screen + PleaseWaitTimeout: number, // Seconds to wait before cancelling the 'Please Wait...' screen + WaitInLineTimeout: number, // Seconds to wait before cancelling the 'Waiting in Line...' screen + WaitOutQueueRestriction: boolean, // Wait out queue if we are restricted, queue time > 10000 + WaitOutQueueExitToMenu: boolean, // Wait out queue restriction at D2 Splash screen if true, else wait out in lobby + GameDoesNotExistTimeout: number, // Seconds to wait before cancelling the 'Game does not exist.' screen + } + + interface ProfileInfo { + profile: string, + account: string, + charName: string, + difficulty: string, + tag: string, + realm: string, + } + + interface StarterInterface { + Config: StarterConfig, + useChat: boolean, + pingQuit: boolean, + inGame: boolean, + firstLogin: boolean, + firstRun: boolean, + isUp: "yes"|"no", + loginRetry: number, + deadCheck: boolean, + chatActionsDone: boolean, + gameStart: boolean, + gameCount: number, + lastGameStatus: string, + handle: number | null, + connectFail: boolean, + connectFailRetry: number, + makeAccount: false, + channelNotify: boolean, + chanInfo: { + joinChannel: string, + firstMsg: string, + afterMsg: string, + announce: boolean, + }, + gameInfo: { + error: string, + gameName?: string, + gamePass?: string, + difficulty?: string, + crashInfo: { + currScript: number, + area: number, + }, + switchKeys: boolean, + }, + joinInfo: {}, + profileInfo: ProfileInfo, + + sayMsg(string: string): void, + timer(tick: number): string, + locationTimeout(time: number, location: number): boolean, + setNextGame(gameInfo: { gameName: string }): void, + updateCount(): void, + scriptMsgEvent(msg: string): void, + receiveCopyData(mode: number, msg: string | object): void, + randomString(len?: number, useNumbers?: boolean): string, + randomNumberString(len?: number): string, + } + + const Starter: StarterInterface; + + namespace Time { + /** + * Converts seconds to milliseconds. + * + * @param {number} [seconds=0] - The number of seconds to convert. + * @returns {number} - The equivalent time in milliseconds. + */ + function seconds(seconds: number): number; + + /** + * Converts minutes to milliseconds. + * + * @param {number} [minutes=0] - The number of minutes to convert. + * @returns {number} - The equivalent time in milliseconds. + */ + function minutes(minutes: number): number; + + /** + * Formats milliseconds into a "HH:MM:SS" string. + * + * @param {number} [ms=0] - The time in milliseconds to format. + * @returns {string} - The formatted time string. + */ + function format(ms: number): string; + + /** + * Converts milliseconds to seconds. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in seconds. + */ + function toSeconds(ms: number): number; + + /** + * Converts milliseconds to minutes. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in minutes. + */ + function toMinutes(ms: number): number; + + /** + * Converts milliseconds to hours. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in hours. + */ + function toHours(ms: number): number; + + /** + * Converts milliseconds to days. + * + * @param {number} [ms=0] - The time in milliseconds to convert. + * @returns {number} - The equivalent time in days. + */ + function toDays(ms: number): number; + + /** + * Calculates the elapsed time from a given timestamp. + * + * @param {number} [ms=0] - The starting time in milliseconds. + * @returns {number} - The elapsed time in milliseconds. + */ + function elapsed(start: number): number; + } + + type PrimitiveType = "undefined" | "object" | "boolean" | "number" | "bigint" | "string" | "symbol" | "function" | "array"; + /** + * Checks if the value matches the expected type. + * + * @param val - The value to be checked. + * @param type - The expected type as a string. + * @returns {boolean} - Returns true if the value matches the expected type, otherwise false. + */ + function isType(val: any, type: T): val is PrimitiveTypeMap[T]; } - -declare function getArea(): Area | false - -declare function getBaseStat(table: string, row: number, column: string): number | string -declare function getBaseStat(row: number, column: string): number | string - -declare class Control { - -} - -declare function getControl(type?: number, x?: number, y?: number, xsize?: number, ysize?: number): Control | false - -declare function getControls(type?: number, x?: number, y?: number, xsize?: number, ysize?: number): Control[] - -declare function getPlayerFlag(meGid: number, otherGid: number, type: number): boolean - -declare function getTickCount(): number - -declare function getInteractedNPC(): Monster | false - -declare function getIsTalkingNPC(): boolean - -declare function getDialogLines(): { handler() }[] | false - -declare function print(what: string): void - -declare function stringToEUC(arg): [] - -declare function utf8ToEuc(arg): [] - -declare function delay(ms: number): void - -declare function load(file: string): boolean - -declare function isIncluded(file: string): boolean - -declare function include(file: string): boolean - -declare function stacktrace(): true - -declare function rand(from: number, to: number): number - -declare function copy(what: string): void - -declare function paste(): string - -declare function sendCopyData(noIdea: null, handle: number, mode: number, data: string) -declare function sendCopyData(noIdea: null, handle: string, mode: number, data: string) - -declare function sendDDE() - -declare function keystate() - -declare type eventName = 'gamepacket' | 'scriptmsg' | 'copydata' | 'keyup' | 'keydown' - -declare function addEventListener(eventType: 'gamepacket', callback: ((bytes: ArrayBufferLike) => boolean)): void -declare function addEventListener(eventType: 'scriptmsg', callback: ((data: string | object | number) => void)): void -declare function addEventListener(eventType: 'copydata', callback: ((mode: number, msg: string) => void)): void -declare function addEventListener(eventType: 'itemaction', callback: ((gid:number,mode?:number,code?:string,global?:true) => void)): void -declare function addEventListener(eventType: 'keyup' | 'keydown', callback: ((key: number) => void)): void -declare function addEventListener(eventType: 'chatmsg', callback: ((nick: string,msg:string) => void)): void -declare function addEventListener(eventType: eventName, callback: ((...args: any) => void)): void - -declare function removeEventListener(eventType: 'gamepacket', callback: ((bytes: ArrayBufferLike) => boolean)): void -declare function removeEventListener(eventType: 'scriptmsg', callback: ((data: string | object | number) => void)): void -declare function removeEventListener(eventType: 'copydata', callback: ((mode: number, msg: string) => void)): void -declare function removeEventListener(eventType: 'itemaction', callback: ((gid:number,mode?:number,code?:string,global?:true) => void)): void -declare function removeEventListener(eventType: 'keyup' | 'keydown', callback: ((key: number) => void)): void -declare function removeEventListener(eventType: 'chatmsg', callback: ((nick: string,msg:string) => void)): void -declare function removeEventListener(eventType: eventName, callback: ((...args: any) => void)): void - -declare function clearEvent() - -declare function clearAllEvents() - -declare function js_strict() - -declare function version():number - -declare function scriptBroadcast(what:string|object):void - -declare function sqlite_version() - -declare function sqlite_memusage() - -declare function dopen(path:string):false|{create(what:string)} - -declare function debugLog(text:string):void - -declare function showConsole():void - -declare function hideConsole():void - -// out of game functions - -declare function login(name?:string):void - -// -// declare function createCharacter()) -// this function is not finished - -declare function selectCharacter() - -declare function createGame() - -declare function joinGame() - -declare function addProfile() - -declare function getLocation() - -declare function loadMpq() - -// game functions that don't have anything to do with gathering data - -declare function submitItem():void - -declare function getMouseCoords() - -declare function copyUnit(unit: Unit):Unit - -declare function clickMap(type: 0|1|2|3,shift:0|1,x:number,y:number) - -declare function acceptTrade() - -declare function tradeOk() - -declare function beep(id?:number) - -declare function clickItem(where: 0|1|2,bodyLocation:number) -declare function clickItem(where: 0|1|2,item:Item) -declare function clickItem(where: 0|1|2,x:number,y:number) -declare function clickItem(where: 0|1|2,x:number,y:number,location:number) - -declare function getDistance(a: Unit,b: Unit):number -declare function getDistance(a: Unit,toX:number, toY: number):number -declare function getDistance(fromX: number, fromY: number,b: Unit):number -declare function getDistance(fromX: number, fromY: number,toX:number, toY: number):number - -declare function gold(amount: number,changeType?: 0|1|2|3|4):void - -declare function checkCollision(a: Unit,b:Unit,type:number):boolean - -declare function playSound(num:number):void - -declare function quit():never - -declare function quitGame():never - -declare function say(what:string):void - -declare function clickParty(player: Party,type: 0|1|2|3|4) - -declare function weaponSwitch():void - -declare function transmute():void - -declare function useStatPoint(type:number):void - -declare function useSkillPoint(type:number):void - -declare function takeScreenshot():void - -declare function moveNPC(npc:Monster,x:number,y:number):void - -declare function getPacket(buffer: DataView):void -declare function getPacket(...args: {size:number, data: number}[]):void - -declare function sendPacket(buffer: DataView):void -declare function sendPacket(...args: {size:number, data: number}[]):void - -declare function getIP():string - -declare function sendKey(key:number):void - -declare function revealLevel(unknown:true):void - -// hash functions - -declare function md5(str:string):string - -declare function sha1(str:string):string - -declare function sha256(str:string):string - -declare function sha384(str:string):string - -declare function sha512(str:string):string - -declare function md5_file(str:string):string - -declare function sha1_file(str:string):string - -declare function sha256_file(str:string):string - -declare function sha384_file(str:string):string - -declare function sha512_file(str:string):string \ No newline at end of file +export {}; diff --git a/d2bs/kolbot/sdk/Shrines.txt b/d2bs/kolbot/sdk/txt/Shrines.txt similarity index 100% rename from d2bs/kolbot/sdk/Shrines.txt rename to d2bs/kolbot/sdk/txt/Shrines.txt diff --git a/d2bs/kolbot/sdk/SuperUniques.txt b/d2bs/kolbot/sdk/txt/SuperUniques.txt similarity index 100% rename from d2bs/kolbot/sdk/SuperUniques.txt rename to d2bs/kolbot/sdk/txt/SuperUniques.txt diff --git a/d2bs/kolbot/sdk/areas.txt b/d2bs/kolbot/sdk/txt/areas.txt similarity index 100% rename from d2bs/kolbot/sdk/areas.txt rename to d2bs/kolbot/sdk/txt/areas.txt diff --git a/d2bs/kolbot/sdk/basestats.txt b/d2bs/kolbot/sdk/txt/basestats.txt similarity index 100% rename from d2bs/kolbot/sdk/basestats.txt rename to d2bs/kolbot/sdk/txt/basestats.txt diff --git a/d2bs/kolbot/sdk/bodylocations.txt b/d2bs/kolbot/sdk/txt/bodylocations.txt similarity index 100% rename from d2bs/kolbot/sdk/bodylocations.txt rename to d2bs/kolbot/sdk/txt/bodylocations.txt diff --git a/d2bs/kolbot/sdk/chests.txt b/d2bs/kolbot/sdk/txt/chests.txt similarity index 100% rename from d2bs/kolbot/sdk/chests.txt rename to d2bs/kolbot/sdk/txt/chests.txt diff --git a/d2bs/kolbot/sdk/enchants.txt b/d2bs/kolbot/sdk/txt/enchants.txt similarity index 100% rename from d2bs/kolbot/sdk/enchants.txt rename to d2bs/kolbot/sdk/txt/enchants.txt diff --git a/d2bs/kolbot/sdk/getskillinfo.txt b/d2bs/kolbot/sdk/txt/getskillinfo.txt similarity index 100% rename from d2bs/kolbot/sdk/getskillinfo.txt rename to d2bs/kolbot/sdk/txt/getskillinfo.txt diff --git a/d2bs/kolbot/sdk/itemflags.txt b/d2bs/kolbot/sdk/txt/itemflags.txt similarity index 100% rename from d2bs/kolbot/sdk/itemflags.txt rename to d2bs/kolbot/sdk/txt/itemflags.txt diff --git a/d2bs/kolbot/sdk/miscscreenmodes.txt b/d2bs/kolbot/sdk/txt/miscscreenmodes.txt similarity index 100% rename from d2bs/kolbot/sdk/miscscreenmodes.txt rename to d2bs/kolbot/sdk/txt/miscscreenmodes.txt diff --git a/d2bs/kolbot/sdk/modes.txt b/d2bs/kolbot/sdk/txt/modes.txt similarity index 100% rename from d2bs/kolbot/sdk/modes.txt rename to d2bs/kolbot/sdk/txt/modes.txt diff --git a/d2bs/kolbot/sdk/monster classID's.txt b/d2bs/kolbot/sdk/txt/monster classID's.txt similarity index 100% rename from d2bs/kolbot/sdk/monster classID's.txt rename to d2bs/kolbot/sdk/txt/monster classID's.txt diff --git a/d2bs/kolbot/sdk/npcmenuid.txt b/d2bs/kolbot/sdk/txt/npcmenuid.txt similarity index 100% rename from d2bs/kolbot/sdk/npcmenuid.txt rename to d2bs/kolbot/sdk/txt/npcmenuid.txt diff --git a/d2bs/kolbot/sdk/quests.txt b/d2bs/kolbot/sdk/txt/quests.txt similarity index 100% rename from d2bs/kolbot/sdk/quests.txt rename to d2bs/kolbot/sdk/txt/quests.txt diff --git a/d2bs/kolbot/sdk/roomstats.txt b/d2bs/kolbot/sdk/txt/roomstats.txt similarity index 100% rename from d2bs/kolbot/sdk/roomstats.txt rename to d2bs/kolbot/sdk/txt/roomstats.txt diff --git a/d2bs/kolbot/sdk/skills.txt b/d2bs/kolbot/sdk/txt/skills.txt similarity index 100% rename from d2bs/kolbot/sdk/skills.txt rename to d2bs/kolbot/sdk/txt/skills.txt diff --git a/d2bs/kolbot/sdk/states.txt b/d2bs/kolbot/sdk/txt/states.txt similarity index 100% rename from d2bs/kolbot/sdk/states.txt rename to d2bs/kolbot/sdk/txt/states.txt diff --git a/d2bs/kolbot/sdk/stats.txt b/d2bs/kolbot/sdk/txt/stats.txt similarity index 100% rename from d2bs/kolbot/sdk/stats.txt rename to d2bs/kolbot/sdk/txt/stats.txt diff --git a/d2bs/kolbot/sdk/stats_skills.txt b/d2bs/kolbot/sdk/txt/stats_skills.txt similarity index 100% rename from d2bs/kolbot/sdk/stats_skills.txt rename to d2bs/kolbot/sdk/txt/stats_skills.txt diff --git a/d2bs/kolbot/sdk/stats_tabs.txt b/d2bs/kolbot/sdk/txt/stats_tabs.txt similarity index 100% rename from d2bs/kolbot/sdk/stats_tabs.txt rename to d2bs/kolbot/sdk/txt/stats_tabs.txt diff --git a/d2bs/kolbot/sdk/superunique_presetunitids.txt b/d2bs/kolbot/sdk/txt/superunique_presetunitids.txt similarity index 100% rename from d2bs/kolbot/sdk/superunique_presetunitids.txt rename to d2bs/kolbot/sdk/txt/superunique_presetunitids.txt diff --git a/d2bs/kolbot/sdk/tile.d2l b/d2bs/kolbot/sdk/txt/tile.d2l similarity index 100% rename from d2bs/kolbot/sdk/tile.d2l rename to d2bs/kolbot/sdk/txt/tile.d2l diff --git a/d2bs/kolbot/sdk/uiflag.txt b/d2bs/kolbot/sdk/txt/uiflag.txt similarity index 100% rename from d2bs/kolbot/sdk/uiflag.txt rename to d2bs/kolbot/sdk/txt/uiflag.txt diff --git a/d2bs/kolbot/sdk/waypoints.txt b/d2bs/kolbot/sdk/txt/waypoints.txt similarity index 100% rename from d2bs/kolbot/sdk/waypoints.txt rename to d2bs/kolbot/sdk/txt/waypoints.txt diff --git a/d2bs/kolbot/sdk/types/Attack.d.ts b/d2bs/kolbot/sdk/types/Attack.d.ts new file mode 100644 index 000000000..9918695e1 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Attack.d.ts @@ -0,0 +1,78 @@ +export {}; +declare global { + interface AttackResult { + FAILED: 0, + SUCCESS: 1, + CANTATTACK: 2, // need to fix the ambiguity between this result and Failed + NEEDMANA: 3 + } + interface ClassAttack { + doAttack(unit: Monster, preattack?: boolean): AttackResult + afterAttack(any?: any): void + doCast(unit: Monster, timedSkill: number, untimedSkill: number): AttackResult + + // Self defined + decideSkill(unit: Monster, skipSkill?: number[]): { timed: number, untimed: number } + } + namespace Attack { + const infinity: boolean; + const auradin: boolean; + const monsterObjects: number[]; + const Result: AttackResult; + const _killed: Set; + function haveKilled(id: number | string): boolean; + function init(): void; + function checkSlot(slot?: 0 | 1): boolean; + function getPrimarySlot(): 0 | 1; + function getCustomAttack(unit: Unit): boolean | [number, number]; + function getCharges(): boolean; + function checkInfinity(): boolean; + function checkAuradin(): boolean; + function canTeleStomp(unit: Monster | Player): boolean; + function kill(classId: number | Unit): boolean; + function hurt(classId: string | number | Unit, percent: number): boolean; + function getScarinessLevel(unit: Unit): number; + function clear(range?: number, spectype?: number, bossId?: number | Unit, sortfunc?: Function, pickit?: boolean): boolean; + function clearClassids(...ids: number[]): boolean; + function getMob(classid: number, spectype: number, range: number, center: Unit | { + x: number; + y: number; + }): Monster[]; + function clearList(mainArg: Function | Unit[], sortFunc?: Function, refresh?: boolean): boolean; + function securePosition(x: number, y: number, range?: number, timer?: number, skipBlocked?: boolean, special?: boolean): void; + function markRoom(room: Room, color: number): void; + function countUniques(): void; + function storeStatistics(area: number): void; + function clearLevel(spectype?: number): boolean; + function sortMonsters(unitA: Unit, unitB: Unit): boolean; + function validSpot(x: number, y: number, skill?: number, unitid?: number): boolean; + function openChests(range: number, x?: number, y?: number): boolean; + function buildMonsterList(): [] | Monster[]; + function findSafeSpot(unit: Unit, distance: number, spread: number, range: number, ...args: any[]): { + x: number; + y: number; + }; + function deploy(unit: Monster, distance: any, spread: any, range: any, ...args: any[]): boolean; + function getMonsterCount(x: any, y: any, range: any, list: any): number; + function buildGrid(xmin: any, xmax: any, ymin: any, ymax: any, spread: any): { + x: any; + y: any; + coll: number; + }[]; + function skipCheck(unit: Monster): boolean; + function getSkillElement(skillId: number): false | "physical" | "fire" | "lightning" | "magic" | "cold" | "poison" | "holybolt" | "none"; + function getResist(unit: Monster, type: "physical" | "fire" | "lightning" | "magic" | "cold" | "poison" | "holybolt" | "none"): boolean; + function getLowerResistPercent(): number; + function getConvictionPercent(): number; + function checkResist(unit: Monster, val: any, maxres?: number): boolean; + function canAttack(unit: Monster): boolean; + function usingBow(): false | "bow" | "crossbow"; + function getIntoPosition(unit: Monster, distance: any, coll: any, walk: any): boolean; + function getNearestMonster(givenSettings?: {}): any; + function checkCorpse(unit: Monster): boolean; + function checkNearCorpses(unit: Monster, range?: number): any; + function whirlwind(unit: Monster | Player): boolean; + function doPreAttack(unit: Monster): AttackResult; + function doChargeCast(unit: Monster): boolean; + } +} diff --git a/d2bs/kolbot/sdk/types/AutoMule.d.ts b/d2bs/kolbot/sdk/types/AutoMule.d.ts new file mode 100644 index 000000000..3bf039648 --- /dev/null +++ b/d2bs/kolbot/sdk/types/AutoMule.d.ts @@ -0,0 +1,142 @@ +// @ts-nocheck +export {}; +declare global { + export type muleObj = { + /** + * - The name of mule profile in d2bot#. It will be started and stopped when needed. + */ + muleProfile: string; + /** + * - Account prefix. Numbers added automatically when making accounts. + */ + accountPrefix: string; + /** + * - Account password + */ + accountPassword: string; + /** + * - Character prefix. Suffix added automatically when making characters. + */ + charPrefix: string; + /** + * - Available options: "useast", "uswest", "europe", "asia" + */ + realm: string; + /** + * - expansion character + */ + expansion: boolean; + /** + * - ladder character + */ + ladder: boolean; + /** + * - Maximum number of mules to create per account (between 1 to 18) + */ + charsPerAcc: number; + /** + * - Game name and password of the mule game. Never use the same game name as for mule logger. + */ + muleGameName: string[]; + /** + * - List of profiles that will mule items. Example: enabledProfiles: ["profile 1", "profile 2"] + */ + enabledProfiles: string[]; + /** + * - Stop a profile prior to muling. Useful when running 8 bots without proxies. + */ + stopProfile: string; + /** + * - true = stopProfile key will get released on stop. useful when using 100% of your keys for botting. + */ + stopProfileKeyRelease: boolean; + /** + * - Trigger muling at the end of a game if used space in stash greater than or equal to given percent. + */ + usedStashTrigger: number; + /** + * - Trigger muling at the end of a game if used space in inventory greater than or equal to given percent. + */ + usedInventoryTrigger: number; + /** + * - Mule items that have been stashed at some point but are no longer in pickit. + */ + muleOrphans: boolean; + /** + * - Mule stays in game for continuous muling. muleProfile must be dedicated and started manually. + */ + continuousMule: boolean; + /** + * - Skip mule response check and attempt to join mule game. Useful if mule is shared and/or ran on different system. + */ + skipMuleResponse: boolean; + /** + * - Only log character when full, solves an issue with droppers attempting to use characters who are already in game + */ + onlyLogWhenFull: boolean; + }; + export const AutoMule: { + Mules: { + [x: string]: muleObj; + }; + TorchAnniMules: { + [x: string]: muleObj; + }; + getInfo(): boolean | muleObj + muleCheck(): void + getMule(): void + outOfGameCheck(): void + inGameCheck(): void + dropStuff(): void + matchItem(item: ItemUnit, list: any): void + getMuleItems(): ItemUnit[] + utilityIngredient(item: ItemUnit): void + cubingIngredient(item: ItemUnit): void + runewordIngredient(item: ItemUnit): void + dropCharm(dropAnni: any): void + }; + export namespace Mule { + let obj: muleObj; + let minGameTime: number; + let maxGameTime: number; + let continuous: boolean; + let makeNext: boolean; + let refresh: boolean; + let master: string; + let mode: number; + let startTick: number; + let status: string; + let statusString: string; + let masterStatus: { status: string }; + + function init(): void; + function gameRefresh(): void; + function ingameTimeout(): boolean; + function getMaster(info: { profile: string, mode: number }): { profile: string, mode: number } + function getMuleFilename(mode?: number, master: string): string; + function getMuleInfo(master?: string): { mode: number, obj: muleObj }[]; + }; + export namespace MuleData { + type MuleDataObj = { + account: string; + accNum: number; + character: string; + charNum: number; + realm: string; + expansion: boolean; + ladder: boolean; + fullChars: number[]; + torchChars: number[]; + }; + const _default: MuleDataObj; + let fileName: string; + function create(): void; + function read(): MuleDataObj; + function write(data: Partial): void; + function nextAccount(): string; + function nextChar(): string; + }; + export namespace LocationAction { + function run(): void; + }; +} diff --git a/d2bs/kolbot/sdk/types/Config.d.ts b/d2bs/kolbot/sdk/types/Config.d.ts new file mode 100644 index 000000000..50d296526 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Config.d.ts @@ -0,0 +1,551 @@ +/** +* @filename Config.js +* @author kolton +* @desc config loading and default config values storage +* +*/ + +declare global { + // interface Scripts { [data: string]: Partial | boolean } + interface Config { + init(notify: any): void; + Loaded: boolean; + DebugMode: { + Path: boolean, + Stack: boolean, + Memory: boolean, + Skill: boolean, + Town: boolean, + }; + StartDelay: number; + PickDelay: number; + AreaDelay: number; + MinGameTime: number; + MaxGameTime: number; + LifeChicken: number; + ManaChicken: number; + UseHP: number; + UseMP: number; + UseRejuvHP: number; + UseRejuvMP: number; + UseMercHP: number; + UseMercRejuv: number; + MercChicken: number; + IronGolemChicken: number; + HealHP: number; + HealMP: number; + HealStatus: boolean; + TownHP: number; + TownMP: number; + StackThawingPots: { + enabled: boolean; + quantity: number; + }; + StackAntidotePots: { + enabled: boolean; + quantity: number; + }; + StackStaminaPots: { + enabled: boolean; + quantity: number; + }; + AutoMap: boolean; + LastMessage: string; + UseMerc: boolean; + MercWatch: boolean; + LowGold: number; + StashGold: number; + FieldID: { + Enabled: boolean; + PacketID: boolean; + UsedSpace: number; + }; + DroppedItemsAnnounce: { + Enable: boolean; + Quality: any[]; + LogToOOG: boolean; + OOGQuality: any[]; + }; + CainID: { + Enable: boolean; + MinGold: number; + MinUnids: number; + }; + Inventory: number[][]; + LocalChat: { + Enabled: boolean; + Toggle: boolean; + Mode: number; + }; + Silence: boolean; + PublicMode: boolean; + PartyAfterScript: boolean; + Greetings: any[]; + DeathMessages: any[]; + Congratulations: any[]; + AnnounceGameTimeRemaing: boolean; + ShitList: boolean; + UnpartyShitlisted: boolean; + Leader: string; + QuitList: any[]; + QuitListMode: number; + QuitListDelay: any[]; + HPBuffer: number; + MPBuffer: number; + RejuvBuffer: number; + PickRange: number; + MakeRoom: boolean; + ClearInvOnStart: boolean; + FastPick: boolean; + ManualPlayPick: boolean; + OpenChests: { + Enabled: boolean; + Range: number; + Types: string[]; + }; + PickitLines: [string, string][]; + PickitFiles: string[]; + BeltColumn: any[]; + MinColumn: any[]; + SkipId: any[]; + SkipEnchant: any[]; + SkipImmune: any[]; + SkipAura: any[]; + SkipException: any[]; + ScanShrines: any[]; + Debug: boolean; + AutoMule: { + Trigger: (number | string | ((item: ItemUnit) => boolean))[]; + Force: any[]; + Exclude: any[]; + }; + ItemInfo: boolean; + ItemInfoQuality: any[]; + LogKeys: boolean; + LogOrgans: boolean; + LogLowRunes: boolean; + LogMiddleRunes: boolean; + LogHighRunes: boolean; + LogLowGems: boolean; + LogHighGems: boolean; + SkipLogging: any[]; + ShowCubingInfo: boolean; + Cubing: boolean; + CubeRepair: boolean; + RepairPercent: number; + Recipes: any[]; + MakeRunewords: boolean; + Runewords: any[][]; + KeepRunewords: any[]; + Gamble: boolean; + GambleItems: any[]; + GambleGoldStart: number; + GambleGoldStop: number; + MiniShopBot: boolean; + TeleSwitch: boolean; + MFSwitchPercent: number; + PrimarySlot: number; + LogExperience: boolean; + TownCheck: boolean; + PingQuit: { + Ping: number; + Duration: number; + }[]; + PacketShopping: boolean; + FCR: number; + FHR: number; + FBR: number; + IAS: number; + PacketCasting: number; + WaypointMenu: boolean; + AntiHostile: boolean; + RandomPrecast: boolean; + HostileAction: number; + TownOnHostile: boolean; + ViperCheck: boolean; + StopOnDClone: boolean; + SoJWaitTime: number; + KillDclone: boolean; + DCloneQuit: boolean; + DCloneWaitTime: number; + FastParty: boolean; + AutoEquip: boolean; + ChampionBias: number; + UseCta: boolean; + Dodge: boolean; + DodgeRange: number; + DodgeHP: number; + AttackSkill: any[]; + LowManaSkill: any[]; + CustomAttack: Record; + CustomPreAttack: Record, + AdvancedCustomAttack: { check: (unit: Monster) => boolean, attack: [number, number] }[], + TeleStomp: boolean; + NoTele: boolean; + ClearType: boolean; + ClearPath: boolean; + BossPriority: boolean; + MaxAttackCount: number; + LightningFuryDelay: number; + UseInnerSight: boolean; + UseSlowMissiles: boolean; + UseDecoy: boolean; + SummonValkyrie: boolean; + UseTelekinesis: boolean; + CastStatic: boolean; + StaticList: any[]; + UseEnergyShield: boolean; + UseColdArmor: boolean; + Golem: number; + ActiveSummon: boolean; + Skeletons: number; + SkeletonMages: number; + Revives: number; + ReviveUnstackable: boolean; + PoisonNovaDelay: number; + Curse: any[]; + CustomCurse: any[]; + ExplodeCorpses: number; + Redemption: number[]; + Charge: boolean; + Vigor: boolean; + AvoidDolls: boolean; + FindItem: boolean; + FindItemSwitch: boolean; + UseWarcries: boolean; + Wereform: number; + SummonRaven: number; + SummonAnimal: number; + SummonVine: number; + SummonSpirit: number; + UseTraps: boolean; + Traps: any[]; + BossTraps: any[]; + UseFade: boolean; + UseBoS: boolean; + UseVenom: boolean; + UseBladeShield: boolean; + UseCloakofShadows: boolean; + AggressiveCloak: boolean; + SummonShadow: boolean; + ChargeCast: { + skill: number; + spectype: number; + classids: (number | string)[]; + }; + CustomClassAttack: string; + MapMode: { + UseOwnItemFilter: boolean; + }; + MFLeader: boolean; + Mausoleum: { + KillBishibosh: boolean; + KillBloodRaven: boolean; + ClearCrypt: boolean; + }; + Cows: { + DontMakePortal: boolean; + JustMakePortal: boolean; + KillKing: boolean; + }; + Tombs: { + KillDuriel: boolean; + }; + Eldritch: { + OpenChest: boolean; + KillSharptooth: boolean; + KillShenk: boolean; + KillDacFarren: boolean; + }; + Pindleskin: { + UseWaypoint: boolean; + KillNihlathak: boolean; + ViperQuit: boolean; + }; + Nihlathak: { + ViperQuit: boolean; + UseWaypoint: boolean; + }; + Pit: { + ClearPath: boolean; + ClearPit1: boolean; + }; + Snapchip: { + ClearIcyCellar: boolean; + }; + Frozenstein: { + ClearFrozenRiver: boolean; + }; + Rakanishu: { + KillGriswold: boolean; + }; + AutoBaal: { + Leader: string; + FindShrine: boolean; + LeechSpot: number[]; + LongRangeSupport: boolean; + }; + KurastChests: { + LowerKurast: boolean; + Bazaar: boolean; + Sewers1: boolean; + Sewers2: boolean; + }; + Countess: { + KillGhosts: boolean; + }; + Baal: { + DollQuit: boolean; + SoulQuit: boolean; + KillBaal: boolean; + HotTPMessage: string; + SafeTPMessage: string; + BaalMessage: string; + }; + BaalAssistant: { + KillNihlathak: boolean; + FastChaos: boolean; + Wait: number; + Helper: boolean; + GetShrine: boolean; + GetShrineWaitForHotTP: boolean; + DollQuit: boolean; + SoulQuit: boolean; + SkipTP: boolean; + WaitForSafeTP: boolean; + KillBaal: boolean; + HotTPMessage: any[]; + SafeTPMessage: any[]; + BaalMessage: any[]; + NextGameMessage: any[]; + }; + BaalHelper: { + Wait: number; + KillNihlathak: boolean; + FastChaos: boolean; + DollQuit: boolean; + KillBaal: boolean; + SkipTP: boolean; + }; + Corpsefire: { + ClearDen: boolean; + }; + Hephasto: { + ClearRiver: boolean; + ClearType: boolean; + }; + Diablo: { + WalkClear: boolean; + Entrance: boolean; + JustViz: boolean; + SealLeader: boolean; + Fast: boolean; + SealWarning: string; + EntranceTP: string; + StarTP: string; + DiabloMsg: string; + ClearRadius: number; + SealOrder: string[]; + }; + DiabloHelper: { + Wait: number; + Entrance: boolean; + SkipIfBaal: boolean; + SkipTP: boolean; + OpenSeals: boolean; + SafePrecast: boolean; + ClearRadius: number; + SealOrder: string[]; + RecheckSeals: boolean; + }; + MFHelper: { + BreakClearLevel: boolean; + }; + Wakka: { + Wait: number; + StopAtLevel: number; + StopProfile: boolean; + SkipIfBaal: boolean; + }; + BattleOrders: { + Mode: number; + Getters: any[]; + Idle: boolean; + QuitOnFailure: boolean; + SkipIfTardy: boolean; + Wait: number; + }; + BoBarbHelper: { + Mode: number; + Wp: number; + }; + ControlBot: { + Bo: boolean; + Cows: { + MakeCows: boolean; + GetLeg: boolean; + }; + Chant: { + Enchant: boolean; + AutoEnchant: boolean; + }; + Wps: { + GiveWps: boolean; + SecurePortal: boolean; + }; + Rush: { + Bloodraven: boolean; + Smith: boolean; + Andy: boolean; + Cube: boolean; + Radament: boolean; + Amulet: boolean; + Staff: boolean; + Summoner: boolean; + Duriel: boolean; + Gidbinn: boolean; + LamEsen: boolean; + Eye: boolean; + Heart: boolean; + Brain: boolean; + Travincal: boolean; + Mephisto: boolean; + Izual: boolean; + Diablo: boolean; + Shenk: boolean; + Anya: boolean; + Ancients: boolean; + Baal: boolean; + }; + EndMessage: string; + GameLength: number; + }; + IPHunter: { + IPList: any[]; + GameLength: number; + }; + Follower: { + Leader: string; + }; + Mephisto: { + MoatTrick: boolean; + KillCouncil: boolean; + TakeRedPortal: boolean; + }; + ShopBot: { + ScanIDs: any[]; + ShopNPC: string; + CycleDelay: number; + QuitOnMatch: boolean; + }; + Coldworm: { + KillBeetleburst: boolean; + ClearMaggotLair: boolean; + }; + Summoner: { + FireEye: boolean; + }; + AncientTunnels: { + OpenChest: boolean; + KillDarkElder: boolean; + }; + OrgTorch: { + WaitForKeys: boolean; + WaitTimeout: boolean; + UseSalvation: boolean; + GetFade: boolean; + MakeTorch: boolean; + PreGame: { + Thawing: { + Drink: number; + At: any[]; + }; + Antidote: { + Drink: number; + At: any[]; + }; + }; + }; + Synch: { + WaitFor: any[]; + }; + TristramLeech: { + Leader: string; + Helper: boolean; + Wait: number; + }; + TravincalLeech: { + Leader: string; + Helper: boolean; + Wait: number; + }; + Tristram: { + PortalLeech: boolean; + WalkClear: boolean; + }; + Travincal: { + PortalLeech: boolean; + }; + SkillStat: { + Skills: any[]; + }; + Bonesaw: { + ClearDrifterCavern: boolean; + }; + ChestMania: { + Act1: any[]; + Act2: any[]; + Act3: any[]; + Act4: any[]; + Act5: any[]; + }; + ClearAnyArea: { + AreaList: any[]; + }; + Rusher: { + WaitPlayerCount: number; + Cain: boolean; + Radament: boolean; + LamEsen: boolean; + Izual: boolean; + Shenk: boolean; + Anya: boolean; + HellAncients: boolean; + GiveWps: boolean; + LastRun: string; + }; + Rushee: { + Quester: boolean; + Bumper: boolean; + Protector: boolean; + }; + Questing: { + StopProfile: boolean; + }; + GetEssences: { + MoatMeph: boolean; + FastDiablo: boolean; + RunDuriel: boolean; + }; + AutoSkill: { + Enabled: boolean; + Build: any[]; + Save: number; + }; + AutoStat: { + Enabled: boolean; + Build: any[]; + Save: number; + BlockChance: number; + UseBulk: boolean; + }; + AutoBuild: { + Enabled: boolean; + Template: string; + Verbose: boolean; + DebugMode: boolean; + }; + } + const Config: Config; +} +export {}; diff --git a/d2bs/kolbot/sdk/types/Cubing.d.ts b/d2bs/kolbot/sdk/types/Cubing.d.ts new file mode 100644 index 000000000..30ee1556e --- /dev/null +++ b/d2bs/kolbot/sdk/types/Cubing.d.ts @@ -0,0 +1,24 @@ +export {}; +declare global { + namespace Cubing { + function init(): void; + function buildGemList(): void; + function getCube(): void; + function buildRecipes(): void; + function buildLists(): void; + function clearSubRecipes(): void; + function update(): void; + function checkRecipe(recipe: any): void; + function getRecipeNeeds(index: any): void; + function checkItem(unit: any): boolean; + function keepItem(unit: any): boolean; + function validItem(unit: any, recipe: any): void; + function doCubing(): boolean; + function cursorCheck(): boolean; + function openCube(): boolean; + function closeCube(closeToStash: boolean): boolean; + function emptyCube(): boolean; + function makeRevPots(): void; + function repairItem(item: ItemUnit): boolean; + } +} diff --git a/d2bs/kolbot/sdk/types/Item.d.ts b/d2bs/kolbot/sdk/types/Item.d.ts new file mode 100644 index 000000000..f4db04829 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Item.d.ts @@ -0,0 +1,22 @@ +export {}; +declare global { + namespace Item { + let useItemLog: boolean; + + function qualityToName(quality : number): string; + function color(unit: ItemUnit, type: boolean): string; + function hasTier(item: ItemUnit): boolean; + function canEquip(item: ItemUnit): boolean; + function equip(item: ItemUnit, bodyLoc: number): boolean; + function getEquippedItem(bodyLoc: number): { classid: number, tier: number }; + function getBodyLoc(item: ItemUnit): number[]; + function autoEquipCheck(item: ItemUnit): boolean; + function autoEquip(): boolean; + function getItemDesc(unit: ItemUnit, logILvl: boolean): string; + function getItemCode(unit: ItemUnit): string; + function getItemSockets(unit: ItemUnit): ItemUnit[]; + function logger(action: string, unit: ItemUnit, text?: string): string; + function logItem(action: string, unit: ItemUnit, keptLine?: string): boolean; + function skipItem(id: number): boolean; + } +} diff --git a/d2bs/kolbot/sdk/types/Loader.d.ts b/d2bs/kolbot/sdk/types/Loader.d.ts new file mode 100644 index 000000000..2bbe902a7 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Loader.d.ts @@ -0,0 +1,54 @@ +export {}; +declare global { + type GlobalScript = () => boolean; + type ScriptContext = { [key: string]: any }; + interface RunnableOptions { + setup?: (ctx: ScriptContext) => any; + preAction?: (ctx: ScriptContext) => any; + postAction?: (ctx: ScriptContext) => any; + cleanup?: (ctx: ScriptContext) => any; + forceTown?: boolean; + bossid?: number; + startArea?: number; + } + + class Runnable { + constructor(action: () => boolean, options: Partial); + + action: (ctx: ScriptContext) => boolean; + startArea: number | null; + setup: ((ctx: ScriptContext) => any) | null; + preAction: (ctx: ScriptContext) => any; + postAction: ((ctx: ScriptContext) => any) | null; + cleanup: ((ctx: ScriptContext) => any) | null; + forceTown: boolean; + bossid: number | null; + } + + namespace Loader { + const fileList: string[]; + const scriptList: string[]; + const scriptIndex: number; + const skipTown: string[]; + const firstScriptAct: number; + const currentScript: GlobalScript | Runnable | null; + const nextScript: GlobalScript | Runnable | null; + const doneScripts: Set; + const tempList: string[]; + + function init(): void; + function getScripts(): void; + function _runCurrent(ctx: ScriptContext): boolean; + function clone(obj: any): void; + function copy(from: any, to: any): void; + function loadScripts(): void; + function runScript(name: string, configOverride: Partial | (() => any)): boolean; + function scriptName(offset?: number): string; + } + + type Scripts = { + [key: string]: boolean; + }; + + const Scripts: Scripts; +} diff --git a/d2bs/kolbot/sdk/types/Misc.d.ts b/d2bs/kolbot/sdk/types/Misc.d.ts new file mode 100644 index 000000000..43fdeb993 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Misc.d.ts @@ -0,0 +1,43 @@ + +export{}; +declare global { + namespace Misc { + const screenshotErrors: any; + const errorConsolePrint: any; + const useItemLog: boolean; + + function click(button: number, shift: number, unit: Unit): void; + function click(button: number, shift: number, x: Unit, y: undefined): void; + function inMyParty(name: string): boolean; + function findPlayer(name: string): Party | false; + function getPlayerUnit(name: string): Player | false; + function getPlayerAct(player: Party | string): number | false; + function getNearbyPlayerCount(): number; + function getPlayerCount(): number; + function getPartyCount(): number; + function checkPartyLevel(levelCheck: number, exclude: string | string[]): boolean; + function getPlayerArea(player: Party | string): number | false; + + type AutoLeaderDetectSettings = { + destination: number | number[], + quitIf: (area: number) => boolean, + timeout: number, + }; + function autoLeaderDetect(givenSettings: AutoLeaderDetectSettings): string | false; + function openChest(unit: any): boolean; + function openChestsInArea(area?: any, chestIds?: any): void; + function openChests(range: any): void; + function scanShrines(range: any): void; + function getShrine(unit: any): void; + function getShrinesInArea(area: any, type: any, use: any): void; + /** @deprecated */ + function townCheck(boolean?: boolean): void; + function spy(name: any): void; + function errorReport(error: Error | string, script?: string): void; + function debugLog(msg: any): void; + function useMenu(id: number): void; + function poll(check: () => T, timeout?: number, sleep?: number): T; + function getUIFlags(excluded?: []): number[] | null; + function getQuestStates(questId: number): number[]; + } +} diff --git a/d2bs/kolbot/sdk/types/NPC.d.ts b/d2bs/kolbot/sdk/types/NPC.d.ts new file mode 100644 index 000000000..1ad58603e --- /dev/null +++ b/d2bs/kolbot/sdk/types/NPC.d.ts @@ -0,0 +1,34 @@ +//@ts-nocheck +declare global { + type NPC = string; + namespace NPC { + function getAct(name: string): number[]; + const Akara: string; + const Gheed: string; + const Charsi: string; + const Kashya: string; + const Warriv: string; + const Fara: string; + const Drognan: string; + const Elzix: string; + const Greiz: string; + const Lysander: string; + const Jerhyn: string; + const Meshif: string; + const Atma: string; + const Ormus: string; + const Alkor: string; + const Hratli: string; + const Asheara: string; + const Jamella: string; + const Halbu: string; + const Tyrael: string; + const Malah: string; + const Anya: string; + const Larzuk: string; + const Qual_Kehk: string; + const Nihlathak: string; + const Cain: string; + } +} +export {}; \ No newline at end of file diff --git a/d2bs/kolbot/sdk/types/NTIP.d.ts b/d2bs/kolbot/sdk/types/NTIP.d.ts new file mode 100644 index 000000000..59e60ac7d --- /dev/null +++ b/d2bs/kolbot/sdk/types/NTIP.d.ts @@ -0,0 +1,33 @@ +export {}; +declare global { + const NTIPAliasType: Record; + const NTIPAliasClassID: Record; + const NTIPAliasClass: Record; + const NTIPAliasQuality: Record; + const NTIPAliasFlag: Record; + const NTIPAliasColor: Record; + const NTIPAliasStat: Record; + + namespace NTIP { + function addLine(itemString: string, fileName: string): boolean; + function OpenFile(filepath: string, notify: boolean): boolean; + function CheckQuantityOwned(item_type: (item: ItemUnit) => boolean, item_stats: (item: ItemUnit) => boolean): number; + function Clear(): void; + function generateTierFunc(tierType: string): (item: ItemUnit) => number; + function GetTier(item: ItemUnit): number; + function GetMercTier(item: ItemUnit): number; + function IsSyntaxInt(ch: string): boolean; + const parseAliasIn: { + in: string; + notin: string; + _regex: RegExp; + test: (input: string) => boolean; + convert: (input: string) => string; + }; + const _props: Map; + const _aliases: Map; + const _lists: Map>; + function ParseLineInt(input: string, info: any): boolean; + function CheckItem(item: ItemUnit, entryList?: [] | false, verbose?: boolean): number | { line: string, result: number }; + } +} diff --git a/d2bs/kolbot/sdk/types/OOG.d.ts b/d2bs/kolbot/sdk/types/OOG.d.ts new file mode 100644 index 000000000..791de0f70 --- /dev/null +++ b/d2bs/kolbot/sdk/types/OOG.d.ts @@ -0,0 +1,156 @@ +// @ts-nocheck +declare global { + namespace DataFile { + function create(): void + function getObj(): void + function getStats(): any + function updateStats(arg: any, value?: any): void + } + + namespace FileAction { + function read(path: string): string; + function write(path: string, msg: string): boolean; + function append(path: string, msg: string): boolean; + function parse(path: string): any; + } + + export const D2Bot: { + handle: number, + init(): void + sendMessage(handle: any, mode: any, msg: any): void + printToConsole(msg: string, color?: number, tooltip?: undefined, trigger?: undefined): void + printToItemLog(itemObj: any): void + uploadItem(itemObj: any): void + writeToFile(filename: any, msg: any): void + postToIRC(ircProfile: any, recepient: any, msg: any): void + ircEvent(mode: any): void + notify(msg: any): void + saveItem(itemObj: any): void + updateStatus(msg: any): void + updateRuns(): void + updateChickens(): void + updateDeaths(): void + requestGameInfo(): void + restart(keySwap?: boolean): void + CDKeyInUse(): void + CDKeyDisabled(): void + CDKeyRD(): void + stop(profile?: undefined, release?: undefined): void + start(profile: any): void + startSchedule(profile: any): void + stopSchedule(profile: any): void + updateCount(): void + shoutGlobal(msg: any, mode: any): void + heartBeat(): void + sendWinMsg(wparam: any, lparam: any): void + ingame(): void + joinMe(profile: any, gameName: any, gameCount: any, gamePass: any, isUp: any): void + requestGame(profile: any): void + getProfile(): void + setProfile(account: any, password: any, character: any, difficulty: any, realm: any, infoTag: any, gamePath: any): void + setTag(tag: any): void + store(info: any): void + retrieve(): void + remove(): void + } + + namespace ControlAction { + let mutedKey: boolean; + enum realms { + 'uswest' = 0, + 'useast' = 1, + 'asia' = 2, + 'europe' = 3 + }; + type ControlParams = { + type: number, + x: number, + y: number, + xsize: number, + ysize: number, + }; + type CharacterInfo = { + charName: string; + charClass: string; + charLevel: number; + expansion: boolean; + hardcore: boolean; + ladder: boolean; + }; + type AccountInfo = { + account: string; + password: string; + realm: realms; + }; + function timeoutDelay( + text: string, + time: number, + stopfunc?: (arg: any) => boolean, + arg?: any + ): void; + // function click( + // ...params: [targetx: number, targety: number, ...rest: ControlParams] + // ): boolean; + // function setText( + // text: string, + // ...params: ControlParams + // ): boolean; + // function getText( + // ...params: ControlParams + // ): string[]; + function click( + type: number, + x: number, + y: number, + xsize: number, + ysize: number, + targetx: number, + targety: number, + ): boolean; + + function setText( + type: number, + x: number, + y: number, + xsize: number, + ysize: number, + text: string + ): boolean; + + function getText( + type: number, + x: number, + y: number, + xsize: number, + ysize: number + ): string[]; + + function parseText( + type: number, + x: number, + y: number, + xsize: number, + ysize: number + ): string; + + function scrollDown(): void; + function clickRealm(realm: realms): boolean; + function findCharacter(info: CharacterInfo): Control | false; + function getCharacters(): string[]; + function getPermStatus(info: CharacterInfo): boolean; + function getPosition(): number; + function makeCharacter(info: CharacterInfo): boolean; + function deleteCharacter(info: CharacterInfo): boolean; + function convertCharacter(info: CharacterInfo): boolean; + function loginCharacter(info: CharacterInfo, startFromTop?: boolean): boolean; + function setEmail(email: string, domain?: string): boolean; + function makeAccount(info: AccountInfo): boolean; + function loginAccount(info: AccountInfo): boolean; + function joinChannel(channel: string): boolean; + function createGame(name: string, pass: string, diff: string, delay: number): void; + function getGameList(): { gameName: string, players: number }[] | false; + function getQueueTime(): number; + function loginOtherMultiplayer(): boolean; + } +} +export {}; diff --git a/d2bs/kolbot/sdk/types/Packet.d.ts b/d2bs/kolbot/sdk/types/Packet.d.ts new file mode 100644 index 000000000..46419f771 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Packet.d.ts @@ -0,0 +1,115 @@ +// @ts-nocheck +declare global { + namespace Packet { + /** + * Interact and open the menu of an NPC + * @param {NPCUnit} unit + * @returns {boolean} + */ + function openMenu(unit: NPCUnit): boolean; + + /** + * Start a trade action with an NPC + * @param {NPCUnit} unit + * @param {number} mode + * @returns {boolean} + */ + function startTrade(unit: NPCUnit, mode: number): boolean; + + /** + * Buy an item from an interacted NPC + * @param {NPCUnit} unit + * @param {boolean} shiftBuy + * @param {boolean} gamble + * @returns {boolean} + */ + function buyItem(unit: NPCUnit, shiftBuy: boolean, gamble: boolean): boolean; + + /** + * Buy scrolls from an interacted NPC, we need this as a separate check because itemcount doesn't change + * if the scroll goes into the tome automatically. + * @param {NPCUnit} unit + * @param {ItemUnit} [tome] + * @param {boolean} [shiftBuy] + * @returns {boolean} + */ + function buyScroll(unit: NPCUnit, tome?: ItemUnit, shiftBuy?: boolean): boolean; + + /** + * Sell an item to a NPC + * @param {ItemUnit} unit + * @returns {boolean} + */ + function sellItem(unit: ItemUnit): boolean; + + /** + * @param {ItemUnit} unit + * @param {ItemUnit} tome + * @returns {boolean} + */ + function identifyItem(unit: ItemUnit, tome: ItemUnit): boolean; + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + function itemToCursor(item: ItemUnit): boolean; + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + function dropItem(item: ItemUnit): boolean; + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + function givePotToMerc(item: ItemUnit): boolean; + + /** + * @param {ItemUnit} item + * @param {number} xLoc + * @returns {boolean} + */ + function placeInBelt(item: ItemUnit, xLoc: number): boolean; + + /** + * @param {ItemUnit} who + * @param {boolean} toCursor + * @returns {boolean} + */ + function click(who: ItemUnit, toCursor?: boolean): boolean; + + /** + * @param {Unit} who + * @returns {boolean} + */ + function entityInteract(who: Unit): boolean; + + /** + * @param {NPCUnit} who + * @returns {boolean} + */ + function cancelNPC(who: NPCUnit): boolean; + + /** + * @param {ItemUnit} pot + * @returns {boolean} + */ + function useBeltItemForMerc(pot: ItemUnit): boolean; + function castSkill(hand: number, wX: number, wY: number): void; + function castAndHoldSkill(hand: number, wX: number, wY: number, duration?: number): void; + function unitCast(hand: number, who: Monster | ItemUnit | ObjectUnit): void; + function telekinesis(who: Monster | ItemUnit | ObjectUnit): boolean; + function enchant(who: Monster | Player | MercUnit): boolean; + function teleport(wX: number, wY: number): boolean; + function teleWalk(x: number, y: number, maxDist: number): boolean; + function questRefresh(): void; + function flash(gid?: number, wait?: number): void; + function changeStat(stat: number, value: number): void; + function addListener(packetType: number | number[], callback: (packet: number) => any): null; + function removeListener(callback: (packet: number) => any): null; + } +} +export {}; \ No newline at end of file diff --git a/d2bs/kolbot/sdk/types/Pather.d.ts b/d2bs/kolbot/sdk/types/Pather.d.ts new file mode 100644 index 000000000..8b9d70dae --- /dev/null +++ b/d2bs/kolbot/sdk/types/Pather.d.ts @@ -0,0 +1,68 @@ +export {}; +declare global { + interface PathSettings { + allowNodeActions?: boolean; + allowTeleport?: boolean; + allowClearing?: boolean; + allowTown?: boolean; + allowPicking?: boolean; + minDist?: number; + retry?: number; + pop?: boolean; + returnSpotOnError?: boolean; + callback?: () => void; + clearSettings?: ClearSettings; + } + + interface ClearSettings { + clearPath?: boolean; + range?: number; + specType?: number; + sort?: () => void; + } + namespace Pather { + const wpAreas: number[]; + let walkDistance: number; + let teleDistance: number; + let teleport: boolean; + const cancelFlags: number[]; + let recursion: boolean; + let lastPortalTick: 0; + let allowBroadcast: boolean; + + function getWalkDistance(x: number, y: number, area?: number, xx?: number, yy?: number, reductionType?: 0 | 1 | 2, radius?: number): number; + function useTeleport(): boolean; + function moveTo(x: number, y: number, retry?: number | undefined, clearPath?: boolean | undefined, pop?: boolean | undefined): boolean; + function teleportTo(x: any, y: any, maxRange?: any): void; + function walkTo(x: any, y: any, minDist?: number | undefined): boolean; + function openDoors(x: any, y: any): boolean; + function moveToUnit(unit: PathNode, offX?: undefined, offY?: undefined, clearPath?: undefined, pop?: undefined): boolean; + function moveToPreset(area: any, unitType: any, unitId: any, offX?: any, offY?: any, clearPath?: any, pop?: any): boolean; + function moveToPresetObject(area: number, unitId: number, givenSettings?: PathSettings): boolean; + function moveToPresetMonster(area: number, unitId: number, givenSettings?: PathSettings): boolean; + function moveToExit(targetArea: any, use?: any, givenSettings?: PathSettings): boolean; + function getDistanceToExit(area?: number, exit?: number): number; + function getExitCoords(area?: number, exit?: number): PathNode | false; + function getNearestRoom(area: number): [number, number] | false; + function openExit(targetArea: number): boolean; + function openUnit(type: number, id: number): void; + function useUnit(type: any, id: any, targetArea: any): boolean; + function broadcastIntent(targetArea: number): void; + function useWaypoint(targetArea: number | null | "random", check?: boolean): boolean; + function makePortal(use?: boolean | undefined): ObjectUnit | boolean; + function usePortal(targetArea?: number | null, owner?: string | undefined, unit?: undefined): boolean; + function getPortal(targetArea: number, owner?: any): ObjectUnit | false; + function getNearestWalkable( + x: number, y: number, range: number, step: number, coll: number, size?: number + ): [number, number] | false; + function checkSpot( + x: number, y: number, coll: number, cacheOnly: boolean, size: number + ): boolean; + /** @deprecated use `me.accessToAct(act)` instead */ + function accessToAct(act: number): boolean; + function getWP(area: number, clearPath?: boolean): boolean; + function journeyTo(area: number): boolean; + function plotCourse(dest: number, src: number): false | { course: number[], useWP: boolean }; + function areasConnected(src: number, dest: number): void; + } +} diff --git a/d2bs/kolbot/sdk/types/Pickit.d.ts b/d2bs/kolbot/sdk/types/Pickit.d.ts new file mode 100644 index 000000000..a8df9e378 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Pickit.d.ts @@ -0,0 +1,38 @@ +export {}; +declare global { + type PickitResult = { + UNID: -1, + UNWANTED: 0, + WANTED: 1, + CUBING: 2, + RUNEWORD: 3, + TRASH: 4, + CRAFTING: 5, + UTILITY: 6 + }; + namespace Pickit { + const gidList: number[]; + let invoLocked: boolean; + let beltSize: 1 | 2 | 3 | 4; + const ignoreLog: number[]; // Ignored item types for item logging + const Result: PickitResult; + const tkable: number[]; + const essentials: number[]; + + function init(notify: any): void; + function itemEvent(gid?: number, mode?: number, code?: number, global?: number): void; + function sortItems(unitA: Unit, unitB: Unit): number; + function sortFastPickItems(unitA: Unit, unitB: Unit): number; + function checkBelt(): boolean; + function canPick(unit: ItemUnit): boolean; + function checkItem(unit: ItemUnit): { result: PickitResult, line: null | number }; + function pickItem( + unit: ItemUnit, + status?: PickitResult, + keptLine?: any, retry?: number + ): { result: PickitResult, line: string | null }; + function canMakeRoom(): boolean; + function pickItems(range?: number): boolean; + function fastPick(): boolean; + } +} diff --git a/d2bs/kolbot/sdk/types/Runewords.d.ts b/d2bs/kolbot/sdk/types/Runewords.d.ts new file mode 100644 index 000000000..418657d8d --- /dev/null +++ b/d2bs/kolbot/sdk/types/Runewords.d.ts @@ -0,0 +1,122 @@ +export {}; +declare global { + /** + * @property {string} name - The name of the runeword. + * @property {number} sockets - The number of sockets required for the item. + * @property {Array} runes - Array of rune IDs required for the runeword. + * @property {Array} itemTypes - Array of item type IDs the runeword can be applied to. + * @method ladderRestricted - Returns true if we are unable to make the runeword because we are not on ladder. + */ + interface runeword { + name: string; + sockets: number; + runes: number[]; + itemTypes: number[]; + _ladder: boolean; + reqLvl: number; + ladderRestricted: () => boolean; + } + + namespace Runeword { + const AncientsPledge: runeword; + const Black: runeword; + const Fury: runeword; + const HolyThunder: runeword; + const Honor: runeword; + const KingsGrace: runeword; + const Leaf: runeword; + const Lionheart: runeword; + const Lore: runeword; + const Malice: runeword; + const Melody: runeword; + const Memory: runeword; + const Nadir: runeword; + const Radiance: runeword; + const Rhyme: runeword; + const Silence: runeword; + const Smoke: runeword; + const Stealth: runeword; + const Steel: runeword; + const Strength: runeword; + const Venom: runeword; + const Wealth: runeword; + const White: runeword; + const Zephyr: runeword; + const Beast: runeword; + const Bramble: runeword; + const BreathoftheDying: runeword; + const CallToArms: runeword; + const ChainsofHonor: runeword; + const Chaos: runeword; + const CrescentMoon: runeword; + const Delirium: runeword; + const Doom: runeword; + const Duress: runeword; + const Enigma: runeword; + const Eternity: runeword; + const Exile: runeword; + const Famine: runeword; + const Gloom: runeword; + const HandofJustice: runeword; + const HeartoftheOak: runeword; + const Kingslayer: runeword; + const Passion: runeword; + const Prudence: runeword; + const Sanctuary: runeword; + const Splendor: runeword; + const Stone: runeword; + const Wind: runeword; + const Brand: runeword; + const Death: runeword; + const Destruction: runeword; + const Dragon: runeword; + const Dream: runeword; + const Edge: runeword; + const Faith: runeword; + const Fortitude: runeword; + const Grief: runeword; + const Harmony: runeword; + const Ice: runeword; + const Infinity: runeword; + const Insight: runeword; + const LastWish: runeword; + const Lawbringer: runeword; + const Oath: runeword; + const Obedience: runeword; + const Phoenix: runeword; + const Pride: runeword; + const Rift: runeword; + const Spirit: runeword; + const VoiceofReason: runeword; + const Wrath: runeword; + const Bone: runeword; + const Enlightenment: runeword; + const Myth: runeword; + const Peace: runeword; + const Principle: runeword; + const Rain: runeword; + const Treachery: runeword; + const Test: runeword; + + function findByName(name: string): runeword | undefined; + function findByRune(rune: number): runeword[]; + function findByType(type: number): runeword[]; + + function addRuneword(name: string, sockets: number, runes: number | number[], itemTypes: number | number[]): runeword | boolean; + } + + namespace Runewords { + function init(): void + function validItem(item: any): void + function buildLists(): void + function update(classid: any, gid: any): void + function checkRunewords(): void + function checkItem(unit: any): boolean + function keepItem(unit: any): boolean + function getBase(runeword: any, base: any, ethFlag: any, reroll: any): void + function socketItem(base: any, rune: any): void + function getScroll(): void + function makeRunewords(): void + function rerollRunewords(): void + } +} diff --git a/d2bs/kolbot/sdk/types/Skill.d.ts b/d2bs/kolbot/sdk/types/Skill.d.ts new file mode 100644 index 000000000..7a3d66a0a --- /dev/null +++ b/d2bs/kolbot/sdk/types/Skill.d.ts @@ -0,0 +1,94 @@ +export {}; +declare global { + class SkillDataInfo { + skillId: number; + hand: number; + state: number; + summonType: number; + summonCount: () => number; + condition: () => boolean; + townSkill: boolean; + timed: boolean; + missleSkill: boolean; + aura: boolean; + charClass: number; + reqLevel: number; + preReqs: number[]; + damageType: string; + private _range: number | (() => number); + private _AoE: () => number; + private _duration: () => number; + private _manaCost: number; + private _mana: number; + private _minMana: number; + private _lvlMana: number; + private _manaShift: number; + private _bestSlot: number; + private _dmg: number; + private _hardPoints: number; + private _softPoints: number; + private _checked: boolean; + + constructor(skillId: number); + + duration(): number; + manaCost(): number; + range(pvpRange?: boolean): number; + AoE(): number; + have(): boolean; + reset(): void; + } + + type Charge = { + skill: number; + level: number; + charges: number; + maxcharges: number; + }; + + class ChargedSkill { + skill: number; + level: number; + charges: number; + maxCharges: number; + gid: number; + unit: ItemUnit; + update(item: ItemUnit): void; + } + namespace Skill { + let usePvpRange: boolean; + const haveTK: boolean; + const manaCostList: object; + const needFloor: number[]; + const missileSkills: number[]; + const charges: ChargedSkill[]; + + function get (skillId: number): SkillDataInfo; + function getClassSkillRange(classid?: number): [number, number]; + function getCharges(): boolean; + function init(): void; + function canUse(skillId: number): boolean; + function getDuration(skillId: number): number; + function getMaxSummonCount(skillId: number): number; + function getSummonType(skillId: number): number; + function getRange(skillId: number): number; + function getAoE(skillId: number): number; + function getHand(skillId: number): number; + function getState(skillId: number): number; + function getCharClass(skillId: number): number; + function getSkillTab(skillId: number): number; + function getManaCost(skillId: number): number; + function isTimed(skillId: number): boolean; + function townSkill(skillId: number): boolean; + function missileSkill(skillId: number): boolean; + function isAura(skillId: number): boolean; + function wereFormCheck(skillId: number): boolean; + function setSkill(skillId: number, hand?: number, item?: any): boolean; + function shapeShift(mode: number | string): boolean; + function unShift(): boolean; + function useTK(unit: Unit): boolean; + function cast(skillId: number, hand?: number, x?: number, y?: number, item?: ItemUnit | undefined): boolean; + function cast(skillId: number, hand?: number, unit?: Unit): boolean; + function castCharges(skillId: number, unit: Unit | { x: number, y: number }): boolean; + } +} diff --git a/d2bs/kolbot/sdk/types/Storage.d.ts b/d2bs/kolbot/sdk/types/Storage.d.ts new file mode 100644 index 000000000..676e49e47 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Storage.d.ts @@ -0,0 +1,101 @@ +// @ts-nocheck +export {}; +declare global { + function Container(name: string, width: number, height: number, location: number): void; + interface Container { + constructor(name: string, width: number, height: number, location: number): Container; + /** The name of the container */ + name: string; + /** The width of the container */ + width: number; + /** The height of the container */ + height: number; + /** The location of the container */ + location: number; + /** A 2D array to store the containers items */ + buffer: number[][]; + /** A list of the items in the container */ + itemList: ItemUnit[]; + /** The number of open positions in the container */ + openPositions: number; + + /** + * A function that marks an item in the container's buffer and adds it to the item list. + * @param item + */ + Mark(item: ItemUnit): boolean; + + /** + * A function that checks if an item is locked in the container. + * @param item + * @param baseRef + */ + IsLocked(item: ItemUnit, baseRef: number[][]): boolean + + /** + * A function that resets the container's buffer and item list. + */ + Reset(): void + + /** + * Checks whether it is possible to fit an item in inventory given available non-locked space. + * @param item + */ + IsPossibleToFit(item: ItemUnit): boolean + + /** + * A function that checks if an item can fit in the container. + * @param item + */ + CanFit(item: ItemUnit): boolean + + /** + * A function that finds a spot for an item in the container. + * @param item + */ + FindSpot(item: ItemUnit): PathNode | false + + /** + * A function that moves an item to a location in a container + * @param item + */ + MoveTo(item: ItemUnit): boolean + + /** + * A function that dumps the information about the container to the console + */ + Dump(): void + + /** + * A function that returns the amount of space used in this container + */ + UsedSpacePercent(): number + + /** + * A function the returns an item list in comparison to a given reference array + * @param baseRef + */ + Compare(baseRef: number[][]): ItemUnit[] | false + + /** + * returns a string representation of the source object + * @deprecated + */ + toSource(): string + } + + type storage = { + StashY: 4 | 8 | 10; + Inventory: Container; + TradeScreen: Container; + Stash: Container; + Belt: Container; + Cube: Container; + InvRef: number[]; + + BeltSize(): 1 | 2 | 3 | 4; + Reload(): void; + Init(): void; + } + const Storage: storage; +} diff --git a/d2bs/kolbot/sdk/types/Town.d.ts b/d2bs/kolbot/sdk/types/Town.d.ts new file mode 100644 index 000000000..3bb0508eb --- /dev/null +++ b/d2bs/kolbot/sdk/types/Town.d.ts @@ -0,0 +1,79 @@ +// @ts-nocheck +declare global { + namespace Town { + let telekinesis: boolean; + let sellTimer: number; + let lastChores: number; + + const tasks: Map; + const ignoredItemTypes: any[]; + function needPotions(): boolean; + function doChores(repair?: boolean): boolean; + function npcInteract(name?: string, cancel?: boolean): boolean | NPCUnit; + function checkQuestItems(): void; + function getTpTool(): ItemUnit; + function getIdTool(): ItemUnit; + function canTpToTown(): boolean; + function initNPC(task?: string, reason?: string): boolean | NPCUnit; + function heal(): boolean; + // function needHealing(): boolean; + function buyPotions(): boolean; + function shiftCheck(col: number, beltSize: 0 | 2 | 1 | 4 | 3): boolean; + function checkColumns(beltSize: 0 | 2 | 1 | 4 | 3): [number, number, number, number]; + function getPotion(npc: Unit, type: "hp" | "mp", highestPot?: 2 | 1 | 4 | 3 | 5): boolean | ItemUnit; + function fillTome(classid: number): boolean; + function checkScrolls(id: number): number; + function identify(): boolean; + function cainID(): boolean; + // function fieldID(): boolean; + // function getUnids(): false | ItemUnit[]; + function identifyItem(unit: ItemUnit, tome: ItemUnit, packetID?: boolean): boolean; + function shopItems(): boolean; + const gambleIds: any[]; + function gamble(): boolean; + function needGamble(): boolean; + function getGambledItem(list?: any[]): false | ItemUnit; + function buyPots(quantity?: number, type?: string | number, drink?: boolean, force?: boolean, npc?: Unit): boolean; + function drinkPots(type?: string | number, log?: boolean): { + potName: string; + quantity: number; + }; + function buyKeys(): boolean; + // function checkKeys(): number; + // function needKeys(): boolean; + // function wantKeys(): boolean; + function repairIngredientCheck(item: ItemUnit): boolean; + function cubeRepair(): boolean; + function cubeRepairItem(item: ItemUnit): boolean; + function repair(force?: boolean): boolean; + // function needRepair(): string[]; + // function getItemsForRepair(repairPercent: number, chargedItems: boolean): ItemUnit[]; + function reviveMerc(): boolean; + // function needMerc(): boolean; + function canStash(item: ItemUnit): boolean; + function stash(stashGold?: boolean): boolean; + // function needStash(): boolean; + function openStash(): boolean; + function getCorpse(): boolean; + function checkShard(): boolean; + // function clearBelt(): boolean; + function clearScrolls(): boolean; + function clearInventory(): boolean; + const act: {}[]; + function initialize(): boolean; + function getDistance(spot?: string): number; + function move(spot?: string, allowTK?: boolean): boolean; + function moveToSpot(spot?: string, allowTK?: boolean): boolean; + function goToTown(act?: 2 | 1 | 4 | 3 | 5, wpmenu?: boolean): boolean; + function visitTown(repair?: boolean): boolean; + } +} +export {}; diff --git a/d2bs/kolbot/sdk/types/Util.d.ts b/d2bs/kolbot/sdk/types/Util.d.ts new file mode 100644 index 000000000..2112d11e7 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Util.d.ts @@ -0,0 +1,138 @@ +// @ts-nocheck +declare global { + /** + * @constructor + * @description new PacketBuilder() - create new packet object + * @example (Spoof 'reassign player' packet to client): + * new PacketBuilder().byte(sdk.packets.recv.ReassignPlayer).byte(0).dword(me.gid).word(x).word(y).byte(1).get(); + * @example (Spoof 'player move' packet to server): + * new PacketBuilder().byte(sdk.packets.send.RunToLocation).word(x).word(y).send(); + * @todo pass the inital byte into the constructor so we don't always have to do `new PacketBuilder().byte(sdk.packets.recv.ReassignPlayer)...` + * it would just be `new PacketBuilder(sdk.packets.recv.ReassignPlayer)...` + */ + function PacketBuilder(): void; + class PacketBuilder { + /** @description size = 4 */ + float(a: number): this + /** @description size = 4 */ + dword(a: number): this + /** @description size = 2 */ + word(a: number): this + /** @description size = 1 */ + byte(a: number): this + string(a: any): this + send(): this + spoof(): this + } + + /** + * @class + * @classdesc A class for creating and sending copy data packets. + * @property {number} _mode - Defaults to 0, works for most D2Bot functions + * @property {number | string} _handle - Defaults to value of D2Bot.handle, works for any D2Bot + * functions that act on ourselves + * @example Request a game from "scl-sorc-001" profile + * new CopyData().handle("scl-sorc-001").mode(3).send(); + * @example Start mule profile "mule" + * new CopyData().data("start", ["mule"]).send(); + */ + function CopyData(): void; + class CopyData { + /** + * @private + * @type {string | number} - The handle to send the copy data to. + */ + private _handle: string | number; + + /** + * @private + * @type {number} - The mode of the copy data packet. + */ + private _mode: number; + + /** + * @private + * @type {string} - The data to send in the copy data + */ + private _data: string; + + /** + * - D2Bot.handle is for any functions that act on ourselves + * - Otherwise it is the D2Bot# profile name of the profile to act upon + * @param {string | number} handle - The handle or profile to send the copy data to. + */ + handle(handle: string | number): CopyData; + + /** + * - 0 is for most functions, and the default value set + * - 1 is for joinMe + * - 3 is for requestGame + * - 0xbbbb is for heartBeat + * @param {number} mode - The mode of the copy data packet. + */ + mode(mode: number): CopyData; + + /** + * @param {string} [func] - The function to call from D2Bot# + * @param {string[]} [args] - The additonal info needed for the function call + */ + data(func?: string, args?: string[]): CopyData; + send(): void; + } + + function getThreads(): Script[]; + function getUnits(type: MonsterType, name?: string, mode?: number, unitId?: number): Monster[]; + function getUnits(type: MonsterType, classId?: number, mode?: number, unitId?: number): Monster[]; + function getUnits(type: ObjectType, name?: string, mode?: number, unitId?: number): ObjectUnit[]; + function getUnits(type: ObjectType, classId?: number, mode?: number, unitId?: number): ObjectUnit[]; + function getUnit(type?: MissileType, name?: string, mode?: number, unitId?: number): Missile[] + function getUnit(type?: MissileType, classId?: number, mode?: number, unitId?: number): Missile[] + function getUnits(type: ItemType, name?: string, mode?: number, unitId?: number): ItemUnit[]; + function getUnits(type: ItemType, classId?: number, mode?: number, unitId?: number): ItemUnit[]; + function getUnits(type: TileType, name?: string, mode?: number, unitId?: number): Tile[]; + function getUnits(type: TileType, classId?: number, mode?: number, unitId?: number): Tile[]; + function getUnits(...args: any[]): Unit[]; + function clickItemAndWait(...args: Args[]): boolean; + function clickUnitAndWait(button: number, shift: 0 | 1, unit: Unit): boolean; + const LocalChat: object; + const areaNames: string[]; + function getAreaName(area: number): string; + namespace Game { + function getDistance(...args: any[]): number; + + function getCursorUnit(): ItemUnit; + function getSelectedUnit(): ItemUnit; + function getPlayer(id: any, mode: any, gid: any): Player; + function getMonster(id?: string | number, mode?: number, gid?: number): Monster; + function getNPC(id?: string | number, mode?: number, gid?: number): NPCUnit; + function getObject(id?: string | number, mode?: number, gid?: number): ObjectUnit; + function getMissile(id?: string | number, mode?: number, gid?: number): Missile; + function getItem(id?: string | number, mode?: number, gid?: number): ItemUnit; + function getStairs(id?: string | number, mode?: number, gid?: number): Tile; + function getPresetMonster(area: number, id: number): PresetUnit; + function getPresetMonsters(area: number, id: number): PresetUnit[]; + function getPresetObject(area: number, id: number): PresetUnit; + function getPresetObjects(area: number, id: number): PresetUnit[]; + function getPresetStair(area: number, id: number): PresetUnit; + function getPresetStairs(area: number, id: number): PresetUnit[]; + } + type Args = { + arg1: 0 | 1 | 2; + arg2: number | ItemUnit; + arg3?: number; + arg4?: number; + }; + + namespace Messaging { + function sendToScript(name: string, message: string): boolean; + function sendToProfile(profile: string, mode: number, msg: string, getResponse?: boolean): boolean; + } + + namespace Sort { + function units(a: Unit, b: Unit): number; + function presetUnits(a: PresetUnit, b: PresetUnit): number; + function points(a: [number, number], b: [number, number]): number; + function numbers(a: number, b: number): number; + } +} +export {}; diff --git a/d2bs/kolbot/sdk/types/sdk.d.ts b/d2bs/kolbot/sdk/types/sdk.d.ts new file mode 100644 index 000000000..d9d87653f --- /dev/null +++ b/d2bs/kolbot/sdk/types/sdk.d.ts @@ -0,0 +1,4936 @@ +declare global { + namespace sdk { + export namespace waypoints { + const Ids: [119, 145, 156, 157, 237, 238, 288, 323, 324, 398, 402, 429, 494, 496, 511, 539]; + const Act1: number[]; + const Act2: number[]; + const Act3: number[]; + const Act4: number[]; + const Act5: number[]; + } + + export namespace difficulty { + const Normal: 0; + const Nightmare: 1; + const Hell: 2; + const Difficulties: ["Normal", "Nightmare", "Hell"]; + + const nameOf: (diff: 0 | 1 | 2) => "Normal" | "Nightmare" | "Hell" | false; + } + + export namespace party { + const NoParty: 65535 + namespace flag { + const Invite: 0; + const InParty: 1; + const Accept: 2; + const Cancel: 4; + } + namespace controls { + const Hostile: 1; + const InviteOrCancel: 2; + const Leave: 3; + const Ignore: 4; + const Squelch: 5; + } + } + + export namespace clicktypes { + namespace click { + namespace item { + const Left: 0; + const Right: 1; + const ShiftLeft: 2; // For belt + const MercFromBelt: 3; // For belt + const Mercenary: 4 // Give to merc + } + namespace map { + const LeftDown: 0; + const LeftHold: 1; + const LeftUp: 2; + const RightDown: 3; + const RightHold: 4; + const RightUp: 5; + } + } + namespace shift { + const NoShift: 0; + const Shift: 1 + } + } + + export namespace cursortype { + const Empty: 1; + const ItemOnUnitHover: 3; // see notes + const ItemOnCursor: 4; // see notes + const Identify: 6; + const Repair: 7; + } + + export namespace collision { + const BlockWall: 0x01; + const LineOfSight: 0x02; + const Ranged: 0x04; + const PlayerToWalk: 0x08; + const DarkArea: 0x10; + const Casting: 0x20; + const Unknown: 0x40; + const Players: 0x80; + const Monsters: 0x100; + const Items: 0x200; + const Objects: 0x400; + const ClosedDoor: 0x800; + const IsOnFloor: 0x1000; + const MonsterIsOnFloor: 0x1100; + const MonsterIsOnFloorDarkArea: 0x1110; // in doorway + const FriendlyNPC: 0x2000; + const Unknown2: 0x4000; + const DeadBodies: 0x8000; + const MonsterObject: 0xFFFF; + const BlockMissile: 0x80E; + const WallOrRanged: 0x5; + const BlockWalk: 0x1805; + const FriendlyRanged: 0x2004; + const BoneWall: 4352; + } + + export namespace areas { + const Towns: [1, 40, 75, 103, 109]; + const None: 0; + + // Act 1 + const RogueEncampment: 1; + const BloodMoor: 2; + const ColdPlains: 3; + const StonyField: 4; + const DarkWood: 5; + const BlackMarsh: 6; + const TamoeHighland: 7; + const DenofEvil: 8; + const CaveLvl1: 9; + const UndergroundPassageLvl1: 10; + const HoleLvl1: 11; + const PitLvl1: 12; + const CaveLvl2: 13; + const UndergroundPassageLvl2: 14; + const HoleLvl2: 15; + const PitLvl2: 16; + const BurialGrounds: 17; + const Crypt: 18; + const Mausoleum: 19; + const ForgottenTower: 20; + const TowerCellarLvl1: 21; + const TowerCellarLvl2: 22; + const TowerCellarLvl3: 23; + const TowerCellarLvl4: 24; + const TowerCellarLvl5: 25; + const MonasteryGate: 26; + const OuterCloister: 27; + const Barracks: 28; + const JailLvl1: 29; + const JailLvl2: 30; + const JailLvl3: 31; + const InnerCloister: 32; + const Cathedral: 33; + const CatacombsLvl1: 34; + const CatacombsLvl2: 35; + const CatacombsLvl3: 36; + const CatacombsLvl4: 37; + const Tristram: 38; + const MooMooFarm: 39; + + // Act 2 + const LutGholein: 40; + const RockyWaste: 41; + const DryHills: 42; + const FarOasis: 43; + const LostCity: 44; + const ValleyofSnakes: 45; + const CanyonofMagic: 46; + const A2SewersLvl1: 47; + const A2SewersLvl2: 48; + const A2SewersLvl3: 49; + const HaremLvl1: 50; + const HaremLvl2: 51; + const PalaceCellarLvl1: 52; + const PalaceCellarLvl2: 53; + const PalaceCellarLvl3: 54; + const StonyTombLvl1: 55; + const HallsoftheDeadLvl1: 56; + const HallsoftheDeadLvl2: 57; + const ClawViperTempleLvl1: 58; + const StonyTombLvl2: 59; + const HallsoftheDeadLvl3: 60; + const ClawViperTempleLvl2: 61; + const MaggotLairLvl1: 62; + const MaggotLairLvl2: 63; + const MaggotLairLvl3: 64; + const AncientTunnels: 65; + const TalRashasTomb1: 66; + const TalRashasTomb2: 67; + const TalRashasTomb3: 68; + const TalRashasTomb4: 69; + const TalRashasTomb5: 70; + const TalRashasTomb6: 71; + const TalRashasTomb7: 72; + const DurielsLair: 73; + const ArcaneSanctuary: 74; + + // Act 3 + const KurastDocktown: 75; + const SpiderForest: 76; + const GreatMarsh: 77; + const FlayerJungle: 78; + const LowerKurast: 79; + const KurastBazaar: 80; + const UpperKurast: 81; + const KurastCauseway: 82; + const Travincal: 83; + const SpiderCave: 84; + const SpiderCavern: 85; + const SwampyPitLvl1: 86; + const SwampyPitLvl2: 87; + const FlayerDungeonLvl1: 88; + const FlayerDungeonLvl2: 89; + const SwampyPitLvl3: 90; + const FlayerDungeonLvl3: 91; + const A3SewersLvl1: 92; + const A3SewersLvl2: 93; + const RuinedTemple: 94; + const DisusedFane: 95; + const ForgottenReliquary: 96; + const ForgottenTemple: 97; + const RuinedFane: 98; + const DisusedReliquary: 99; + const DuranceofHateLvl1: 100; + const DuranceofHateLvl2: 101; + const DuranceofHateLvl3: 102; + + // Act 4 + const PandemoniumFortress: 103; + const OuterSteppes: 104; + const PlainsofDespair: 105; + const CityoftheDamned: 106; + const RiverofFlame: 107; + const ChaosSanctuary: 108; + + // Act 5 + const Harrogath: 109; + const BloodyFoothills: 110; + const FrigidHighlands: 111; + const ArreatPlateau: 112; + const CrystalizedPassage: 113; + const FrozenRiver: 114; + const GlacialTrail: 115; + const DrifterCavern: 116; + const FrozenTundra: 117; + const AncientsWay: 118; + const IcyCellar: 119; + const ArreatSummit: 120; + const NihlathaksTemple: 121; + const HallsofAnguish: 122; + const HallsofPain: 123; + const HallsofVaught: 124; + const Abaddon: 125; + const PitofAcheron: 126; + const InfernalPit: 127; + const WorldstoneLvl1: 128; + const WorldstoneLvl2: 129; + const WorldstoneLvl3: 130; + const ThroneofDestruction: 131; + const WorldstoneChamber: 132; + + // Ubers + const MatronsDen: 133; + const ForgottenSands: 134; + const FurnaceofPain: 135; + const UberTristram: 136; + + const actOf: (act: number) => 1 | 2 | 3 | 4 | 5; + const townOf: (townArea: number) => 1 | 40 | 75 | 103 | 109; + const townOfAct: (act: 1 | 2 | 3 | 4 | 5) => 1 | 40 | 75 | 103 | 109; + } + + export namespace skills { + namespace get { + const RightName: 0; + const LeftName: 1; + const RightId: 2; + const LeftId: 3; + const AllSkills: 4 + } + namespace hand { + const Right: 0; + const Left: 1; + const LeftNoShift: 2; + const RightShift: 3; + } + namespace subindex { + const HardPoints: 0; + const SoftPoints: 1 + } + // General + const Attack: 0; + const Kick: 1; + const Throw: 2; + const Unsummon: 3; + const LeftHandThrow: 4; + const LeftHandSwing: 5; + + // Amazon + const MagicArrow: 6; + const FireArrow: 7; + const InnerSight: 8; + const CriticalStrike: 9; + const Jab: 10; + const ColdArrow: 11; + const MultipleShot: 12; + const Dodge: 13; + const PowerStrike: 14; + const PoisonJavelin: 15; + const ExplodingArrow: 16; + const SlowMissiles: 17; + const Avoid: 18; + const Impale: 19; + const LightningBolt: 20; + const IceArrow: 21; + const GuidedArrow: 22; + const Penetrate: 23; + const ChargedStrike: 24; + const PlagueJavelin: 25; + const Strafe: 26; + const ImmolationArrow: 27; + const Dopplezon: 28; + const Decoy: 28; + const Evade: 29; + const Fend: 30; + const FreezingArrow: 31; + const Valkyrie: 32; + const Pierce: 33; + const LightningStrike: 34; + const LightningFury: 35; + + // Sorc + const FireBolt: 36; + const Warmth: 37; + const ChargedBolt: 38; + const IceBolt: 39; + const FrozenArmor: 40; + const Inferno: 41; + const StaticField: 42; + const Telekinesis: 43; + const FrostNova: 44; + const IceBlast: 45; + const Blaze: 46; + const FireBall: 47; + const Nova: 48; + const Lightning: 49; + const ShiverArmor: 50; + const FireWall: 51; + const Enchant: 52; + const ChainLightning: 53; + const Teleport: 54; + const GlacialSpike: 55; + const Meteor: 56; + const ThunderStorm: 57; + const EnergyShield: 58; + const Blizzard: 59; + const ChillingArmor: 60; + const FireMastery: 61; + const Hydra: 62; + const LightningMastery: 63; + const FrozenOrb: 64; + const ColdMastery: 65; + + // Necro + const AmplifyDamage: 66; + const Teeth: 67; + const BoneArmor: 68; + const SkeletonMastery: 69; + const RaiseSkeleton: 70; + const DimVision: 71; + const Weaken: 72; + const PoisonDagger: 73; + const CorpseExplosion: 74; + const ClayGolem: 75; + const IronMaiden: 76; + const Terror: 77; + const BoneWall: 78; + const GolemMastery: 79; + const RaiseSkeletalMage: 80; + const Confuse: 81; + const LifeTap: 82; + const PoisonExplosion: 83; + const BoneSpear: 84; + const BloodGolem: 85; + const Attract: 86; + const Decrepify: 87; + const BonePrison: 88; + const SummonResist: 89; + const IronGolem: 90; + const LowerResist: 91; + const PoisonNova: 92; + const BoneSpirit: 93; + const FireGolem: 94; + const Revive: 95; + + // Paladin + const Sacrifice: 96; + const Smite: 97; + const Might: 98; + const Prayer: 99; + const ResistFire: 100; + const HolyBolt: 101; + const HolyFire: 102; + const Thorns: 103; + const Defiance: 104; + const ResistCold: 105; + const Zeal: 106; + const Charge: 107; + const BlessedAim: 108; + const Cleansing: 109; + const ResistLightning: 110; + const Vengeance: 111; + const BlessedHammer: 112; + const Concentration: 113; + const HolyFreeze: 114; + const Vigor: 115; + const Conversion: 116; + const HolyShield: 117; + const HolyShock: 118; + const Sanctuary: 119; + const Meditation: 120; + const FistoftheHeavens: 121; + const Fanaticism: 122; + const Conviction: 123; + const Redemption: 124; + const Salvation: 125; + + // Barb + const Bash: 126; + const SwordMastery: 127; + const AxeMastery: 128; + const MaceMastery: 129; + const Howl: 130; + const FindPotion: 131; + const Leap: 132; + const DoubleSwing: 133; + const PoleArmMastery: 134; + const ThrowingMastery: 135; + const SpearMastery: 136; + const Taunt: 137; + const Shout: 138; + const Stun: 139; + const DoubleThrow: 140; + const IncreasedStamina: 141; + const FindItem: 142; + const LeapAttack: 143; + const Concentrate: 144; + const IronSkin: 145; + const BattleCry: 146; + const Frenzy: 147; + const IncreasedSpeed: 148; + const BattleOrders: 149; + const GrimWard: 150; + const Whirlwind: 151; + const Berserk: 152; + const NaturalResistance: 153; + const WarCry: 154; + const BattleCommand: 155; + + // General stuff + const IdentifyScroll: 217; + const BookofIdentify: 218; + const TownPortalScroll: 219; + const BookofTownPortal: 220; + + // Druid + const Raven: 221; + const PoisonCreeper: 222; // External + const PlaguePoppy: 222; // Internal + const Werewolf: 223; // External + const Wearwolf: 223; // Internal + const Lycanthropy: 224; // External + const ShapeShifting: 224; // Internal + const Firestorm: 225; + const OakSage: 226; + const SpiritWolf: 227; // External + const SummonSpiritWolf: 227; // Internal + const Werebear: 228; // External + const Wearbear: 228; // Internal + const MoltenBoulder: 229; + const ArcticBlast: 230; + const CarrionVine: 231; // External + const CycleofLife: 231; // Internal + const FeralRage: 232; + const Maul: 233; + const Fissure: 234; // Internal + const Eruption: 234; // Internal + const CycloneArmor: 235; + const HeartofWolverine: 236; + const SummonDireWolf: 237; // External + const SummonFenris: 237; // Internal + const Rabies: 238; + const FireClaws: 239; + const Twister: 240; + const SolarCreeper: 241; // External + const Vines: 241; // Internal + const Hunger: 242; + const ShockWave: 243; + const Volcano: 244; + const Tornado: 245; + const SpiritofBarbs: 246; + const Grizzly: 247; // External + const SummonGrizzly: 247; // Internal + const Fury: 248; + const Armageddon: 249; + const Hurricane: 250; + + // Assa + const FireBlast: 251; // External + const FireTrauma: 251; // Internal + const ClawMastery: 252; + const PsychicHammer: 253; + const TigerStrike: 254; + const DragonTalon: 255; + const ShockWeb: 256; // External + const ShockField: 256; // Internal + const BladeSentinel: 257; + const Quickness: 258; // Internal name + const BurstofSpeed: 258; // Shown name + const FistsofFire: 259; + const DragonClaw: 260; + const ChargedBoltSentry: 261; + const WakeofFire: 262; // External + const WakeofFireSentry: 262; // Internal + const WeaponBlock: 263; + const CloakofShadows: 264; + const CobraStrike: 265; + const BladeFury: 266; + const Fade: 267; + const ShadowWarrior: 268; + const ClawsofThunder: 269; + const DragonTail: 270; + const LightningSentry: 271; + const WakeofInferno: 272; // External + const InfernoSentry: 272; // Internal + const MindBlast: 273; + const BladesofIce: 274; + const DragonFlight: 275; + const DeathSentry: 276; + const BladeShield: 277; + const Venom: 278; + const ShadowMaster: 279; + const PhoenixStrike: 280; // External + const RoyalStrike: 280; // Internal + const WakeofDestructionSentry: 281; // Not used? + const Summoner: 500; // special + namespace tabs { + // Ama + const BowandCrossbow: 0; + const PassiveandMagic: 1; + const JavelinandSpear: 2; + + // Sorc + const Fire: 8; + const Lightning: 9; + const Cold: 10; + + // Necro + const Curses: 16; + const PoisonandBone: 17; + const NecroSummoning: 18; + + // Pala + const PalaCombat: 24; + const Offensive: 25; + const Defensive: 26; + + // Barb + const BarbCombat: 32; + const Masteries: 33; + const Warcries: 34; + + // Druid + const DruidSummon: 40; + const ShapeShifting: 41; + const Elemental: 42; + + // Assa + const Traps: 48; + const ShadowDisciplines: 49; + const MartialArts: 50; + } + } + export const skillTabs: undefined + + export namespace quest { + export namespace item { + // Act 1 + const WirtsLeg: 88; + const HoradricMalus: 89; + const ScrollofInifuss: 524; + const KeytotheCairnStones: 525; + // Act 2 + const FinishedStaff: 91; + const HoradricStaff: 91; + const IncompleteStaff: 92; + const ShaftoftheHoradricStaff: 92; + const ViperAmulet: 521; + const TopoftheHoradricStaff: 521; + const Cube: 549; + const BookofSkill: 552; + // Act 3 + const DecoyGidbinn: 86; + const TheGidbinn: 87; + const KhalimsFlail: 173; + const KhalimsWill: 174; + const PotofLife: 545; + const AJadeFigurine: 546; + const JadeFigurine: 546; + const TheGoldenBird: 547; + const LamEsensTome: 548; + const KhalimsEye: 553; + const KhalimsHeart: 554; + const KhalimsBrain: 555; + // Act 4 + const HellForgeHammer: 90; + const Soulstone: 551; + const MephistosSoulstone: 551; + // Act 5 + const MalahsPotion: 644; + const ScrollofKnowledge: 645; + const ScrollofResistance: 646; + // Pandemonium Event + const KeyofTerror: 647; + const KeyofHate: 648; + const KeyofDestruction: 649; + const DiablosHorn: 650; + const BaalsEye: 651; + const MephistosBrain: 652; + const StandardofHeroes: 658; + // Essences/Token + const TokenofAbsolution: 653; + const TwistedEssenceofSuffering: 654; + const ChargedEssenceofHatred: 655; + const BurningEssenceofTerror: 656; + const FesteringEssenceofDestruction: 657; + // Misc + const TheBlackTowerKey: 544; + } + const items: [ + // act 1 + 88, 89, 524, 525, + // act 2 + 91, 92, 521, 549, 552, + // act 3 + 86, 87, 173, 174, 545, 546, 547, 548, 553, 554, 555, + // act 4 + 90, 551, + // act 5 + 644, 645, 646, + ]; + export namespace chest { + // act1 + const StoneAlpha: 17; + const StoneBeta: 18; + const StoneGamma: 19; + const StoneDelta: 20; + const StoneLambda: 21; + const StoneTheta: 22; // ? + const CainsJail: 26; + const InifussTree: 30; + const MalusHolder: 108; + const Wirt: 268; + + // act 2 + const ViperAmuletChest: 149; + const HoradricStaffHolder: 152; + const HoradricCubeChest: 354; + const HoradricScrollChest: 355; + const ShaftoftheHoradricStaffChest: 356; + const Journal: 357; + + // act 3 + const ForestAltar: 81; + const LamEsensTomeHolder: 193; + const GidbinnAltar: 252; + const KhalimsHeartChest: 405; + const KhalimsBrainChest: 406; + const KhalimsEyeChest: 407; + + // act 4 + const HellForge: 376; + + // act 5 + const BarbCage: 473; + const FrozenAnya: 558; + const AncientsAltar: 546; + } + const chests: [ + // act 1 + 17, 18, 19, 20, 21, 22, 26, 30, 108, + // act 2 + 149, 152, 354, 355, 356, 357, + // act 3 + 81, 193, 405, 406, 407, + // act 4 + 376, + // act 5 + 434, 558, 546 + ]; + export namespace id { + const SpokeToWarriv: 0; + const DenofEvil: 1; + const SistersBurialGrounds: 2; + const TheSearchForCain: 4; + const ForgottenTower: 5; + const ToolsoftheTrade: 3; + const SistersToTheSlaughter: 6; + const AbleToGotoActII: 7; + const SpokeToJerhyn: 8; + const RadamentsLair: 9; + const TheHoradricStaff: 10; + const TheTaintedSun: 11; + const TheArcaneSanctuary: 12; + const TheSummoner: 13; + const TheSevenTombs: 14; + const AbleToGotoActIII: 15; + const SpokeToHratli: 16; + const TheGoldenBird: 20; + const BladeoftheOldReligion: 19; + const KhalimsWill: 18; + const LamEsensTome: 17; + const TheBlackenedTemple: 21; + const TheGuardian: 22; + const AbleToGotoActIV: 23; + const SpokeToTyrael: 24; + const TheFallenAngel: 25; + const HellsForge: 27; + const TerrorsEnd: 26; + const AbleToGotoActV: 28; + const SiegeOnHarrogath: 35; + const RescueonMountArreat: 36; + const PrisonofIce: 37; + const BetrayalofHarrogath: 38; + const RiteofPassage: 39; + const EyeofDestruction: 40; + const Respec: 41; + } + // just common states for now + namespace states { + const Completed: 0; + const ReqComplete: 1; + const GreyedOut: 12; + const PartyMemberComplete: 13; + const CannotComplete: 14; + } + } + + // in game data + export namespace uiflags { + const Inventory: 0x01; + const StatsWindow: 0x02; + const QuickSkill: 0x03; + const SkillWindow: 0x04; + const ChatBox: 0x05; + const NPCMenu: 0x08; + const EscMenu: 0x09; + const KeytotheCairnStonesScreen: 0x10; + const AutoMap: 0x0A; + const ConfigControls: 0x0B; + const Shop: 0x0C; + const ShowItem: 0x0D; + const SubmitItem: 0x0E; + const Quest: 0x0F; + const QuestLog: 0x11; + const StatusArea: 0x12; + const Waypoint: 0x14; + const MiniPanel: 0x15; + const Party: 0x16; + const TradePrompt: 0x17; + const Msgs: 0x18; + const Stash: 0x19; + const Cube: 0x1A; + const ShowBelt: 0x1F; + const Help: 0x21; + const MercScreen: 0x24; + const ScrollWindow: 0x25 + } + + export namespace menu { + const Respec: 0x2BA0; + const Ok: 0x0D49; + const Talk: 0x0D35; + const Trade: 0x0D44; + const TradeRepair: 0x0D06; + const Imbue: 0x0FB1; + const Gamble: 0x0D46; + const Hire: 0x0D45; + const GoEast: 0x0D36; + const GoWest: 0x0D37; + const IdentifyItems: 0x0FB4; + const SailEast: 0x0D38; + const SailWest: 0x0D39; + const RessurectMerc: 0x1507; + const AddSockets: 0x58DC; + const Personalize: 0x58DD; + const TravelToHarrogath: 0x58D2; + } + + // shrine types + export namespace shrines { + const Presets: [2, 81, 83, 170, 344, 197, 202]; + const Ids: [ + 2, 77, 81, 83, 84, 85, 93, 96, 97, 109, 116, 123, 124, + 133, 134, 135, 136, 150, 151, 164, 165, 166, 167, 168, + 170, 172, 173, 184, 190, 191, 197, 199, 200, 201, 202, + 206, 226, 231, 232, 236, 249, 260, 262, 263, 264, 265, + 275, 276, 277, 278, 279, 280, 281, 282, 299, 300, 302, + 303, 320, 325, 343, 344, 361, 414, 415, 421, 422, 423, + 427, 428, 464, 465, 472, 479, 483, 484, 488, 491, 492, + 495, 497, 499, 503, 509, 512, 520, 521, 522 + ]; + const None: 0; + const Refilling: 1; + const Health: 2; + const Mana: 3; + const HealthExchange: 4; + const ManaExchange: 5; + const Armor: 6; + const Combat: 7; + const ResistFire: 8; + const ResistCold: 9; + const ResistLightning: 10; + const ResistPoison: 11; + const Skill: 12; + const ManaRecharge: 13; + const Stamina: 14; + const Experience: 15; + const Enirhs: 16; + const Portal: 17; + const Gem: 18; + const Fire: 19; + const Monster: 20; + const Exploding: 21; + const Poison: 22 + } + + // unit states + export namespace states { + const None: 0; + const FrozenSolid: 1; + const Poison: 2; + const ResistFire: 3; + const ResistCold: 4; + const ResistLightning: 5; + const ResistMagic: 6; + const PlayerBody: 7; + const ResistAll: 8; + const AmplifyDamage: 9; + const FrozenArmor: 10; + const Frozen: 11; + const Inferno: 12; + const Blaze: 13; + const BoneArmor: 14; + const Concentrate: 15; + const Enchant: 16; + const InnerSight: 17; + const SkillMove: 18; + const Weaken: 19; + const ChillingArmor: 20; + const Stunned: 21; + const SpiderLay: 22; + const DimVision: 23; + const Slowed: 24; + const FetishAura: 25; + const Shout: 26; + const Taunt: 27; + const Conviction: 28; + const Convicted: 29; + const EnergyShield: 30; + const Venom: 31; + const BattleOrders: 32; + const Might: 33; + const Prayer: 34; + const HolyFire: 35; + const Thorns: 36; + const Defiance: 37; + const ThunderStorm: 38; + const LightningBolt: 39; + const BlessedAim: 40; + const Stamina: 41; + const Concentration: 42; + const Holywind: 43; + const HolyFreeze: 43; + const HolywindCold: 44; + const HolyFreezeCold: 44; + const Cleansing: 45; + const HolyShock: 46; + const Sanctuary: 47; + const Meditation: 48; + const Fanaticism: 49; + const Redemption: 50; + const BattleCommand: 51; + const PreventHeal: 52; + const Conversion: 53; + const Uninterruptable: 54; + const IronMaiden: 55; + const Terror: 56; + const Attract: 57; + const LifeTap: 58; + const Confuse: 59; + const Decrepify: 60; + const LowerResist: 61; + const OpenWounds: 62; + const Dopplezon: 63; + const Decoy: 63; + const CriticalStrike: 64; + const Dodge: 65; + const Avoid: 66; + const Penetrate: 67; + const Evade: 68; + const Pierce: 69; + const Warmth: 70; + const FireMastery: 71; + const LightningMastery: 72; + const ColdMastery: 73; + const SwordMastery: 74; + const AxeMastery: 75; + const MaceMastery: 76; + const PoleArmMastery: 77; + const ThrowingMastery: 78; + const SpearMastery: 79; + const IncreasedStamina: 80; + const IronSkin: 81; + const IncreasedSpeed: 82; + const NaturalResistance: 83; + const FingerMageCurse: 84; + const NoManaReg: 85; + const JustHit: 86; + const SlowMissiles: 87; + const ShiverArmor: 88; + const BattleCry: 89; + const Blue: 90; + const Red: 91; + const DeathDelay: 92; + const Valkyrie: 93; + const Frenzy: 94; + const Berserk: 95; + const Revive: 96; + const ItemFullSet: 97; + const SourceUnit: 98; + const Redeemed: 99; + const HealthPot: 100; + const HolyShield: 101; + const JustPortaled: 102; + const MonFrenzy: 103; + const CorpseNoDraw: 104; + const Alignment: 105; + const ManaPot: 106; + const Shatter: 107; + const SyncWarped: 108; + const ConversionSave: 109; + const Pregnat: 110; + const Rabies: 112; + const DefenceCurse: 113; + const BloodMana: 114; + const Burning: 115; + const DragonFlight: 116; + const Maul: 117; + const CorpseNoSelect: 118; + const ShadowWarrior: 119; + const FeralRage: 120; + const SkillDelay: 121; + const ProgressiveDamage: 122; + const ProgressiveSteal: 123; + const ProgressiveOther: 124; + const ProgressiveFire: 125; + const ProgressiveCold: 126; + const ProgressiveLighting: 127; + const ShrineArmor: 128; + const ShrineCombat: 129; + const ShrineResLighting: 130; + const ShrineResFire: 131; + const ShrineResCold: 132; + const ShrineResPoison: 133; + const ShrineSkill: 134; + const ShrineManaRegen: 135; + const ShrineStamina: 136; + const ShrineExperience: 137; + const FenrisRage: 138; + const Wolf: 139; + const Wearwolf: 139; + const Bear: 140; + const Wearbear: 140; + const Bloodlust: 141; + const ChangeClass: 142; + const Attached: 143; + const Hurricane: 144; + const Armageddon: 145; + const Invis: 146; + const Barbs: 147; + const HeartofWolverine: 148; + const OakSage: 149; + const VineBeast: 150; + const CycloneArmor: 151; + const ClawMastery: 152; + const CloakofShadows: 153; + const Recyled: 154; + const WeaponBlock: 155; + const Cloaked: 156; + const Quickness: 157; // Internal name + const BurstofSpeed: 157; // External name + const BladeShield: 158; + const Fade: 159; + const RestInPeace: 172; + const Glowing: 175; + const Delerium: 177; + const Antidote: 178; + const Thawing: 179; + const StaminaPot: 180; + } + + export namespace enchant { + const RandName: 1; + const HpMultiply: 2; + const AddLightRadius: 3; + const AddMLvl: 4; + const ExtraStrong: 5; + const ExtraFast: 6; + const Cursed: 7; + const MagicResistant: 8; + const FireEnchanted: 9; + const PoisonDeath: 10; + const InsectDeath: 11; + const ChainLightingDeath: 12; + const IgnoreTargetDefense: 13; + const UnknownMod: 14; + const KillMinionsDeath: 15; + const ChampMods: 16; + const LightningEnchanted: 17; + const ColdEnchanted: 18; + const UnusedMercMod: 19; + const ChargedBoltWhenStruck: 20; + const TempSummoned: 21; + const QuestMod: 22; + const PoisonField: 23; + const Thief: 24; + const ManaBurn: 25; + const Teleportation: 26; + const SpectralHit: 27; + const StoneSkin: 28; + const MultipleShots: 29; + const Aura: 30; + const CorpseExplosion: 31; + const FireExplosionOnDeath: 32; // not sure what the difference is between this and 9 + const FreezeOnDeath: 33; + const SelfResurrect: 34; + const IceShatter: 35; + const ChampStoned: 36; + const ChampStats: 37; + const ChampCurseImmune: 38; + } + + // unit stats + export namespace stats { + const StunLength: 66; + const VelocityPercent: 67; + const OtherAnimrate: 69; + const HpRegen: 74; + + const LastBlockFrame: 95; + const State: 98; + const MonsterPlayerCount: 100; + + const CurseResistance: 109; + const IronMaidenLevel: 129; + const LifeTapLevel: 130; + + const Alignment: 172; + const Target0: 173; + const Target1: 174; + const GoldLost: 175; + const MinimumRequiredLevel: 176; + const ConversionLevel: 176; + const ConversionMaxHp: 177; + const UnitDooverlay: 178; + const AttackVsMontype: 179; + const DamageVsMontype: 180; + + const ArmorOverridePercent: 182; + const FireLength: 315; + const BurningMin: 316; + const BurningMax: 317; + const ProgressiveDamage: 318; + const ProgressiveSteal: 319; + const ProgressiveOther: 320; + const ProgressiveFire: 321; + const ProgressiveCold: 322; + const ProgressiveLightning: 323; + const ProgressiveTohit: 325; + const PoisonCount: 326; + const DamageFramerate: 327; + const PierceIdx: 328; + + const ModifierListSkill: 350; + const ModifierListLevel: 351; + + const LastSentHpPct: 352; + const SourceUnitType: 353; + const SourceUnitId: 354; + + const SkillThornsPercent: 131; + const SkillBoneArmor: 132; + const SkillCycloneArmor: 132; + const SkillBoneArmorMax: 133; + const SkillCycloneArmorMax: 133; + const SkillFade: 181; + const SkillPoisonOverrideLength: 101; + const SkillBypassUndead: 103; + const SkillBypassDemons: 104; + const SkillBypassBeasts: 106; + const SkillHandofAthena: 161; + const SkillStaminaPercent: 162; + const SkillPassiveStaminaPercent: 163; + const SkillConcentration: 164; + const SkillEnchant: 165; + const SkillPierce: 166; + const SkillConviction: 167; + const SkillChillingArmor: 168; + const SkillFrenzy: 169; + const SkillDecrepify: 170; + const SkillArmorPercent: 171; + + const Strength: 0; + const Energy: 1; + const Dexterity: 2; + const Vitality: 3; + const StatPts: 4; + const NewSkills: 5; + const HitPoints: 6; + const MaxHp: 7; + const Mana: 8; + const MaxMana: 9; + const Stamina: 10; + const MaxStamina: 11; + const Level: 12; + const Experience: 13; + const Gold: 14; + const GoldBank: 15; + const ArmorPercent: 16; + const MaxDamagePercent: 17; + const MinDamagePercent: 18; + const EnhancedDamage: 18; + const ToHit: 19; + const ToBlock: 20; + const MinDamage: 21; + const MaxDamage: 22; + const SecondaryMinDamage: 23; + const SecondaryMaxDamage: 24; + const DamagePercent: 25; + const ManaRecovery: 26; + const ManaRecoveryBonus: 27; + const StaminaRecoveryBonus: 28; + const LastExp: 29; + const NextExp: 30; + const ArmorClass: 31; + const Defense: 31; + const ArmorClassVsMissile: 32; + const ArmorClassVsHth: 33; + const NormalDamageReduction: 34; + const MagicDamageReduction: 35; + const DamageResist: 36; + const MagicResist: 37; + const MaxMagicResist: 38; + const FireResist: 39; + const MaxFireResist: 40; + const LightResist: 41; + const LightningResist: 41; + const MaxLightResist: 42; + const ColdResist: 43; + const MaxColdResist: 44; + const PoisonResist: 45; + const MaxPoisonResist: 46; + const DamageAura: 47; + const FireMinDamage: 48; + const FireMaxDamage: 49; + const LightMinDamage: 50; + const LightMaxDamage: 51; + const MagicMinDamage: 52; + const MagicMaxDamage: 53; + const ColdMinDamage: 54; + const ColdMaxDamage: 55; + const ColdLength: 56; + const PoisonMinDamage: 57; + const PoisonMaxDamage: 58; + const PoisonLength: 59; + const LifeDrainMinDamage: 60; + const LifeLeech: 60; + const LifeDrainMaxDamage: 61; + const ManaDrainMinDamage: 62; + const ManaLeech: 62; + const ManaDrainMaxDamage: 63; + const StaminaDrainMinDamage: 64; + const StaminaDrainMaxDamage: 65; + const AttackRate: 68; + const PreviousSkillRight: 181; + const PreviousSkillMiddle: 182; + const PreviousSkillLeft: 183; + const PassiveFireMastery: 329; + const PassiveLightningMastery: 330; + const PassiveColdMastery: 331; + const PassivePoisonMastery: 332; + const PassiveFirePierce: 333; + const PassiveLightningPierce: 334; + const PassiveColdPierce: 335; + const PassivePoisonPierce: 336; + const PassiveCriticalStrike: 337; + const PassiveDodge: 338; + const PassiveAvoid: 339; + const PassiveEvade: 340; + const PassiveWarmth: 341; + const PassiveMasteryMeleeTh: 342; + const PassiveMasteryMeleeDmg: 343; + const PassiveMasteryMeleeCrit: 344; + const PassiveMasteryThrowTh: 345; + const PassiveMasteryThrowDmg: 346; + const PassiveMasteryThrowCrit: 347; + const PassiveWeaponBlock: 348; + const PassiveSummonResist: 349; + const PassiveMagMastery: 357; + const PassiveMagPierce: 358; + const Quantity: 70; + const Value: 71; + const Durability: 72; + const MaxDurability: 73; + const MaxDurabilityPercent: 75; + const MaxHpPercent: 76; + const MaxManaPercent: 77; + const AttackerTakesDamage: 78; + const GoldBonus: 79; + const MagicBonus: 80; + const Knockback: 81; + const TimeDuration: 82; + const AddClassSkills: 83; + const AddExperience: 85; + const HealAfterKill: 86; + const ReducedPrices: 87; + const DoubleHerbDuration: 88; + const LightRadius: 89; + const LightColor: 90; + const ReqPercent: 91; + const LevelReq: 92; + const FasterAttackRate: 93; + const IAS: 93; + const LevelReqPct: 94; + const FasterMoveVelocity: 96; + const FRW: 96; + const NonClassSkill: 97; + const OSkill: 97; + const FasterGetHitRate: 99; + const FHR: 99; + const FasterBlockRate: 102; + const FBR: 102; + const FasterCastRate: 105; + const FCR: 105; + const SingleSkill: 107; + const RestinPeace: 108; + const PoisonLengthResist: 110; + const NormalDamage: 111; + const Howl: 112; + const Stupidity: 113; + const DamagetoMana: 114; + const IgnoreTargetAc: 115; + const IgnoreTargetDefense: 115; + const FractionalTargetAc: 116; + const PreventHeal: 117; + const HalfFreezeDuration: 118; + const ToHitPercent: 119; + const DamageTargetAc: 120; + const DemonDamagePercent: 121; + const UndeadDamagePercent: 122; + const DemontoHit: 123; + const UndeadtoHit: 124; + const Throwable: 125; + const ElemSkill: 126; + const AllSkills: 127; + const AttackerTakesLightDamage: 128; + const Freeze: 134; + const OpenWounds: 135; + const CrushingBlow: 136; + const KickDamage: 137; + const ManaAfterKill: 138; + const HealAfterDemonKill: 139; + const ExtraBlood: 140; + const DeadlyStrike: 141; + const AbsorbFirePercent: 142; + const AbsorbFire: 143; + const AbsorbLightPercent: 144; + const AbsorbLight: 145; + const AbsorbMagicPercent: 146; + const AbsorbMagic: 147; + const AbsorbColdPercent: 148; + const AbsorbCold: 149; + const AbsorbSlash: 262; + const AbsorbCrush: 263; + const AbsorbThrust: 264; + const AbsorbSlashPercent: 265; + const AbsorbCrushPercent: 266; + const AbsorbThrustPercent: 267; + const Slow: 150; + const Indestructible: 152; + const CannotbeFrozen: 153; + const StaminaDrainPct: 154; + const Reanimate: 155; + const Pierce: 156; + const MagicArrow: 157; + const ExplosiveArrow: 158; + const ThrowMinDamage: 159; + const ThrowMaxDamage: 160; + const AddSkillTab: 188; + const NumSockets: 194; + const SkillOnAura: 151; + const SkillOnAttack: 195; + const SkillOnKill: 196; + const SkillOnDeath: 197; + const SkillOnHit: 198; + const SkillOnStrike: 198; + const SkillOnLevelUp: 199; + const SkillOnGetHit: 201; + const SkillWhenStruck: 201; + const ChargedSkill: 204; + const PerLevelArmor: 214; + const PerLevelArmorPercent: 215; + const PerLevelHp: 216; + const PerLevelMana: 217; + const PerLevelMaxDamage: 218; + const PerLevelMaxDamagePercent: 219; + const PerLevelStrength: 220; + const PerLevelDexterity: 221; + const PerLevelEnergy: 222; + const PerLevelVitality: 223; + const PerLevelTohit: 224; + const PerLevelTohitPercent: 225; + const PerLevelColdDamageMax: 226; + const PerLevelFireDamageMax: 227; + const PerLevelLtngDamageMax: 228; + const PerLevelPoisDamageMax: 229; + const PerLevelResistCold: 230; + const PerLevelResistFire: 231; + const PerLevelResistLtng: 232; + const PerLevelResistPois: 233; + const PerLevelAbsorbCold: 234; + const PerLevelAbsorbFire: 235; + const PerLevelAbsorbLtng: 236; + const PerLevelAbsorbPois: 237; + const PerLevelThorns: 238; + const PerLevelFindGold: 239; + const PerLevelFindMagic: 240; + const PerLevelRegenstamina: 241; + const PerLevelStamina: 242; + const PerLevelDamageDemon: 243; + const PerLevelDamageUndead: 244; + const PerLevelTohitDemon: 245; + const PerLevelTohitUndead: 246; + const PerLevelCrushingblow: 247; + const PerLevelOpenwounds: 248; + const PerLevelKickDamage: 249; + const PerLevelDeadlystrike: 250; + const PerLevelFindGems: 251; + const ReplenishDurability: 252; + const ReplenishQuantity: 253; + const ExtraStack: 254; + const Find: 255; + const SlashDamage: 256; + const SlashDamagePercent: 257; + const CrushDamage: 258; + const CrushDamagePercent: 259; + const ThrustDamage: 260; + const ThrustDamagePercent: 261; + const ArmorByTime: 268; + const ArmorPercentByTime: 269; + const HpByTime: 270; + const ManaByTime: 271; + const MaxDamageByTime: 272; + const MaxDamagePercentByTime: 273; + const StrengthByTime: 274; + const DexterityByTime: 275; + const EnergyByTime: 276; + const VitalityByTime: 277; + const TohitByTime: 278; + const TohitPercentByTime: 279; + const ColdDamageMaxByTime: 280; + const FireDamageMaxByTime: 281; + const LtngDamageMaxByTime: 282; + const PoisDamageMaxByTime: 283; + const ResistColdByTime: 284; + const ResistFireByTime: 285; + const ResistLtngByTime: 286; + const ResistPoisByTime: 287; + const AbsorbColdByTime: 288; + const AbsorbFireByTime: 289; + const AbsorbLtngByTime: 290; + const AbsorbPoisByTime: 291; + const FindGoldByTime: 292; + const FindMagicByTime: 293; + const RegenstaminaByTime: 294; + const StaminaByTime: 295; + const DamageDemonByTime: 296; + const DamageUndeadByTime: 297; + const TohitDemonByTime: 298; + const TohitUndeadByTime: 299; + const CrushingBlowByTime: 300; + const OpenWoundsByTime: 301; + const KickDamageByTime: 302; + const DeadlyStrikeByTime: 303; + const FindGemsByTime: 304; + const PierceCold: 305; + const PierceFire: 306; + const PierceLtng: 307; + const PiercePois: 308; + const DamageVsMonster: 309; + const DamagePercentVsMonster: 310; + const TohitVsMonster: 311; + const TohitPercentVsMonster: 312; + const AcVsMonster: 313; + const AcPercentVsMonster: 314; + const ExtraCharges: 324; + const QuestDifficulty: 356; + + // doesn't exist but define for prototypes + const AllRes: 555; + } + + // unit info + export namespace unittype { + const Player: 0; + const NPC: 1; + const Monster: 1; + const Object: 2; + const Missile: 3; + const Item: 4; + const Stairs: 5; // const ToDo: might be more as stairs + } + + export namespace player { + export namespace flag { + const Ignore: 2; + const Hostile: 8; + } + export namespace slot { + const Main: 0; + const Secondary: 1 + } + export namespace move { + const Walk: 0; + const Run: 1 + } + export namespace mode { // sdk.player.mode. + const Death: 0; + const StandingOutsideTown: 1; + const Walking: 2; + const Running: 3; + const GettingHit: 4; + const StandingInTown: 5; + const WalkingInTown: 6; + const Attacking1: 7; + const Attacking2: 8; + const Blocking: 9; + const CastingSkill: 10; + const ThrowingItem: 11; + const Kicking: 12; + const UsingSkill1: 13; + const UsingSkill2: 14; + const UsingSkill3: 15; + const UsingSkill4: 16; + const Dead: 17; + const SkillActionSequence: 18; + const KnockedBack: 19; + } + namespace _class { + const Amazon: 0; + const Sorceress: 1; + const Necromancer: 2; + const Paladin: 3; + const Barbarian: 4; + const Druid: 5; + const Assassin: 6; + + const nameOf: (classid: 0 | 1 | 2 | 3 | 4 | 5 | 6) => "Amazon" | "Sorceress" | "Necromancer" | "Paladin" | "Barbarian" | "Druid" | "Assassin" | false; + } + export { _class as class }; + } + + export namespace npcs { + // same as monsters but more clear to use units.npcs.mode + namespace mode { + const Death: 0; + const Standing: 1; + const Walking: 2; + const GettingHit: 3; + const Attacking1: 4; + const Attacking2: 5; + const Blocking: 6; + const CastingSkill: 7; + const UsingSkill1: 8; + const UsingSkill2: 9; + const UsingSkill3: 10; + const UsingSkill4: 11; + const Dead: 12; + const KnockedBack: 13; + const Spawning: 14; + const Running: 15 + } + + const Akara: 148; + const Alkor: 254; + const Asheara: 252; + const WarrivAct1: 155; + const WarrivAct2: 175; + const Atma: 176; + const Tyrael: 367; + const Tyrael2: 251; + const Tyrael3: 521; + const Charsi: 154; + const DeckardCain1: 146; + const DeckardCain2: 244; + const DeckardCain3: 245; + const DeckardCain4: 246; + const DeckardCain5: 265; + const DeckardCain6: 520; + const Drognan: 177; + const Elzix: 199; + const Fara: 178; + const Gheed: 147; + const Greiz: 198; + const Halbu: 257; + const Hratli: 253; + const Jamella: 405; + const Jerhyn: 201; + const Kaelan: 331; + const Kashya: 150; + const Larzuk: 511; + const Lysander: 202; + const Malah: 513; + const Meshif: 210; + const Meshif2: 264; + const Natalya: 297; + const Ormus: 255; + const NihlathakNPC: 526; + const Qualkehk: 515; + const RogueScout: 270; + const TempleGuard1: 52; + const TempleGuard2: 665; + const TempleGuard3: 666; + const Townguard1: 535; + const Townguard2: 536; + } + + export namespace objects { + namespace mode { + const Inactive: 0; + const Interacted: 1; + const Active: 2; + } + + const chestIds: [ + 5, 6, 87, 104, 105, 106, 107, 143, 140, 141, 144, 146, 147, 148, 176, 177, 181, 183, 198, 240, 241, + 242, 243, 329, 330, 331, 332, 333, 334, 335, 336, 354, 355, 356, 371, 387, 389, 390, 391, 397, 405, + 406, 407, 413, 420, 424, 425, 430, 431, 432, 433, 454, 455, 501, 502, 504, 505, 580, 581 + ]; + + // act1 + const MoldyTome: 8; + const A1TownFire: 39; + const A1Waypoint: 119; + const StoneAlpha: 17; + const StoneBeta: 18; + const StoneGamma: 19; + const StoneDelta: 20; + const StoneLambda: 21; + const StoneTheta: 22; + const CainsJail: 26; + const InifussTree: 30; + const Malus: 108; + + // act 2 + const A2Waypoint: 156; + const A2UndergroundUpStairs: 22; + const TrapDoorA2: 74; // ancienttunnel/sewers act 2 + const DoorbyDockAct2: 75; // incorrect ? const TODO: figure out what 75 really corresponds to since the door is obj type 5 with classid 20 + const PortaltoDurielsLair: 100; + const HoradricStaffHolder: 152; + const ArcaneSanctuaryPortal: 298; + const HoradricCubeChest: 354; + const HoradricScrollChest: 355; + const Journal: 357; + + // act 3 + const A3Waypoint: 237; + const ForestAltar: 81; + const LamEsensTome: 193; + const SewerStairsA3: 366; + const SewerLever: 367; + const DuranceEntryStairs: 386; + const RedPortalToAct4: 342; + const CompellingOrb: 404; + + // act 4 + const A4Waypoint: 398; + const SealGlow: 131; + const DiabloStar: 255; + const DiabloSealInfector: 392; + const DiabloSealInfector2: 393; + const DiabloSealSeis: 394; + const DiabloSealVizier: 396; + const DiabloSealVizier2: 395; + const RedPortalToAct5: 566; // The one of tyreal + + // act 5 + const A5Waypoint: 429; + const SideCavesA5: 75; // FrozenRiver, DrifterCavern; IcyCellar + const Act5Gate: 449; + const KorlictheProtectorStatue: 474; + const TalictheDefenderStatue: 475; + const MadawctheGuardianStatue: 476; + const AncientsAltar: 546; + const ArreatEnterAncientsWay: 564; + const ArreatEnterWorldstone: 547; + //const AncientsDoor: 547; + const AncientsDoor: 547; // Worldstone keep lvl 1 + const FrozenAnya: 558; + const FrozenAnyasPlatform: 460; + const NihlathaksPlatform: 462; + const WorldstonePortal: 563; + + const FrigidHighlandsChest: 455; + const IcyCellarChest: 397; + + const SmallSparklyChest: 397; + const LargeSparklyChest: 455; + const SuperChest: 580; + + // misc + const BubblingPoolofBlood: 82; + const HornShrine: 83; + const Stash: 267; + const BluePortal: 59; + const RedPortal: 60; + const Smoke: 401; + } + + export namespace exits { + namespace type { + const WalkThrough: 1; + const Stairs: 2; + const RedPortal: 60; + } + namespace preset { + const AreaEntrance: 0; // special + // act 1 + const CaveHoleUp: 4; + const CaveHoleLvl2: 5; + const Crypt: 6; + const Mausoleum: 7; + const CryptMausExit: 8; + const JailUpStairs: 13; + const JailDownStairs: 14; + const CathedralDownStairs: 15; + const CathedralUpStairs: 16; + const CatacombsUpStairs: 17; + const CatacombsDownStairs: 18; + + // act 2 + const A2SewersTrapDoor: 19; + const A2EnterSewersDoor: 20; + const A2ExitSewersDoor: 21; + const A2UndergroundUpStairs: 22; + const A2DownStairs: 23; + const EnterHaremStairs: 24; + const ExitHaremStairs: 25; + const PreviousLevelHaremRight: 26; + const PreviousLevelHaremLeft: 27; + const NextLevelHaremRight: 28; + const NextLevelHaremLeft: 29; + const PreviousPalaceRight: 30; + const PreviousPalaceLeft: 31; + const NextLevelPalace: 32; + const EnterStonyTomb: 33; + const EnterHalls: 36; + const EnterTalTomb1: 38; + const EnterTalTomb2: 39; + const EnterTalTomb3: 40; + const EnterTalTomb4: 41; + const EnterTalTomb5: 42; + const EnterTalTomb6: 43; + const EnterTalTomb7: 44; + const PreviousAreaTomb: 45; + const NextLevelTomb: 46; + const EnterMaggotLair: 47; + const PreviousAreaMaggotLair: 48; + const NextLevelMaggotLair: 49; + const AncientTunnelsTrapDoor: 50; + const EntrancetoDurielsLair: 100; + + // act 3 + const EnterSpiderHole: 51; + const ExitSpiderHole: 52; + const EnterPit: 53; + const EnterDungeon: 54; + const PreviousAreaDungeon: 55; + const NextLevelDungeon: 56; + const A3EnterSewers: 57; + const A3ExitSewersUpperK: 58; + const A3SewersPreviousArea: 58; + const A3ExitSewers: 59; + const A3NextLevelSewers: 60; + const EnterTemple: 61; + const ExitTemple: 63; + const EnterDurance: 64; + const PreviousLevelDurance: 65; + const NextLevelDurance: 68; + const SewerStairsA3: 366; + const DuranceEntryStairs: 386; + + // act 4 + const EnterRiverStairs: 69; + const ExitRiverStairs: 70; + // act 5 + const EnterCrystal: 71; + const A5ExitCave: 73; + const A5NextLevelCave: 74; + const EnterSubLevelCave: 75; + const EnterNithsTemple: 76; + const PreviousAreaNithsTemple: 77; + const NextAreaNithsTemple: 78; + const ArreatEnterAncientsWay: 79; + const ArreatEnterWorldstone: 80; + const PreviousAreaWorldstone: 81; + const NextAreaWorldstone: 82; + } + } + + export namespace monsters { + namespace preset { + // Confirmed + const Izual: 256; + const Bishibosh: 734; + const Bonebreak: 735; + const Coldcrow: 736; + const Rakanishu: 737; + const TreeheadWoodFist: 738; + const Griswold: 739; + const TheCountess: 740; + const PitspawnFouldog: 741; + const FlamespiketheCrawler: 742; + const BoneAsh: 743; + const Radament: 744; + const BloodwitchtheWild: 745; + const Fangskin: 746; + const Beetleburst: 747; + const CreepingFeature: 748; + const ColdwormtheBurrower: 749; + const FireEye: 750; + const DarkElder: 751; + const TheSummoner: 752; + const AncientKaatheSoulless: 753; + const TheSmith: 754; + const SszarktheBurning: 755; + const WitchDoctorEndugu: 756; + const Stormtree: 757; + const BattlemaidSarina: 758; + const IcehawkRiftwing: 759; + const IsmailVilehand: 760; + const GelebFlamefinger: 761; + const BremmSparkfist: 762; + const ToorcIcefist: 763; + const WyandVoidfinger: 764; + const MafferDragonhand: 765; + const WingedDeath: 766; + const Taintbreeder: 768; + const RiftwraiththeCannibal: 769; + const InfectorofSouls: 770; + const LordDeSeis: 771; + const GrandVizierofChaos: 772; + const TheCowKing: 773; + const Corpsefire: 774; + const Hephasto: 775; + const ShenktheOverseer: 776; + const TalictheDefender: 777; + const MadawctheGuardian: 778; + const KorlictheProtector: 779; + const AxeDweller: 780; + const BonesawBreaker: 781; + const DacFarren: 782; + const EldritchtheRectifier: 783; + const EyebacktheUnleashed: 784; + const ThreshSocket: 785; + const Pindleskin: 786; + const SnapchipShatter: 787; + const AnodizedElite: 788; + const VinvearMolech: 789; + const SharpToothSayer: 790; + const MagmaTorquer: 791; + const BlazeRipper: 792; + const Frozenstein: 793; + const Nihlathak: 794; + const ColenzotheAnnihilator: 795; + const AchmeltheCursed: 796; + const BartuctheBloody: 797; + const VentartheUnholy: 798; + const ListertheTormentor: 799; + const BloodRaven: 805; + + // Unconfirmed + // Questionable + const GriefGrumble: 741; // JailLvl2 + const UniqueJailLvl3: 273; + const UniqueArcaneSanctuary: 371; + } + namespace mode { + const Death: 0; + const Standing: 1; + const Walking: 2; + const GettingHit: 3; + const Attacking1: 4; + const Attacking2: 5; + const Blocking: 6; + const CastingSkill: 7; + const UsingSkill1: 8; + const UsingSkill2: 9; + const UsingSkill3: 10; + const UsingSkill4: 11; + const Dead: 12; + const KnockedBack: 13; + const Spawning: 14; + const Running: 15 + } + namespace spectype { + const All: 0; + const Super: 1; + const Champion: 2; + const Unique: 4; + const SuperUnique: 5; + const Magic: 6; + const Minion: 8; + } + // todo - determine what all these correlate to + namespace type { + const Undead: 1; + const Demon: 2; + const Insect: 3; + const Human: 4; + const Construct: 5; + const LowUndead: 6; + const HighUndead: 7; + const Skeleton: 8; + const Zombie: 9; + const BigHead: 10; + const FoulCrow: 11; + const Fallen: 12; + const Brute: 13; + const SandRaider: 14; + const Wraith: 15; + const CorruptRogue: 16; + const Baboon: 17; + const GoatMan: 18; + const QuillRat: 19; + const SandMaggot: 20; + const Viper: 21; + const SandLeaper: 22; + const PantherWoman: 23; + const Swarm: 24; + const Scarab: 25; + const Mummy: 26; + const Unraveler: 27; + const Vulture: 28; + const Mosquito: 29; + const WillowWisp: 30; + const Arach: 31; + const ThornHulk: 32; + const Vampire: 33; + const BatDemon: 34; + const Fetish: 35; + const Blunderbore: 36; + const UndeadFetish: 37; + const Zakarum: 38; + const FrogDemon: 39; + const Tentacle: 40; + const FingerMage: 41; + const Golem: 42; + const Vilekind: 43; + const Regurgitator: 44; + const DoomKnight: 45; + const CouncilMember: 46; + const MegaDemon: 47; + const Bovine: 48; + const SeigeBeast: 49; + const SnowYeti: 50; + const Minion: 51; + const Succubus: 52; + const Overseer: 53; + const Imp: 54; + const FrozenHorror: 55; + const BloodLord: 56; + const DeathMauler: 57; + const PutridDefiler: 58; + } + const DiablosBoneCage: 340; + const DiablosBoneCage2: 342; + const Dummy1: 149; + const Dummy2: 268; + const AbyssKnight: 311; + const Afflicted: 10; + const Afflicted2: 580; + const AlbinoRoach: 95; + const Ancient1: 104; + const Ancient2: 669; + const Ancient3: 670; + const Apparition: 41; + const Arach1: 122; + const Arach2: 685; + const Assailant: 33; + const Assailant2: 603; + const BaalColdMage: 381; + const Balrog1: 360; + const Balrog2: 686; + const Banished: 135; + const Barbs: 422; + const Bear1: 428; + const Bear2: 431; + const Beast: 441; + const BerserkSlayer: 462; + const BlackArcher: 163; + const BlackLancer1: 168; + const BlackLancer2: 617; + const BlackLocusts: 88; + const BlackRaptor1: 17; + const BlackRaptor2: 592; + const BlackRogue: 46; + const BlackSoul1: 121; + const BlackSoul2: 640; + const BlackVultureNest: 208; + const BloodBoss: 482; + const BloodBringer: 443; + const BloodClan1: 55; + const BloodClan2: 588; + const BloodDiver: 139; + const BloodGolem: 290; + const BloodHawk1: 16; + const BloodHawk2: 591; + const BloodHawkNest: 207; + const BloodHook: 116; + const BloodHookNest: 336; + const BloodLord1: 134; + const BloodLord2: 695; + const BloodWing: 117; + const BloodWingNest: 337; + const Blunderbore1: 186; + const Blunderbore2: 618; + const BoneArcher1: 172; + const BoneArcher2: 576; + const BoneMage1: 275; + const BoneMage2: 380; + const BoneMage3: 384; + const BoneMage4: 388; + const BoneMage5: 624; + const BoneWarrior1: 2; + const BoneWarrior2: 648; + const HellBovine: 391; + const BrambleHulk: 128; + const Brute: 24; + const Bunny: 556; + const BurningDead: 3; + const BurningDeadArcher1: 173; + const BurningDeadArcher2: 575; + const BurningDeadArcher3: 577; + const BurningDeadMage1: 276; + const BurningDeadMage2: 385; + const BurningDeadMage3: 389; + const BurningDeadMage4: 621; + const BurningSoul1: 641; + const BurningSoul2: 120; + const Cadaver1: 100; + const Cadaver2: 703; + const Cantor: 239; + const CarrionBird1: 110; + const CarrionBird2: 608; + const Carver1: 642; + const Carver2: 20; + const CarverShaman: 645; + const CarverShaman2: 59; + const CaveLeaper1: 79; + const CaveLeaper2: 629; + const ClawViper1: 74; + const ClawViper2: 594; + const CloudStalker1: 18; + const CloudStalker2: 593; + const CloudStalkerNest: 209; + const Combatant1: 522; + const Combatant2: 523; + const ConsumedFireBoar: 464; + const ConsumedIceBoar: 463; + const CorpseSpitter: 308; + const Corpulent: 307; + const Creature1: 248; + const Creature2: 427; + const Creeper: 413; + const CrushBiest: 442; + const Crusher: 26; + const Damned1: 14; + const Damned2: 584; + const DarkArcher1: 162; + const DarkArcher2: 614; + const DarkFamiliar: 140; + const DarkHunter: 43; + const DarkLancer1: 167; + const DarkLancer2: 616; + const DarkLord1: 133; + const DarkLord2: 697; + const DarkOne1: 22; + const DarkOne2: 644; + const DarkRanger: 160; + const DarkShaman1: 61; + const DarkShaman2: 647; + const DarkShape: 42; + const DarkSpearwoman: 165; + const DarkStalker: 45; + const DeamonSteed: 445; + const DeathClan1: 57; + const DeathClan2: 589; + const Decayed: 97; + const DefiledWarrior: 440; + const Defiler1: 546; + const Defiler2: 547; + const Defiler3: 548; + const Defiler4: 549; + const Defiler5: 550; + const DesertWing: 136; + const Destruction: 410; + const Devilkin: 643; + const Devilkin2: 21; + const DevilkinShaman: 646; + const DevilkinShaman2: 60; + const Devourer: 70; + const DevourerEgg: 192; + const DevourerQueen: 286; + const DevourerYoung: 182; + const Disfigured: 13; + const Disfigured2: 583; + const Dominus1: 474; + const Dominus2: 636; + const DoomApe: 51; + const DoomKnight: 310; + const DoomKnight1: 699; + const DoomKnight2: 700; + const Drehya1: 512; + const Drehya2: 527; + const DriedCorpse: 96; + const DrownedCarcass: 8; + const DuneBeast: 48; + const DungSoldier: 91; + const Dweller: 247; + const Eagle: 429; + const Embalmed: 98; + const Faithful: 236; + const Fallen: 19; + const FallenShaman: 58; + const FanaticMinion: 461; + const Feeder: 115; + const FeederNest: 335; + const Fenris: 421; + const Fetish1: 142; + const BoneFetish2: 213; + const Fetish3: 397; + const FetishShaman: 279; + const Fiend1: 137; + const Fiend2: 651; + const FireBoar: 456; + const FireTower: 372; + const FlameSpider: 125; + const Flayer1: 143; + const BoneFetish3: 214; + const Flayer3: 398; + const Flayer4: 659; + const Flayer5: 656; + const FlayerShaman1: 280; + const FlayerShaman2: 662; + const FleshArcher: 164; + const FleshBeast1: 301; + const FleshBeast2: 678; + const FleshHunter: 47; + const FleshLancer: 169; + const FleshSpawner1: 298; + const FleshSpawner2: 676; + const FlyingScimitar: 234; + const FoulCrow: 15; + const FoulCrow2: 590; + const FoulCrowNest: 206; + const FrenziedHellSpawn: 465; + const FrenziedIceSpawn: 466; + const GargantuanBeast: 28; + const Geglash: 200; + const Ghost1: 38; + const Ghost2: 631; + const Ghoul: 7; + const GhoulLord1: 131; + const GhoulLord2: 696; + const GiantLamprey: 71; + const GiantLampreyEgg: 193; + const GiantLampreyQueen: 287; + const GiantLampreyYoung: 183; + const GiantUrchin: 317; + const Gloam1: 118; + const Gloam2: 639; + const Gloombat1: 138; + const Gloombat2: 650; + const Gorbelly: 187; + const GoreBearer: 444; + const GreaterHellSpawn1: 459; + const GreaterHellSpawn2: 684; + const GreaterIceSpawn: 460; + const Groper: 304; + const Grotesque1: 300; + const Grotesque2: 675; + const GrotesqueWyrm1: 303; + const GrotesqueWyrm2: 677; + const Guardian1: 102; + const Guardian2: 667; + const Hawk: 419; + const Heirophant1: 240; + const Heirophant2: 241; + const Heirophant3: 673; + const Heirophant4: 674; + const HellBuzzard: 112; + const HellCat: 86; + const HellClan1: 56; + const HellClan2: 587; + const HellSlinger: 376; + const HellSpawn1: 457; + const HellSpawn2: 683; + const HellSwarm: 90; + const HellWhip: 483; + const HollowOne: 101; + const Horror: 4; + const Horror1: 501; + const Horror2: 502; + const Horror3: 503; + const Horror4: 504; + const Horror5: 505; + const HorrorArcher1: 174; + const HorrorArcher2: 579; + const HorrorMage1: 277; + const HorrorMage2: 382; + const HorrorMage3: 386; + const HorrorMage4: 390; + const HorrorMage5: 623; + const HorrorMage6: 625; + const HorrorMage7: 626; + const Hs1: 560; + const HungryDead: 6; + const Huntress1: 83; + const Huntress2: 627; + const Hut: 528; + const Hydra1: 351; + const Hydra2: 352; + const Hydra3: 353; + const IceBoar: 455; + const IceSpawn: 458; + const Imp1: 492; + const Imp2: 493; + const Imp3: 494; + const Imp4: 495; + const Imp5: 496; + const Imp6: 688; + const Imp7: 689; + const Infidel1: 32; + const Infidel2: 600; + const InsaneHellSpawn: 467; + const InsaneIceSpawn: 468; + const Invader1: 31; + const Invader2: 602; + const Itchies: 87; + const JungleHunter: 50; + const JungleUrchin: 67; + const Larva: 283; + const Lasher: 480; + const LightningSpire: 371; + const Lord1: 506; + const Lord2: 507; + const Lord3: 508; + const Lord4: 509; + const Lord5: 510; + const Lord6: 652; + const Lord7: 653; + const Maggot: 227; + const Malachai: 408; + const Marauder: 30; + const Marauder2: 599; + const Master: 418; + const Mauler: 188; + const Mauler1: 529; + const Mauler12: 604; + const Mauler2: 530; + const Mauler3: 531; + const Mauler4: 532; + const Mauler5: 533; + const Mauler6: 619; + const MawFiend: 694; + const MawFiend2: 309; + const Council1: 345; + const Council2: 346; + const Council3: 347; + const Council4: 557; + const Minion1: 572; + const Minion2: 573; + const Enslaved: 453; + const MinionSlayerSpawner: 485; + const MinionSpawner: 484; + const Misshapen1: 12; + const Misshapen2: 582; + const MoonClan1: 53; + const MoonClan2: 585; + const BaalSubjectMummy: 105; + const Navi: 266; + const Flavie: 266; + const NightClan1: 54; + const NightClan2: 586; + const NightLord: 132; + const NightMarauder: 295; + const NightSlinger1: 375; + const NightSlinger2: 395; + const NightTiger: 85; + const OblivionKnight1: 312; + const OblivionKnight2: 701; + const OblivionKnight3: 702; + const OverLord: 481; + const OverSeer: 479; + const PitLord1: 361; + const PitLord2: 687; + const PitViper1: 76; + const PitViper2: 595; + const PlagueBearer: 9; + const PlagueBugs: 89; + const PoisonSpinner: 124; + const PreservedDead: 99; + const ProwlingDead: 438; + const QuillBear: 313; + const QuillRat1: 63; + const QuillRat2: 605; + const RatMan1: 141; + const RatMan2: 396; + const BoneFetish1: 212; + const RatMan4: 407; + const RatManShaman: 278; + const RazorBeast: 316; + const RazorPitDemon: 82; + const RazorSpine1: 66; + const RazorSpine2: 607; + const ReanimatedHorde: 437; + const Returned1: 1; + const Returned2: 649; + const ReturnedArcher1: 171; + const ReturnedArcher2: 578; + const ReturnedMage: 274; + const ReturnedMage1: 379; + const ReturnedMage2: 383; + const ReturnedMage3: 387; + const ReturnedMage4: 620; + const ReturnedMage5: 622; + const RiverStalkerHead: 262; + const RiverStalkerLimb: 259; + const RockDweller: 49; + const RockWorm: 69; + const RockWormEgg: 191; + const RockWormQueen: 285; + const RockWormYoung: 181; + const RotWalker: 436; + const SaberCat1: 84; + const SaberCat2: 628; + const Salamander1: 75; + const Salamander2: 596; + const SandFisher: 123; + const SandLeaper: 78; + const SandMaggot: 68; + const SandMaggotEgg: 190; + const SandMaggotYoung: 180; + const SandRaider1: 29; + const SandRaider2: 601; + const DeathBeetle: 92; + const Scarab1: 93; + const Scarab2: 654; + const Sentry1: 411; + const Sentry2: 412; + const Sentry3: 415; + const Sentry4: 416; + const SerpentMagus1: 77; + const SerpentMagus2: 598; + const Sexton: 238; + const Skeleton: 0; + const SkeletonArcher: 170; + const Slayerexp1: 454; + const Slayerexp2: 682; + const Slinger1: 373; + const Slinger2: 610; + const Slinger3: 611; + const Slinger4: 612; + const SnowYeti1: 446; + const SnowYeti2: 447; + const SnowYeti3: 448; + const SnowYeti4: 449; + const SoulKiller: 691; + const SoulKiller1: 399; + const SoulKiller2: 144; + const SoulKiller3: 215; + const SoulKiller4: 658; + const SoulKiller5: 661; + const SoulKillerShaman1: 664; + const SoulKillerShaman2: 281; + const SpearCat: 394; + const SpearCat1: 374; + const Specter1: 40; + const Specter2: 633; + const SpiderMagus: 126; + const SpikeFiend1: 64; + const SpikeFiend2: 606; + const Spikefist: 130; + const SpikeGiant: 314; + const SteelWeevil1: 94; + const SteelWeevil2: 655; + const StormCaster1: 306; + const StormCaster2: 693; + const Strangler1: 305; + const Strangler2: 692; + const StygianDog: 302; + const StygianDoll1: 145; + const StygianDoll2: 216; + const StygianDoll3: 400; + const StygianDoll4: 660; + const StygianDoll5: 657; + const StygianDoll6: 690; + const StygianDollShaman1: 663; + const StygianDollShaman2: 282; + const StygianFury: 476; + const StygianHag: 299; + const StygianHarlot: 471; + const StygianWatcherHead: 263; + const StygianWatcherLimb: 260; + const Succubusexp1: 469; + const Succubusexp2: 634; + const Sucker: 114; + const SuckerNest: 334; + const Summoner: 250; + const SwampGhost: 119; + const Tainted: 11; + const Tainted2: 581; + const Taunt: 545; + const Temptress1: 472; + const Temptress2: 473; + const Temptress3: 635; + const Tentacle1: 562; + const Tentacle2: 563; + const Tentacle3: 564; + const Tentacle4: 565; + const Tentacle5: 566; + const ThornBeast: 65; + const ThornBrute: 315; + const ThornedHulk1: 127; + const ThornedHulk2: 609; + const Thrasher: 129; + const TombCreeper1: 80; + const TombCreeper2: 630; + const TombViper1: 73; + const TombViper2: 597; + const TrappedSoul1: 403; + const TrappedSoul2: 404; + const TreeLurker: 81; + const UndeadScavenger: 111; + const UnholyCorpse1: 439; + const UnholyCorpse2: 698; + const Unraveler1: 103; + const Unraveler2: 668; + const Urdar: 189; + const VenomLord1: 362; + const VenomLord2: 558; + const VileArcher1: 161; + const VileArcher2: 613; + const VileHunter: 44; + const VileLancer1: 166; + const VileLancer2: 615; + const VileTemptress: 470; + const VileWitch1: 475; + const VileWitch2: 638; + const WailingBeast: 27; + const WarpedFallen: 23; + const WarpedShaman: 62; + const Warrior: 417; + const WaterWatcherHead: 261; + const WaterWatcherLimb: 258; + const WingedNightmare: 113; + const Witch1: 637; + const Witch2: 477; + const Witch3: 478; + const Wolf1: 359; + const Wolf2: 420; + const Wolf3: 430; + const WolfRider1: 450; + const WolfRider2: 451; + const WolfRider3: 452; + const WorldKiller1: 679; + const WorldKiller2: 72; + const WorldKillerEgg1: 681; + const WorldKillerEgg2: 194; + const WorldKillerQueen: 288; + const WorldKillerYoung1: 680; + const WorldKillerYoung2: 184; + const Worm1: 551; + const Worm2: 552; + const Worm3: 553; + const Worm4: 554; + const Worm5: 555; + const Wraith1: 39; + const Wraith2: 632; + const Yeti: 25; + const Zakarumite: 235; + const Zealot1: 237; + const Zealot2: 671; + const Zealot3: 672; + const Zombie: 5; + + // Bosses/Ubers + const Andariel: 156; + const Duriel: 211; + const Mephisto: 242; + const Diablo: 243; + const DiabloClone: 333; + const ThroneBaal: 543; + const Baal: 544; + const BaalClone: 570; + const UberMephisto: 704; + const UberBaal: 705; + const UberIzual: 706; + const Lilith: 707; + const UberDuriel: 708; + const UberDiablo: 709; + + // Mini-Bosses + const TheSmith: 402; + const BloodRaven: 267; + const Radament: 229; + const TheSummoner: 250; + const Griswold: 365; + const Izual: 256; + const Hephasto: 409; + const KorlictheProtector: 540; + const TalictheDefender: 541; + const MadawctheGuardian: 542; + const ListerTheTormenter: 571; + const TheCowKing: 743; + const ColdwormtheBurrower: 284; + const Nihlathak: 526; + + // Objects + const Turret1: 348; + const Turret2: 349; + const Turret3: 350; + const CatapultS: 497; + const CatapultE: 498; + const CatapultSiege: 499; + const CatapultW: 500; + const Compellingorb: 366; + const GargoyleTrap: 273; + const MummyGenerator: 228; + const Stairs: 559; + const BarricadeDoor1: 432; + const BarricadeDoor2: 433; + const PrisonDoor: 434; + const BarricadeTower: 435; + const BarricadeWall1: 524; + const BarricadeWall2: 525; + + // Misc? + const Youngdiablo: 368; + const Left: 525; + const Life: 426; + const Effect: 574; + const Pet: 414; + const Prince: 249; + const POW: 534; + const Right: 524; + const Sage: 424; + const Town: 514; + const Cow: 179; + } + + export namespace summons { + namespace type { + const Valkyrie: 2; + const Golem: 3; + const Skeleton: 4; + const SkeletonMage: 5; + const Revive: 6; + const Mercenary: 7; + const Dopplezon: 8; + const Raven: 10; + const SpiritWolf: 11; + const Fenris: 12; + const DireWolf: 12; + const Totem: 13; + const Spirit: 13; + const Vine: 14; + const Grizzly: 15; + const ShadowWarrior: 16; + const Shadow: 16; + const AssassinTrap: 17; + const Hydra: 19; + } + + namespace mode { + const Death: 0; + const Standing: 1; + const Walking: 2; + const GettingHit: 3; + const Attacking1: 4; + const Attacking2: 5; + const Blocking: 6; + const CastingSkill: 7; + const UsingSkill1: 8; + const UsingSkill2: 9; + const UsingSkill3: 10; + const UsingSkill4: 11; + const Dead: 12; + const KnockedBack: 13; + const Spawning: 14; + const Running: 15 + } + + const ClayGolem: 289; + const Dopplezon: 356; + const Valkyrie: 357; + const FireGolem: 292; + const IronGolem: 291; + const NecroMage: 364; + const NecroSkeleton: 363; + const Poppy: 425; + const Wolverine: 423; + } + + export namespace mercs { + namespace mode { + const Death: 0; + const Standing: 1; + const Walking: 2; + const GettingHit: 3; + const Attacking1: 4; + const Attacking2: 5; + const Blocking: 6; + const CastingSkill: 7; + const UsingSkill1: 8; + const UsingSkill2: 9; + const UsingSkill3: 10; + const UsingSkill4: 11; + const Dead: 12; + const KnockedBack: 13; + const Spawning: 14; + const Running: 15 + } + + const Rogue: 271; + const Guard: 338; + const IronWolf: 359; + const A5Barb: 561; + } + + export namespace missiles { + const DiabloLightning: 172; + const FissureCrack1: 462; + const FissureCrack2: 463; + } + + export namespace storage { + const Equipped: 1; + const Belt: 2; + const Inventory: 3; + const TradeWindow: 5; + const Cube: 6; + const Stash: 7; + } + + export namespace node { + const NotOnPlayer: 0; + const Storage: 1; + const Belt: 2; + const Equipped: 3; + const Cursor: 4; + } + + // Same apply's for merc with less things available + export namespace body { + const None: 0; + const Head: 1; + const Neck: 2; + const Torso: 3; + const Armor: 3; + const RightArm: 4; + const LeftArm: 5; + const RingRight: 6; + const RingLeft: 7; + const Belt: 8; + const Feet: 9; + const Gloves: 10; + const RightArmSecondary: 11; + const LeftArmSecondary: 12 + } + + export namespace items { + export namespace cost { + const ToBuy: 0; + const ToSell: 1; + const ToRepair: 2; + } + export namespace flags { + const Equipped: 0x00000001; + const InSocket: 0x00000008; + const Identified: 0x00000010; + const OnActiveWeaponSlot: 0x00000040; + const OnSwapWeaponSlot: 0x00000080; + const Broken: 0x00000100; + const FullRejuv: 0x00000400; + const Socketed: 0x00000800; + const InTradeGamble: 0x00002000; + const NotInSocket: 0x00004000; + const Ear: 0x00010000; + const StartingItem: 0x00020000; + const RuneQuestPotion: 0x00200000; + const Ethereal: 0x00400000; + const IsAnItem: 0x00800000; + const Personalized: 0x01000000; + const Runeword: 0x04000000; + } + export namespace mode { + const inStorage: 0; //Item inven stash cube store = Item inven stash cube store + const Equipped: 1; // Item equipped self or merc + const inBelt: 2; // Item in belt + const onGround: 3; // Item on ground + const onCursor: 4; // Item on cursor + const Dropping: 5; // Item being dropped + const Socketed: 6 // Item socketed in item + } + export namespace quality { + const LowQuality: 1; + const Normal: 2; + const Superior: 3; + const Magic: 4; + const Set: 5; + const Rare: 6; + const Unique: 7; + const Crafted: 8; + } + export namespace _class1 { + const Normal: 0; + const Exceptional: 1; + const Elite: 2; + } + export { _class1 as class }; + export namespace type { + const Shield: 2; + const Armor: 3; + const Gold: 4; + const BowQuiver: 5; + const CrossbowQuiver: 6; + const PlayerBodyPart: 7; + const Herb: 8; + const Potion: 9; + const Ring: 10; + const Elixir: 11; + const Amulet: 12; + const Charm: 13; + const notused0: 14; + const Boots: 15; + const Gloves: 16; + const notused1: 17; + const Book: 18; + const Belt: 19; + const Gem: 20; + const Torch: 21; + const Scroll: 22; + const notused2: 23; + const Scepter: 24; + const Wand: 25; + const Staff: 26; + const Bow: 27; + const Axe: 28; + const Club: 29; + const Sword: 30; + const Hammer: 31; + const Knife: 32; + const Spear: 33; + const Polearm: 34; + const Crossbow: 35; + const Mace: 36; + const Helm: 37; + const MissilePotion: 38; + const Quest: 39; + const Bodypart: 40; + const Key: 41; + const ThrowingKnife: 42; + const ThrowingAxe: 43; + const Javelin: 44; + const Weapon: 45; + const MeleeWeapon: 46; + const MissileWeapon: 47; + const ThrownWeapon: 48; + const ComboWeapon: 49; + const AnyArmor: 50; + const AnyShield: 51; + const Miscellaneous: 52; + const SocketFiller: 53; + const Secondhand: 54; + const StavesandRods: 55; + const Missile: 56; + const Blunt: 57; + const Jewel: 58; + const ClassSpecific: 59; + const AmazonItem: 60; + const BarbarianItem: 61; + const NecromancerItem: 62; + const PaladinItem: 63; + const SorceressItem: 64; + const AssassinItem: 65; + const DruidItem: 66; + const HandtoHand: 67; + const Orb: 68; + const VoodooHeads: 69; + const AuricShields: 70; + const PrimalHelm: 71; + const Pelt: 72; + const Cloak: 73; + const Rune: 74; + const Circlet: 75; + const HealingPotion: 76; + const ManaPotion: 77; + const RejuvPotion: 78; + const StaminaPotion: 79; + const AntidotePotion: 80; + const ThawingPotion: 81; + const SmallCharm: 82; + const LargeCharm: 83; + const GrandCharm: 84; + const AmazonBow: 85; + const AmazonSpear: 86; + const AmazonJavelin: 87; + const AssassinClaw: 88; + const MagicBowQuiv: 89; + const MagicxBowQuiv: 90; + const ChippedGem: 91; + const FlawedGem: 92; + const StandardGem: 93; + const FlawlessGem: 94; + const PerfectgGem: 95; + const Amethyst: 96; + const Diamond: 97; + const Emerald: 98; + const Ruby: 99; + const Sapphire: 100; + const Topaz: 101; + const Skull: 102; + } + + // Weapons + export const HandAxe: 0; + export const Axe: 1; + export const DoubleAxe: 2; + export const MilitaryPick: 3; + export const WarAxe: 4; + export const LargeAxe: 5; + export const BroadAxe: 6; + export const BattleAxe: 7; + export const GreatAxe: 8; + export const GiantAxe: 9; + export const Wand: 10; + export const YewWand: 11; + export const BoneWand: 12; + export const GrimWand: 13; + export const Club: 14; + export const Scepter: 15; + export const GrandScepter: 16; + export const WarScepter: 17; + export const SpikedClub: 18; + export const Mace: 19; + export const MorningStar: 20; + export const Flail: 21; + export const WarHammer: 22; + export const Maul: 23; + export const GreatMaul: 24; + export const ShortSword: 25; + export const Scimitar: 26; + export const Sabre: 27; + export const Falchion: 28; + export const CrystalSword: 29; + export const BroadSword: 30; + export const LongSword: 31; + export const WarSword: 32; + export const Two_HandedSword: 33; + export const Claymore: 34; + export const GiantSword: 35; + export const BastardSword: 36; + export const Flamberge: 37; + export const GreatSword: 38; + export const Dagger: 39; + export const Dirk: 40; + export const Kris: 41; + export const Blade: 42; + export const ThrowingKnife: 43; + export const ThrowingAxe: 44; + export const BalancedKnife: 45; + export const BalancedAxe: 46; + export const Javelin: 47; + export const Pilum: 48; + export const ShortSpear: 49; + export const Glaive: 50; + export const ThrowingSpear: 51; + export const Spear: 52; + export const Trident: 53; + export const Brandistock: 54; + export const Spetum: 55; + export const Pike: 56; + export const Bardiche: 57; + export const Voulge: 58; + export const Scythe: 59; + export const Poleaxe: 60; + export const Halberd: 61; + export const WarScythe: 62; + export const ShortStaff: 63; + export const LongStaff: 64; + export const GnarledStaff: 65; + export const BattleStaff: 66; + export const WarStaff: 67; + export const ShortBow: 68; + export const HuntersBow: 69; + export const LongBow: 70; + export const CompositeBow: 71; + export const ShortBattleBow: 72; + export const LongBattleBow: 73; + export const ShortWarBow: 74; + export const LongWarBow: 75; + export const LightCrossbow: 76; + export const Crossbow: 77; + export const HeavyCrossbow: 78; + export const RepeatingCrossbow: 79; + export const Hatchet: 93; + export const Cleaver: 94; + export const TwinAxe: 95; + export const Crowbill: 96; + export const Naga: 97; + export const MilitaryAxe: 98; + export const BeardedAxe: 99; + export const Tabar: 100; + export const GothicAxe: 101; + export const AncientAxe: 102; + export const BurntWand: 103; + export const PetrifiedWand: 104; + export const TombWand: 105; + export const GraveWand: 106; + export const Cudgel: 107; + export const RuneScepter: 108; + export const HolyWaterSprinkler: 109; + export const DivineScepter: 110; + export const BarbedClub: 111; + export const FlangedMace: 112; + export const JaggedStar: 113; + export const Knout: 114; + export const BattleHammer: 115; + export const WarClub: 116; + export const MarteldeFer: 117; + export const Gladius: 118; + export const Cutlass: 119; + export const Shamshir: 120; + export const Tulwar: 121; + export const DimensionalBlade: 122; + export const BattleSword: 123; + export const RuneSword: 124; + export const AncientSword: 125; + export const Espandon: 126; + export const DacianFalx: 127; + export const TuskSword: 128; + export const GothicSword: 129; + export const Zweihander: 130; + export const ExecutionerSword: 131; + export const Poignard: 132; + export const Rondel: 133; + export const Cinquedeas: 134; + export const Stiletto: 135; + export const BattleDart: 136; + export const Francisca: 137; + export const WarDart: 138; + export const Hurlbat: 139; + export const WarJavelin: 140; + export const GreatPilum: 141; + export const Simbilan: 142; + export const Spiculum: 143; + export const Harpoon: 144; + export const WarSpear: 145; + export const Fuscina: 146; + export const WarFork: 147; + export const Yari: 148; + export const Lance: 149; + export const LochaberAxe: 150; + export const Bill: 151; + export const BattleScythe: 152; + export const Partizan: 153; + export const Bec_de_Corbin: 154; + export const GrimScythe: 155; + export const JoStaff: 156; + export const Quarterstaff: 157; + export const CedarStaff: 158; + export const GothicStaff: 159; + export const RuneStaff: 160; + export const EdgeBow: 161; + export const RazorBow: 162; + export const CedarBow: 163; + export const DoubleBow: 164; + export const ShortSiegeBow: 165; + export const LargeSiegeBow: 166; + export const RuneBow: 167; + export const GothicBow: 168; + export const Arbalest: 169; + export const SiegeCrossbow: 170; + export const Ballista: 171; + export const Chu_Ko_Nu: 172; + export const Katar: 175; + export const WristBlade: 176; + export const HatchetHands: 177; + export const Cestus: 178; + export const Claws: 179; + export const BladeTalons: 180; + export const ScissorsKatar: 181; + export const Quhab: 182; + export const WristSpike: 183; + export const Fascia: 184; + export const HandScythe: 185; + export const GreaterClaws: 186; + export const GreaterTalons: 187; + export const ScissorsQuhab: 188; + export const Suwayyah: 189; + export const WristSword: 190; + export const WarFist: 191; + export const BattleCestus: 192; + export const FeralClaws: 193; + export const RunicTalons: 194; + export const ScissorsSuwayyah: 195; + export const Tomahawk: 196; + export const SmallCrescent: 197; + export const EttinAxe: 198; + export const WarSpike: 199; + export const BerserkerAxe: 200; + export const FeralAxe: 201; + export const Silver_edgedAxe: 202; + export const Decapitator: 203; + export const ChampionAxe: 204; + export const GloriousAxe: 205; + export const PolishedWand: 206; + export const GhostWand: 207; + export const LichWand: 208; + export const UnearthedWand: 209; + export const Truncheon: 210; + export const MightyScepter: 211; + export const SeraphRod: 212; + export const Caduceus: 213; + export const TyrantClub: 214; + export const ReinforcedMace: 215; + export const DevilStar: 216; + export const Scourge: 217; + export const LegendaryMallet: 218; + export const OgreMaul: 219; + export const ThunderMaul: 220; + export const Falcata: 221; + export const Ataghan: 222; + export const ElegantBlade: 223; + export const HydraEdge: 224; + export const PhaseBlade: 225; + export const ConquestSword: 226; + export const CrypticSword: 227; + export const MythicalSword: 228; + export const LegendSword: 229; + export const HighlandBlade: 230; + export const BalrogBlade: 231; + export const ChampionSword: 232; + export const ColossusSword: 233; + export const ColossusBlade: 234; + export const BoneKnife: 235; + export const MithrilPoint: 236; + export const FangedKnife: 237; + export const LegendSpike: 238; + export const FlyingKnife: 239; + export const FlyingAxe: 240; + export const WingedKnife: 241; + export const WingedAxe: 242; + export const HyperionJavelin: 243; + export const StygianPilum: 244; + export const BalrogSpear: 245; + export const GhostGlaive: 246; + export const WingedHarpoon: 247; + export const HyperionSpear: 248; + export const StygianPike: 249; + export const Mancatcher: 250; + export const GhostSpear: 251; + export const WarPike: 252; + export const OgreAxe: 253; + export const ColossusVoulge: 254; + export const Thresher: 255; + export const CrypticAxe: 256; + export const GreatPoleaxe: 257; + export const GiantThresher: 258; + export const WalkingStick: 259; + export const Stalagmite: 260; + export const ElderStaff: 261; + export const Shillelagh: 262; + export const ArchonStaff: 263; + export const SpiderBow: 264; + export const BladeBow: 265; + export const ShadowBow: 266; + export const GreatBow: 267; + export const DiamondBow: 268; + export const CrusaderBow: 269; + export const WardBow: 270; + export const HydraBow: 271; + export const PelletBow: 272; + export const GorgonCrossbow: 273; + export const ColossusCrossbow: 274; + export const DemonCrossbow: 275; + export const EagleOrb: 276; + export const SacredGlobe: 277; + export const SmokedSphere: 278; + export const ClaspedOrb: 279; + export const JaredsStone: 280; + export const StagBow: 281; + export const ReflexBow: 282; + export const MaidenSpear: 283; + export const MaidenPike: 284; + export const MaidenJavelin: 285; + export const GlowingOrb: 286; + export const CrystallineGlobe: 287; + export const CloudySphere: 288; + export const SparklingBall: 289; + export const SwirlingCrystal: 290; + export const AshwoodBow: 291; + export const CeremonialBow: 292; + export const CeremonialSpear: 293; + export const CeremonialPike: 294; + export const CeremonialJavelin: 295; + export const HeavenlyStone: 296; + export const EldritchOrb: 297; + export const DemonHeart: 298; + export const VortexOrb: 299; + export const DimensionalShard: 300; + export const MatriarchalBow: 301; + export const GrandMatronBow: 302; + export const MatriarchalSpear: 303; + export const MatriarchalPike: 304; + export const MatriarchalJavelin: 305; + export const Cap: 306; + export const SkullCap: 307; + export const Helm: 308; + export const FullHelm: 309; + export const GreatHelm: 310; + export const Crown: 311; + export const Mask: 312; + export const QuiltedArmor: 313; + export const LeatherArmor: 314; + export const HardLeatherArmor: 315; + export const StuddedLeather: 316; + export const RingMail: 317; + export const ScaleMail: 318; + export const ChainMail: 319; + export const BreastPlate: 320; + export const SplintMail: 321; + export const PlateMail: 322; + export const FieldPlate: 323; + export const GothicPlate: 324; + export const FullPlateMail: 325; + export const AncientArmor: 326; + export const LightPlate: 327; + export const Buckler: 328; + export const SmallShield: 329; + export const LargeShield: 330; + export const KiteShield: 331; + export const TowerShield: 332; + export const GothicShield: 333; + export const LeatherGloves: 334; + export const HeavyGloves: 335; + export const ChainGloves: 336; + export const LightGauntlets: 337; + export const Gauntlets: 338; + export const Boots: 339; + export const HeavyBoots: 340; + export const ChainBoots: 341; + export const LightPlatedBoots: 342; + export const Greaves: 343; + export const Sash: 344; + export const LightBelt: 345; + export const Belt: 346; + export const HeavyBelt: 347; + export const PlatedBelt: 348; + export const BoneHelm: 349; + export const BoneShield: 350; + export const SpikedShield: 351; + export const WarHat: 352; + export const Sallet: 353; + export const Casque: 354; + export const Basinet: 355; + export const WingedHelm: 356; + export const GrandCrown: 357; + export const DeathMask: 358; + export const GhostArmor: 359; + export const SerpentskinArmor: 360; + export const DemonhideArmor: 361; + export const TrellisedArmor: 362; + export const LinkedMail: 363; + export const TigulatedMail: 364; + export const MeshArmor: 365; + export const Cuirass: 366; + export const RussetArmor: 367; + export const TemplarCoat: 368; + export const SharktoothArmor: 369; + export const EmbossedPlate: 370; + export const ChaosArmor: 371; + export const OrnatePlate: 372; + export const MagePlate: 373; + export const Defender: 374; + export const RoundShield: 375; + export const Scutum: 376; + export const DragonShield: 377; + export const Pavise: 378; + export const AncientShield: 379; + export const DemonhideGloves: 380; + export const SharkskinGloves: 381; + export const HeavyBracers: 382; + export const BattleGauntlets: 383; + export const WarGauntlets: 384; + export const DemonhideBoots: 385; + export const SharkskinBoots: 386; + export const MeshBoots: 387; + export const BattleBoots: 388; + export const WarBoots: 389; + export const DemonhideSash: 390; + export const SharkskinBelt: 391; + export const MeshBelt: 392; + export const BattleBelt: 393; + export const WarBelt: 394; + export const GrimHelm: 395; + export const GrimShield: 396; + export const BarbedShield: 397; + export const WolfHead: 398; + export const HawkHelm: 399; + export const Antlers: 400; + export const FalconMask: 401; + export const SpiritMask: 402; + export const JawboneCap: 403; + export const FangedHelm: 404; + export const HornedHelm: 405; + export const AssaultHelmet: 406; + export const AvengerGuard: 407; + export const Targe: 408; + export const Rondache: 409; + export const HeraldicShield: 410; + export const AerinShield: 411; + export const CrownShield: 412; + export const PreservedHead: 413; + export const ZombieHead: 414; + export const UnravellerHead: 415; + export const GargoyleHead: 416; + export const DemonHead: 417; + export const Circlet: 418; + export const Coronet: 419; + export const Tiara: 420; + export const Diadem: 421; + export const Shako: 422; + export const Hydraskull: 423; + export const Armet: 424; + export const GiantConch: 425; + export const SpiredHelm: 426; + export const Corona: 427; + export const Demonhead: 428; + export const DuskShroud: 429; + export const Wyrmhide: 430; + export const ScarabHusk: 431; + export const WireFleece: 432; + export const DiamondMail: 433; + export const LoricatedMail: 434; + export const Boneweave: 435; + export const GreatHauberk: 436; + export const BalrogSkin: 437; + export const HellforgePlate: 438; + export const KrakenShell: 439; + export const LacqueredPlate: 440; + export const ShadowPlate: 441; + export const SacredArmor: 442; + export const ArchonPlate: 443; + export const Heater: 444; + export const Luna: 445; + export const Hyperion: 446; + export const Monarch: 447; + export const Aegis: 448; + export const Ward: 449; + export const BrambleMitts: 450; + export const VampireboneGloves: 451; + export const Vambraces: 452; + export const CrusaderGauntlets: 453; + export const OgreGauntlets: 454; + export const WyrmhideBoots: 455; + export const ScarabshellBoots: 456; + export const BoneweaveBoots: 457; + export const MirroredBoots: 458; + export const MyrmidonGreaves: 459; + export const SpiderwebSash: 460; + export const VampirefangBelt: 461; + export const MithrilCoil: 462; + export const TrollBelt: 463; + export const ColossusGirdle: 464; + export const BoneVisage: 465; + export const TrollNest: 466; + export const BladeBarrier: 467; + export const AlphaHelm: 468; + export const GriffonHeaddress: 469; + export const HuntersGuise: 470; + export const SacredFeathers: 471; + export const TotemicMask: 472; + export const JawboneVisor: 473; + export const LionHelm: 474; + export const RageMask: 475; + export const SavageHelmet: 476; + export const SlayerGuard: 477; + export const AkaranTarge: 478; + export const AkaranRondache: 479; + export const ProtectorShield: 480; + export const GildedShield: 481; + export const RoyalShield: 482; + export const MummifiedTrophy: 483; + export const FetishTrophy: 484; + export const SextonTrophy: 485; + export const CantorTrophy: 486; + export const HierophantTrophy: 487; + export const BloodSpirit: 488; + export const SunSpirit: 489; + export const EarthSpirit: 490; + export const SkySpirit: 491; + export const DreamSpirit: 492; + export const CarnageHelm: 493; + export const FuryVisor: 494; + export const DestroyerHelm: 495; + export const ConquerorCrown: 496; + export const GuardianCrown: 497; + export const SacredTarge: 498; + export const SacredRondache: 499; + export const KurastShield: 500; + export const ZakarumShield: 501; + export const VortexShield: 502; + export const MinionSkull: 503; + export const HellspawnSkull: 504; + export const OverseerSkull: 505; + export const SuccubusSkull: 506; + export const BloodlordSkull: 507; + export const Amulet: 520; + export const Ring: 522; + export const Arrows: 526; + export const Bolts: 528; + export const Jewel: 643; + + // Misc? + const Elixir: 508; + const Torch: 527; + const Heart: 531; + const Brain: 532; + const Jawbone: 533; + const Eye: 534; + const Horn: 535; + const Tail: 536; + const Flag: 537; + const Fang: 538; + const Quill: 539; + const Soul: 540; + const Scalp: 541; + const Spleen: 542; + const Ear: 556; + const Herb: 602; + const anevilforce: 609; + // Potions, tomes/scrolls, gold + export const Key: 543; + export const TomeofTownPortal: 518; + export const TomeofIdentify: 519; + export const ScrollofTownPortal: 529; + export const ScrollofIdentify: 530; + export const RancidGasPotion: 80; + export const OilPotion: 81; + export const ChokingGasPotion: 82; + export const ExplodingPotion: 83; + export const StranglingGasPotion: 84; + export const FulminatingPotion: 85; + export const StaminaPotion: 513; + export const AntidotePotion: 514; + export const RejuvenationPotion: 515; + export const FullRejuvenationPotion: 516; + export const ThawingPotion: 517; + export const MinorHealingPotion: 587; + export const LightHealingPotion: 588; + export const HealingPotion: 589; + export const GreaterHealingPotion: 590; + export const SuperHealingPotion: 591; + export const MinorManaPotion: 592; + export const LightManaPotion: 593; + export const ManaPotion: 594; + export const GreaterManaPotion: 595; + export const SuperManaPotion: 596; + export const Gold: 523; + // Charms + export const SmallCharm: 603; + export const LargeCharm: 604; + export const GrandCharm: 605; + + export namespace quest { + // Act 1 + const WirtsLeg: 88; + const HoradricMalus: 89; + const ScrollofInifuss: 524; + const KeytotheCairnStones: 525; + // Act 2 + const FinishedStaff: 91; + const HoradricStaff: 91; + const IncompleteStaff: 92; + const ShaftoftheHoradricStaff: 92; + const ViperAmulet: 521; + const TopoftheHoradricStaff: 521; + const Cube: 549; + const BookofSkill: 552; + // Act 3 + const DecoyGidbinn: 86; + const TheGidbinn: 87; + const KhalimsFlail: 173; + const KhalimsWill: 174; + const PotofLife: 545; + const AJadeFigurine: 546; + const JadeFigurine: 546; + const TheGoldenBird: 547; + const LamEsensTome: 548; + const KhalimsEye: 553; + const KhalimsHeart: 554; + const KhalimsBrain: 555; + // Act 4 + const HellForgeHammer: 90; + const Soulstone: 551; + const MephistosSoulstone: 551; + // Act 5 + const MalahsPotion: 644; + const ScrollofKnowledge: 645; + const ScrollofResistance: 646; + // Pandemonium Event + const KeyofTerror: 647; + const KeyofHate: 648; + const KeyofDestruction: 649; + const DiablosHorn: 650; + const BaalsEye: 651; + const MephistosBrain: 652; + const StandardofHeroes: 658; + // Essences/Token + const TokenofAbsolution: 653; + const TwistedEssenceofSuffering: 654; + const ChargedEssenceofHatred: 655; + const BurningEssenceofTerror: 656; + const FesteringEssenceofDestruction: 657; + // Misc + const TheBlackTowerKey: 544; + } + export namespace runes { + const El: 610; + const Eld: 611; + const Tir: 612; + const Nef: 613; + const Eth: 614; + const Ith: 615; + const Tal: 616; + const Ral: 617; + const Ort: 618; + const Thul: 619; + const Amn: 620; + const Sol: 621; + const Shael: 622; + const Dol: 623; + const Hel: 624; + const Io: 625; + const Lum: 626; + const Ko: 627; + const Fal: 628; + const Lem: 629; + const Pul: 630; + const Um: 631; + const Mal: 632; + const Ist: 633; + const Gul: 634; + const Vex: 635; + const Ohm: 636; + const Lo: 637; + const Sur: 638; + const Ber: 639; + const Jah: 640; + const Cham: 641; + const Zod: 642; + } + export namespace gems { + export namespace Perfect { + const Amethyst: 561; + const Topaz: 566; + const Sapphire: 571; + const Emerald: 576; + const Ruby: 581; + const Diamond: 586; + const Skull: 601; + } + export namespace Flawless { + const Amethyst: 560; + const Topaz: 565; + const Sapphire: 570; + const Emerald: 575; + const Ruby: 580; + const Diamond: 585; + const Skull: 600; + } + export namespace Normal { + const Amethyst: 559; + const Topaz: 564; + const Sapphire: 569; + const Emerald: 574; + const Ruby: 579; + const Diamond: 584; + const Skull: 599; + } + export namespace Flawed { + const Amethyst: 558; + const Topaz: 563; + const Sapphire: 568; + const Emerald: 573; + const Ruby: 578; + const Diamond: 583; + const Skull: 598; + } + export namespace Chipped { + const Amethyst: 557; + const Topaz: 562; + const Sapphire: 567; + const Emerald: 572; + const Ruby: 577; + const Diamond: 582; + const Skull: 597; + } + } + } + + // locale strings + export namespace locale { + export namespace monsters { + // bosses + const Andariel: 3021; + const Duriel: 3054; + const Mephisto: 3062; + const Diablo: 3060; + const Baal: 3061; + // Mini bosses + const BloodRaven: 3111; + const TreeheadWoodFist: 2873; + const TheCountess: 2875; + const TheSmith: 2889; + const Radament: 2879; + const TheSummoner: 3099; + const HephastoTheArmorer: 1067; + const Izual: 1014; + const ShenktheOverseer: 22435; + // Uniques + const Corpsefire: 3319; + const TheCowKing: 2850; + const GrandVizierofChaos: 2851; + const LordDeSeis: 2852; + const InfectorofSouls: 2853; + const RiftwraiththeCannibal: 2854; + const Taintbreeder: 2855; + const TheTormentor: 2856; + const Darkwing: 2857; + const MafferDragonhand: 2858; + const WyandVoidbringer: 2859; + const ToorcIcefist: 2860; + const BremmSparkfist: 2861; + const GelebFlamefinger: 2862; + const IsmailVilehand: 2863; + const IcehawkRiftwing: 2864; + const BattlemaidSarina: 2865; + const Stormtree: 2866; + const WitchDoctorEndugu: 2867; + const SszarkTheBurning: 2868; + const Bishibosh: 2869; + const Bonebreaker: 2870; + const Coldcrow: 2871; + const Rakanishu: 2872; + const Griswold: 2874; + const PitspawnFouldog: 2876; + const FlamespiketheCrawler: 2877; + const BoneAsh: 2878; + const BloodwitchtheWild: 2880; + const Fangskin: 2881; + const Beetleburst: 2882; + const CreepingFeature: 2883; + const ColdwormtheBurrower: 2884; + const FireEye: 2885; + const DarkElder: 2886; + const AncientKaatheSoulless: 2888; + const SharpToothSayer: 22493; + const SnapchipShatter: 22496; + const Pindleskin: 22497; + const ThreshSocket: 22498; + const EyebacktheUnleashed: 22499; + const EldritchtheRectifier: 22500; + const DacFarren: 22501; + const BonesawBreaker: 22502; + const Frozenstein: 22504; + const Rogue: 2897; + const StygianDoll: 2898; + const SoulKiller: 2899; + const Flayer: 2900; + const Fetish: 2901; + const RatMan: 2902; + const UndeadStygianDoll: 2903; + const UndeadSoulKiller: 2904; + const UndeadFlayer: 2905; + const UndeadFetish: 2906; + const UndeadRatMan: 2907; + const DarkFamiliar: 2908; + const BloodDiver: 2909; + const Gloombat: 2910; + const DesertWing: 2911; + const TheBanished: 2912; + const BloodLord: 2913; + const DarkLord: 2914; + const NightLord: 2915; + const GhoulLord: 2916; + const Spikefist: 2917; + const Thrasher: 2918; + const BrambleHulk: 2919; + const ThornedHulk: 2920; + const SpiderMagus: 2921; + const FlameSpider: 2922; + const PoisonSpinner: 2923; + const SandFisher: 2924; + const Arach: 2925; + const BloodWing: 2926; + const BloodHook: 2927; + const Feeder: 2928; + const Sucker: 2929; + const WingedNightmare: 2930; + const HellBuzzard: 2931; + const UndeadScavenger: 2932; + const CarrionBird: 2933; + const Unraveler: 2934; + const Guardian: 2935; + const HollowOne: 2936; + const HoradrimAncient: 2937; + const BoneScarab: 2938; + const SteelScarab: 2939; + const Scarab: 2940; + const DeathBeetle: 2941; + const DungSoldier: 2942; + const HellSwarm: 2943; + const PlagueBugs: 2944; + const BlackLocusts: 2945; + const Itchies: 2946; + const HellCat: 2947; + const NightTiger: 2948; + const SaberCat: 2949; + const Huntress: 2950; + const CliffLurker: 2951; + const TreeLurker: 2952; + const CaveLeaper: 2953; + const TombCreeper: 2954; + const SandLeaper: 2955; + const TombViper: 2956; + const PitViper: 2957; + const Salamander: 2958; + const ClawViper: 2959; + const SerpentMagus: 2960; + const BloodMaggot: 2961; + const GiantLamprey: 2962; + const Devourer: 2963; + const RockWorm: 2964; + const SandMaggot: 2965; + const BushBarb: 2966; + const RazorSpine: 2967; + const ThornBeast: 2968; + const SpikeFiend: 2969; + const QuillRat: 2970; + const HellClan: 2971; + const MoonClan: 2972; + const NightClan: 2973; + const DeathClan: 2974; + const BloodClan: 2975; + const TempleGuard: 2976; + const DoomApe: 2977; + const JungleHunter: 2978; + const RockDweller: 2979; + const DuneBeast: 2980; + const FleshHunter: 2981; + const BlackRogue: 2982; + const DarkStalker: 2983; + const VileHunter: 2984; + const DarkHunter: 2985; + const DarkShape: 2986; + const Apparition: 2987; + const Specter: 2988; + const Wraith: 2989; + const Ghost: 2990; + const Assailant: 2991; + const Infidel: 2992; + const Invader: 2993; + const Marauder: 2994; + const SandRaider: 2995; + const GargantuanBeast: 2996; + const WailingBeast: 2997; + const Yeti: 2998; + const Crusher: 2999; + const Brute: 3000; + const CloudStalker: 3001; + const BlackVulture: 3002; + const BlackRaptor: 3003; + const BloodHawk: 3004; + const FoulCrow: 3005; + const PlagueBearer: 3006; + const Ghoul: 3007; + const DrownedCarcass: 3008; + const HungryDead: 3009; + const Zombie: 3010; + const Horror: 3012; + const Returned: 3013; + const BurningDead: 3014; + const BoneWarrior: 3015; + const Damned: 3016; + const Disfigured: 3017; + const Misshapen: 3018; + const Tainted: 3019; + const Afflicted: 3020; + const Camel: 3033; + const Cadaver: 3034; + const PreservedDead: 3035; + const Embalmed: 3036; + const DriedCorpse: 3037; + const Decayed: 3038; + const Urdar: 3039; + const Mauler: 3040; + const Gorebelly: 3041; + const Blunderbore: 3042; + const BloodMaggotYoung: 3043; + const GiantLampreyYoung: 3044; + const DevourerYoung: 3045; + const RockWormYoung: 3046; + const SandMaggotYoung: 3047; + const BloodMaggotEgg: 3048; + const GiantLampreyEgg: 3049; + const DevourerEgg: 3050; + const RockWormEgg: 3051; + const SandMaggotEgg: 3052; + const Maggot: 3053; + const BloodHawkNest: 3055; + const FlyingScimitar: 3056; + const CloudStalkerNest: 3057; + const BlackRaptorNest: 3058; + const FoulCrowNest: 3059; + const Cantor: 3063; + const Heirophant: 3064; + const Sexton: 3065; + const Zealot: 3066; + const Faithful: 3067; + const Zakarumite: 3068; + const BlackSoul: 3069; + const BurningSoul: 3070; + const SwampGhost: 3071; + const Gloam: 3072; + const WarpedShaman: 3073; + const DarkShaman: 3074; + const DevilkinShaman: 3075; + const CarverShaman: 3076; + const FallenShaman: 3077; + const WarpedOne: 3078; + const DarkOne: 3079; + const Devilkin: 3080; + const Carver: 3081; + const Fallen: 3082; + const ReturnedArcher: 3083; + const HorrorArcher: 3084; + const BurningDeadArcher: 3085; + const BoneArcher: 3086; + const CorpseArcher: 3087; + const SkeletonArcher: 3088; + const FleshLancer: 3089; + const BlackLancer: 3090; + const DarkLancer: 3091; + const VileLancer: 3092; + const DarkSpearwoman: 3093; + const FleshArcher: 3094; + const BlackArcher: 3095; + const DarkRanger: 3096; + const VileArcher: 3097; + const DarkArcher: 3098; + const StygianDollShaman: 3100; + const SoulKillerShaman: 3101; + const FlayerShaman: 3102; + const FetishShaman: 3103; + const RatManShaman: 3104; + const HorrorMage: 3105; + const BurningDeadMage: 3106; + const BoneMage: 3107; + const CorpseMage: 3108; + const ReturnedMage: 3109; + const GargoyleTrap: 3110; + const NightMarauder: 3121; + const FireGolem: 3122; + const IronGolem: 3123; + const BloodGolem: 3124; + const ClayGolem: 3125; + const BloodMaggotQueen: 3126; + const GiantLampreyQueen: 3127; + const DevourerQueen: 3128; + const RockWormQueen: 3129; + const SandMaggotQueen: 3130; + const SlimePrince: 3131; + const BogCreature: 3132; + const SwampDweller: 3133; + const BarbedGiant: 3134; + const RazorBeast: 3135; + const ThornBrute: 3136; + const SpikeGiant: 3137; + const QuillBear: 3138; + const CouncilMember: 3139; + const DarkWanderer: 3141; + const HellSlinger: 3142; + const NightSlinger: 3143; + const SpearCat: 3144; + const Slinger: 3145; + const FireTower: 3146; + const LightningSpire: 3147; + const PitLord: 3148; + const Balrog: 3149; + const VenomLord: 3150; + const IronWolf: 3151; + const InvisoSpawner: 3152; + const OblivionKnight: 3153; + const Mage: 3154; + const AbyssKnight: 3155; + const FighterMage: 3156; + const DoomKnight: 3157; + const Fighter: 3158; + const MawFiend: 3159; + const CorpseSpitter: 3160; + const Corpulent: 3161; + const StormCaster: 3162; + const Strangler: 3163; + const DoomCaster: 3164; + const GrotesqueWyrm: 3165; + const StygianDog: 3166; + const FleshBeast: 3167; + const Grotesque: 3168; + const StygianHag: 3169; + const FleshSpawner: 3170; + const RogueScout: 3171; + const BloodWingNest: 3172; + const BloodHookNest: 3173; + const FeederNest: 3174; + const SuckerNest: 3175; + const Hydra: 3325; + } + namespace npcs { + const Asheara: 1008; + const Hratli: 1009; + const Alkor: 1010; + const Ormus: 1011; + const Natalya: 1012; + const Tyrael: 1013; + const Izual1: 1014; + const Izual2: 1015; + const Jamella: 1016; + const Halbu: 1017; + const Hadriel: 1018; + const Hazade: 1019; + const Alhizeer: 1020; + const Azrael: 1021; + const Ahsab: 1022; + const Chalan: 1023; + const Haseen: 1024; + const Razan: 1025; + const Emilio: 1026; + const Pratham: 1027; + const Fazel: 1028; + const Jemali: 1029; + const Kasim: 1030; + const Gulzar: 1031; + const Mizan: 1032; + const Leharas: 1033; + const Durga: 1034; + const Neeraj: 1035; + const Ilzan: 1036; + const Zanarhi: 1037; + const Waheed: 1038; + const Vikhyat: 1039; + const Jelani: 1040; + const Barani: 1041; + const Jabari: 1042; + const Devak: 1043; + const Raldin: 1044; + const Telash: 1045; + const Ajheed: 1046; + const Narphet: 1047; + const Khaleel: 1048; + const Phaet: 1049; + const Geshef: 1050; + const Vanji: 1051; + const Haphet: 1052; + const Thadar: 1053; + const Yatiraj: 1054; + const Rhadge: 1055; + const Yashied: 1056; + const Lharhad: 1057; + const Flux: 1058; + const Scorch: 1059; + //const Natalya: 3022; both 1012 and 3022 return Natalya? + const DeckardCain: 2890; + const Gheed: 2891; + const Akara: 2892; + const Kashya: 2893; + const Charsi: 2894; + const Warriv: 2895; + const Drognan: 3023; + const Atma: 3024; + const Fara: 3025; + const Lysander: 3026; + const Jerhyn: 3028; + const Geglash: 3029; + const Elzix: 3030; + const Greiz: 3031; + const Flavie: 3112; + const Kaelan: 3113; + const Meshif: 3114; + const Larzuk: 22476; + const Anya: 22477; + const Malah: 22478; + const Nihlathak1: 22479; + const QualKehk: 22480; + const Guard: 22481; + const Combatant: 22482; + const Nihlathak2: 22483; + } + namespace items { + const KhalimsFlail: 1060; + const KhalimsWill1: 1061; + const KhalimsFlail2: 1062; + const KhalimsWill2: 1063; + const KhalimsEye: 1064; + const KhalimsBrain: 1065; + const KhalimsHeart: 1066; + const ScrollofInifuss: 2216; + const KeytotheCairnStones: 2217; + const AJadeFigurine: 2227; + const TheGoldenBird: 2228; + const LamEsensTome1: 2229; + const LamEsensTome2: 2230; + const HoradricCube: 2231; + const HoradricScroll: 2232; + const MephistosSoulstone: 2233; + const Ear: 2235; + const AmuletoftheViper: 2697; + const StaffofKings: 2698; + const HoradricStaff: 2699; + + // Sets + // Angelic Rainment + const AngelicsSword: 10172; + const AngelicsArmor: 10173; + const AngelicsRing: 10174; + const AngelicsAmulet: 10175; + // Arcannas Tricks + const ArcannasAmulet: 10180; + const ArcannasStaff: 10181; + const ArcannasHelmet: 10182; + const ArcannasArmor: 10183; + // Artic Gear + const ArticsBow: 10176; + const ArticsArmor: 10177; + const ArticsBelt: 10178; + const ArticsGloves: 10179; + // Berserkers Gear + const BerserkersHelmet: 10166; + const BerserkersAxe: 10167; + const BerserkersArmor: 10168; + // Cathans Traps + const CathansRing: 10147; + const CathansAmulet: 10148; + const CathansHelmet: 10149; + const CathansArmor: 10150; + const CathansStaff: 10151; + // Civerbs Gear + const CiverbsShield: 10122; + const CiverbsAmulet: 10123; + const CiverbsScepter: 10124; + // Clegaws Brace + const ClegawsSword: 10128; + const ClegawsShield: 10129; + const ClegawsGloves: 10130; + // Deaths Disguise + const DeathsGloves: 10169; + const DeathsBelt: 10170; + const DeathsSword: 10171; + // Hsarus Defense + const HsarusBoots: 10125; + const HsarusShield: 10126; + const HsarusBelt: 10127; + // Infernal Tools + const InfernalsHelmet: 10163; + const InfernalsWand: 10164; + const InfernalsBelt: 10165; + // Irathas Finery + const IrathasBelt: 10131; + const IrathasHelmet: 10132; + const IrathasGloves: 10133; + const IrathasAmulet: 10134; + // Isenharts Armory + const IsenhartsHelmet: 10135; + const IsenhartsArmor: 10136; + const IsenhartsShield: 10137; + const IsenhartsSword: 10138; + // Milabrega Regalia + const MilabregasArmor: 10143; + const MilabregasHelmet: 10144; + const MilabregasScepter: 10145; + const MilabregasShield: 10146; + // Sigons + const SigonsHelmet: 10157; + const SigonsArmor: 10158; + const SigonsGloves: 10159; + const SigonsBoots: 10160; + const SigonsBelt: 10161; + const SigonsShield: 10162; + // Tancreds + const TancredsPick: 10152; + const TancredsArmor: 10153; + const TancredsBoots: 10154; + const TancredsAmulet: 10155; + const TancredsHelmet: 10156; + // Vidalas + const VidalasAmulet: 10139; + const VidalasArmor: 10140; + const VidalasBoots: 10141; + const VidalasBow: 10142; + + // LoD Sets + // Aldurs's Legacy + const AldursHelmet: 21697; + const AldursArmor: 21698; + const AldursBoots: 21700; + const AldursMace: 21847; + // Bul-Kathos's Children + const BulKathosBlade: 21688; + const BulKathoSword: 21689; + // Cow Kings's Leathers + const CowKingsHelmet: 21723; + const CowKingsArmor: 21724; + const CowKingsBoots: 21725; + // Disciples + const DisciplesAmulet: 21717; + const DisciplesGloves: 21718; + const DisciplesBoots: 21719; + const DisciplesArmor: 21720; + const DisciplesBelt: 21721; + // Griswolds's Legacy + const GriswoldsScepter: 21673; + const GriswoldsShield: 21674; + const GriswoldsArmor: 21675; + const GriswoldsHelmet: 21676; + // Heaven's Brethren + const HeavensMace: 21823; + const HeavensHelmet: 21824; + const HeavensShield: 21825; + const HeavensArmor: 21826; + // Hwanin's + const HwaninsHelmet: 21712; + const HwaninsPolearm: 21713; + const HwaninsArmor: 21714; + const HwaninsBelt: 21821; + // IK + const ImmortalKingsMaul: 21840; + const ImmortalKingsBoots: 21841; + const ImmortalKingsGloves: 21842; + const ImmortalKingsBelt: 21843; + const ImmortalKingsArmor: 21844; + const ImmortalKingsHelmet: 21845; + // M'avina's + const MavinasHelmet: 21702; + const MavinasArmor: 21703; + const MavinasGloves: 21704; + const MavinasBelt: 21705; + const MavinasBow: 21706; + // Natalya's + const NatalyasHelmet: 21668; + const NatalyasClaw: 21669; + const NatalyasArmor: 21670; + const NatalyasBoots: 21671; + // Naj's + const NajsStaff: 21640; + const NajsArmor: 21831; + const NajsHelmet: 21832; + // Orphan's + const OrphansHelmet: 21731; + const OrphansBelt: 21732; + const OrphansGloves: 21733; + const OrphansShield: 21734; + // Sanders's + const SandersGloves: 21876; + const SandersBoots: 21877; + const SandersHelmet: 21878; + const SandersWand: 21879; + // Sazabi's + const SazabisSword: 21708; + const SazabisArmor: 21709; + const SazabisHelmet: 21710; + // Tal + const TalRashasBelt: 21816; + const TalRashasAmulet: 21817; + const TalRashasArmor: 21818; + const TalRashasOrb: 21819; + const TalRashasHelmet: 21820; + // Trang-Ouls + const TrangOulsHelmet: 21661; + const TrangOulsShield: 21662; + const TrangOulsArmor: 21664; + const TrangOulsGloves: 21665; + const TrangOulsBelt: 21666; + + // Uniques + // Quest/Misc + const KeyofTerror: 11146; + const KeyofHate: 11147; + const KeyofDestruction: 11148; + const DiablosHorn: 11149; + const BaalsEye: 11150; + const MephistosBrain: 11151; + const StandardofHeroes: 11152; + const HellfireTorch: 11153; + const Annihilus: 21743; + + // Unique Items + const WitchwildString: 10911; + const TitansRevenge: 21735; + const LycandersAim: 21737; + const ArreatsFace: 21744; + const Homunculus: 21755; + const JalalsMane: 21750; + const HeraldofZakarum: 21758; + const BloodRavensCharge: 21508; + const Gimmershred: 21637; + const MedusasGaze: 21516; + const Rockstopper: 21519; + const CrownofThieves: 21522; + const BlackhornsFace: 21523; + const TheSpiritShroud: 21524; + const SkinoftheFlayedOne: 21525; + const IronPelt: 21526; + const SpiritForge: 21527; + const CrowCaw: 21528; + const DurielsShell: 21529; + const SkulldersIre: 21530; + const Toothrow: 21531; + const AtmasWail: 21532; + const BlackHades: 21533; + const Corpsemourn: 21534; + const QueHegans: 21535; + const QueHegansWisdom: 21535; + const Mosers: 21536; + const MosersBlessedCircle: 21536; + const Stormchaser: 21537; + const TiamatsRubuke: 21538; + const GerkesSanctuary: 21539; + const RadamentsSphere: 21540; + const Gravepalm: 21541; + const Ghoulhide: 21542; + const Hellmouth: 21543; + const Infernostride: 21544; + const Waterwalk: 21545; + const Silkweave: 21546; + const WarTraveler: 21547; + const Razortail: 21548; + const GloomsTrap: 21549; + const Snowclash: 21550; + const ThundergodsVigor: 21551; + const LidlessWall: 21552; + const LanceGuard: 21553; + const Boneflame: 21555; + const SteelPillar: 21556; + const NightwingsVeil: 21557; + const CrownofAges: 21559; + const AndarielsVisage: 21560; + const Dragonscale: 21562; + const SteelCarapace: 21563; + const RainbowFacet: 21565; + const Ravenlore: 21566; + const Boneshade: 21567; + const Flamebellow: 21570; + const DeathsFathom: 21571; + const Wolfhowl: 21572; + const SpiritWard: 21573; + const KirasGuardian: 21574; + const OrmusRobe: 21575; + const GheedsFortune: 21576; + const HalberdsReign: 21579; + const DraculsGrasp: 21583; + const Frostwind: 21584; + const TemplarsMight: 21585; + const EschutasTemper: 21586; // also 21620? + const FirelizardsTalons: 21587; + const SandstormTrek: 21588; + const Marrowwalk: 21589; + const HeavensLight: 21590; + const ArachnidMesh: 21592; + const NosferatusCoil: 21593; + const Verdungos: 21595; + const VerdungosHeartyCord: 21595; + const CarrionWind: 21597; + const GiantSkull: 21598; + const AstreonsIronWard: 21599; + const SaracensChance: 21608; + const HighlordsWrath: 21609; + const Ravenfrost: 21610; + const Dwarfstar: 21611; + const AtmasScarab: 21612; + const Maras: 21613; + const MarasKaleidoscope: 21613; + const CrescentMoonAmulet: 21614; + const TheRisingSun: 21615; + const TheCatsEye: 21616; + const BulKathosWeddingBand: 21617; + const Metalgrid: 21619; + const Stormshield: 21621; + const BlackoakShield: 21622; + const ArkainesValor: 21624; + const TheGladiatorsBane: 21625; + const HarlequinsCrest: 21627; + const GuardianAngel: 21632; + const TheGrandfather: 21643; + const Doombringer: 21644; + const TyraelsMight: 21645; + const Lightsabre: 21646; + const TheCraniumBasher: 21647; + const DeathsWeb: 21650; + const TheAtlantean: 21654; + const CarinShard: 21658; + const Coldkill: 21286; + const ButchersCleaver: 21287; + const Islestrike: 21289; + const GuardianNaga: 21291; + const SpellSteel: 21293; + const SuicideBranch: 21297; + const ArmofKingLeoric: 21299; + const BlackhandKey: 21300; + const DarkClanCrusher: 21301; + const TheFetidSprinkler: 21304; + const HandofBlessedLight: 21305; + const Fleshrender: 21306; + const SureshrillFrost: 21307; + const Moonfall: 21308; + const BaezilsVortex: 21309; + const Earthshaker: 21310; + const TheGavelofPain: 21312; + const Bloodletter: 21313; + const ColdstealEye: 21314; + const Hexfire: 21315; + const BladeofAliBaba: 21316; + const Riftslash: 21317; + const Headstriker: 21318; + const PlagueBearer: 21319; + //const TheAtlantean: 21320; + const CrainteVomir: 21321; + const BingSzWang: 21322; + const TheVileHusk: 21323; + const Cloudcrack: 21324; + const TodesfaelleFlamme: 21325; + const Swordguard: 21326; + const Spineripper: 21327; + const HeartCarver: 21328; + const BlackbogsSharp: 21329; + const Stormspike: 21330; + const TheImpaler: 21331; + const HoneSudan: 21334; + const SpireofHonor: 21335; + const TheMeatScraper: 21336; + const BlackleachBlade: 21337; + const AthenasWrath: 21338; + const PierreTombaleCouant: 21339; + const GrimsBurningDead: 21341; + const Ribcracker: 21342; + const ChromaticIre: 21343; + const Warspear: 21344; + const SkullCollector: 21345; + const Skystrike: 21346; + //const WitchwildString: 21349; + const GoldstrikeArch: 21350; + const PusSpitter: 21352; + const VampireGaze: 21354; + const StringofEars: 21355; + const GoreRider: 21356; + const LavaGout: 21357; + const VenomGrip: 21358; + const Visceratuant: 21359; + //const GuardianAngel: 21360; + const Shaftstop: 21361; + const SkinofVipermagi: 21362; + const Blackhorn: 21363; + const ValkyrieWing: 21364; + const PeasantCrown: 21365; + const DemonMachine: 21366; + const Riphook: 21369; + const Razorswitch: 21370; + const OndalsWisdom: 21375; + const Deathbit: 21379; + const Warshrike: 21380; + const DemonLimb: 21387; + const SteelShade: 21388; + const TombReaver: 21389; + //const DeathsWeb: 21390; + const AngelsSong: 21393; + const TheRedeemer: 21394; + const Bonehew: 21398; + const Steelrend: 21399; + const AriocsNeedle: 21402; + const SoulDrainer: 21407; + const RuneMaster: 21408; + const DeathCleaver: 21409; + const ExecutionersJustice: 21410; + const Leviathan: 21412; + const WispProjector: 21417; + const Lacerator: 21419; + const MangSongsLesson: 21420; + const Viperfork: 21421; + const TheReapersToll: 21427; + const SpiritKeeper: 21428; + const Hellrack: 21429; + const AlmaNegra: 21430; + const DarkforceSpawn: 21431; + const Ghostflame: 21438; + const ShadowKiller: 21439; + const GriffonsEye: 21442; + const Thunderstroke: 21445; + const DemonsArch: 21447; + const DjinnSlayer: 21450; + const Windforce: 21635; + const GinthersRift: 21829; + + // Runewords + const AncientsPledge: 20507; + const Armageddon: 20508; + const Authority: 20509; + const Beast: 20510; + const Beauty: 20511; + const Black: 20512; + const Blood: 20513; + const Bone: 20514; + const Bramble: 20515; + const Brand: 20516; + const BreathoftheDying: 20517; + const BrokenPromise: 20518; + const CalltoArms: 20519; + const ChainsofHonor: 20520; + const Chance: 20521; + const Chaos: 20522; + const CrescentMoon: 20523; + const Darkness: 20524; + const Daylight: 20525; + const Death: 20526; + const Deception: 20527; + const Delerium: 20528; + const Desire: 20529; + const Despair: 20530; + const Destruction: 20531; + const Doom: 20532; + const Dragon: 20533; + const Dread: 20534; + const Dream: 20535; + const Duress: 20536; + const Edge: 20537; + const Elation: 20538; + const Enigma: 20539; + const Enlightenment: 20540; + const Envy: 20541; + const Eternity: 20542; + const Exile: 20543; + const Faith: 20544; + const Famine: 20545; + const Flame: 20546; + const Fortitude: 20547; + const Fortune: 20548; + const Friendship: 20549; + const Fury: 20550; + const Gloom: 20551; + const Grief: 20553; + const HandofJustice: 20554; + const Harmony: 20555; + const HeartoftheOak: 20557; + const HolyThunder: 20560; + const Honor: 20561; + const Revenge: 20562; + const Humility: 20563; + const Hunger: 20564; + const Ice: 20565; + const Infinity: 20566; + const Innocence: 20567; + const Insight: 20568; + const Jealousy: 20569; + const Judgement: 20570; + const KingsGrace: 20571; + const Kingslayer: 20572; + const KnightsVigil: 20573; + const Knowledge: 20574; + const LastWish: 20575; + const Law: 20576; + const Lawbringer: 20577; + const Leaf: 20578; + const Lightning: 20579; + const Lionheart: 20580; + const Lore: 20581; + const Love: 20582; + const Loyalty: 20583; + const Lust: 20584; + const Madness: 20585; + const Malice: 20586; + const Melody: 20587; + const Memory: 20588; + const Mist: 20589; + const Morning: 20590; + const Mystery: 20591; + const Myth: 20592; + const Nadir: 20593; + const NaturesKingdom: 20594; + const Night: 20595; + const Oath: 20596; + const Obedience: 20597; + const Oblivion: 20598; + const Obsession: 20599; + const Passion: 20600; + const Patience: 20601; + const Patter: 20602; + const Peace: 20603; + const VoiceofReason: 20604; + const Penitence: 20605; + const Peril: 20606; + const Pestilence: 20607; + const Phoenix: 20608; + const Piety: 20609; + const PillarofFaith: 20610; + const Plague: 20611; + const Praise: 20612; + const Prayer: 20613; + const Pride: 20614; + const Principle: 20615; + const ProwessinBattle: 20616; + const Prudence: 20617; + const Punishment: 20618; + const Purity: 20619; + const Question: 20620; + const Radiance: 20621; + const Rain: 20622; + const Reason: 20623; + const Red: 20624; + const Rhyme: 20625; + const Rift: 20626; + const Sanctuary: 20627; + const Serendipity: 20628; + const Shadow: 20629; + const ShadowofDoubt: 20630; + const Silence: 20631; + const SirensSong: 20632; + const Smoke: 20633; + const Sorrow: 20634; + const Spirit: 20635; + const Splendor: 20636; + const Starlight: 20637; + const Stealth: 20638; + const Steel: 20639; + const StillWater: 20640; + const Sting: 20641; + const Stone: 20642; + const Storm: 20643; + const Strength: 20644; + const Tempest: 20645; + const Temptation: 20646; + const Terror: 20647; + const Thirst: 20648; + const Thought: 20649; + const Thunder: 20650; + const Time: 20651; + const Tradition: 20652; + const Treachery: 20653; + const Trust: 20654; + const Truth: 20655; + const UnbendingWill: 20656; + const Valor: 20657; + const Vengeance: 20658; + const Venom: 20659; + const Victory: 20660; + const Voice: 20661; + const Void: 20662; + const War: 20663; + const Water: 20664; + const Wealth: 20665; + const Whisper: 20666; + const White: 20667; + const Wind: 20668; + const WingsofHope: 20669; + const Wisdom: 20670; + const Woe: 20671; + const Wonder: 20672; + const Wrath: 20673; + const Youth: 20674; + const Zephyr: 20675; + } + namespace dialog { + const youDoNotHaveEnoughGoldForThat: 3362 + } + + namespace text { + const RepairCost: 3330; + const SellValue: 3331; + const IdentifyCost: 3332; + const ItemCannotBeTradedHere: 3333; + const TradeRepair: 3334; + const Buy: 3335; + const Sell: 3336; + const Heal: 3337; + const Repair: 3338; + const NextPage: 3339; + const PreviousPage: 3340; + const Transmute: 3341; + const YourGold: 3342; + const WhichItemShouldBeImbued: 3343; + const Yes: 3344; + const No: 3345; + const Gold2: 3346; + const Sell2: 3347; + const Buy2: 3358; + const Hire: 3349; + const ToStrength: 3473; + const ToDexterity: 3474; + const Defense: 3481; + const Identify: 3350; + const Repair2: 3351; + const EnhancedDefense: 3520; + const RealmGoingDownInXMinutes: 3651; + const Strength: 4060; + const Dexterity: 4062; + const Vitality: 4066; + const Energy: 4069; + const DoNotMeetLevelReqForThisGame: 5162; + const CdKeyDisabled: 5199; + const CdKeyInUseBy: 5200; + const OnlyOneInstanceAtATime: 5201; + const CdKeyIntendedForAnotherProduct: 5202; + const InvalidPassword: 5207; + const AccountDoesNotExist: 5208; + const AccountIsCorrupted: 5209; + const AccountMustBeAtLeast: 5217; + const AccountCantBeMoreThan: 5218; + const PasswordMustBeAtLeast: 5219; + const PasswordCantBeMoreThan: 5220; + const LoginError: 5224; + const UsernameMustBeAtLeast: 5231; + const UsernameIncludedIllegalChars: 5232; + const UsernameIncludedDisallowedwords: 5233; + const AccountNameAlreadyExist: 5239; + const UnableToCreateAccount: 5249; + const CannotCreateGamesDeadHCChar: 5304; + const Disconnected: 5347; + const UnableToIndentifyVersion: 5245; + const BattlenetNotResponding: 5353; + const BattlenetNotResponding2: 5354; + const HcCannotPlayWithSc: 5361; + const ScCannotPlayWithHc: 5362; + const CannotPlayInHellClassic: 5363; + const CannotPlayInNightmareClassic: 5364; + const EnhancedDamage: 10038; + const ClassicCannotPlayWithXpac: 10101; + const XpacCannotPlayWithClassic: 10102; + const LoDKeyDisabled: 10913; + const LodKeyInUseBy: 10914; + const LoDKeyIntendedForAnotherProduct: 10915; + const NonLadderCannotPlayWithLadder: 10929; + const LadderCannotPlayWithNonLadder: 10930; + const YourPositionInLineIs: 11026; + const Gateway: 11049; + const Ghostly: 11084; + const Fanatic: 11085; + const Possessed: 11086; + const Berserker: 11087; + const ExpiresIn: 11133; + const CdKeyDisabledFromRealm: 11161; + const CannotPlayInHellXpac: 21793; + const CannotPlayInNightmareXpac: 21794; + } + + namespace areas { + // Act 1 + const RogueEncampment: 5055; + const BloodMoor: 5054; + const ColdPlains: 5053; + const StonyField: 5052; + const DarkWood: 5051; + const BlackMarsh: 5050; + const TamoeHighland: 5049; + const DenofEvil: 5048; + const CaveLvl1: 5047; + const UndergroundPassageLvl1: 5046; + const HoleLvl1: 5045; + const PitLvl1: 5044; + const CaveLvl2: 5043; + const UndergroundPassageLvl2: 5042; + const HoleLvl2: 5041; + const PitLvl2: 5040; + const BurialGrounds: 5039; + const Crypt: 5038; + const Mausoleum: 5037; + const ForgottenTower: 5036; + const TowerCellarLvl1: 5035; + const TowerCellarLvl2: 5034; + const TowerCellarLvl3: 5033; + const TowerCellarLvl4: 5032; + const TowerCellarLvl5: 5031; + const MonasteryGate: 5030; + const OuterCloister: 5029; + const Barracks: 5038; + const JailLvl1: 5027; + const JailLvl2: 5026; + const JailLvl3: 5025; + const InnerCloister: 5024; + const Cathedral: 5023; + const CatacombsLvl1: 5022; + const CatacombsLvl2: 5021; + const CatacombsLvl3: 5020; + const CatacombsLvl4: 5019; + const Tristram: 5018; + const MooMooFarm: 788; + + // Act 2 + const LutGholein: 852; + const RockyWaste: 851; + const DryHills: 850; + const FarOasis: 849; + const LostCity: 848; + const ValleyofSnakes: 847; + const CanyonofMagic: 846; + const A2SewersLvl1: 845; + const A2SewersLvl2: 844; + const A2SewersLvl3: 843; + const HaremLvl1: 842; + const HaremLvl2: 841; + const PalaceCellarLvl1: 840; + const PalaceCellarLvl2: 839; + const PalaceCellarLvl3: 838; + const StonyTombLvl1: 837; + const HallsoftheDeadLvl1: 836; + const HallsoftheDeadLvl2: 835; + const ClawViperTempleLvl1: 834; + const StonyTombLvl2: 833; + const HallsoftheDeadLvl3: 832; + const ClawViperTempleLvl2: 831; + const MaggotLairLvl1: 830; + const MaggotLairLvl2: 829; + const MaggotLairLvl3: 828; + const AncientTunnels: 827; + const TalRashasTomb1: 826; + const TalRashasTomb2: 826; + const TalRashasTomb3: 826; + const TalRashasTomb4: 826; + const TalRashasTomb5: 826; + const TalRashasTomb6: 826; + const TalRashasTomb7: 826; + const DurielsLair: 825; + const ArcaneSanctuary: 824; + + // Act 3 + const KurastDocktown: 820; + const SpiderForest: 819; + const GreatMarsh: 818; + const FlayerJungle: 817; + const LowerKurast: 816; + const KurastBazaar: 815; + const UpperKurast: 814; + const KurastCauseway: 813; + const Travincal: 812; + const SpiderCave: 810; + const SpiderCavern: 811; + const SwampyPitLvl1: 809; + const SwampyPitLvl2: 808; + const FlayerDungeonLvl1: 806; + const FlayerDungeonLvl2: 805; + const SwampyPitLvl3: 807; + const FlayerDungeonLvl3: 804; + const A3SewersLvl1: 845; + const A3SewersLvl2: 844; + const RuinedTemple: 803; + const DisusedFane: 802; + const ForgottenReliquary: 801; + const ForgottenTemple: 800; + const RuinedFane: 799; + const DisusedReliquary: 798; + const DuranceofHateLvl1: 797; + const DuranceofHateLvl2: 796; + const DuranceofHateLvl3: 795; + + // Act 4 + const PandemoniumFortress: 790; + const OuterSteppes: 792; + const PlainsofDespair: 793; + const CityoftheDamned: 794; + const RiverofFlame: 791; + const ChaosSanctuary: 789; + + // Act 5 + const Harrogath: 22646; + const BloodyFoothills: 22647; + const FrigidHighlands: 22648; + const ArreatPlateau: 22649; + const CrystalizedPassage: 22650; + const FrozenRiver: 22651; + const GlacialTrail: 22652; + const DrifterCavern: 22653; + const FrozenTundra: 22654; + const AncientsWay: 22655; + const IcyCellar: 22656; + const ArreatSummit: 22657; + const NihlathaksTemple: 22658; + const HallsofAnguish: 22659; + const HallsofPain: 22660; + const HallsofVaught: 22662; + const Abaddon: 21865; + const PitofAcheron: 21866; + const InfernalPit: 21867; + const WorldstoneLvl1: 22663; + const WorldstoneLvl2: 22664; + const WorldstoneLvl3: 22665; + const ThroneofDestruction: 22667; + const WorldstoneChamber: 22666; + + // Ubers + const MatronsDen: 5389; + const ForgottenSands: 5389; + const FurnaceofPain: 5389; + const UberTristram: 5018; + } + } + + export namespace game { + namespace profiletype { + const SinglePlayer: 1; + const Battlenet: 2; + const OpenBattlenet: 3; + const TcpIpHost: 4; + const TcpIpJoin: 5 + } + + namespace controls { + const Disabled: 4; + } + + namespace gametype { + const Classic: 0; + const Expansion: 1; + } + + // out of game locations + namespace locations { + const PreSplash: 0; + const Lobby: 1; + const WaitingInLine: 2; + const LobbyChat: 3; + const CreateGame: 4; + const JoinGame: 5; + const Ladder: 6; + const ChannelList: 7; + const MainMenu: 8; + const Login: 9; + const LoginError: 10; + const LoginUnableToConnect: 11; + const CharSelect: 12; + const RealmDown: 13; + const Disconnected: 14; + const NewCharSelected: 15; + const CharSelectPleaseWait: 16; + const LobbyLostConnection: 17; + const SplashScreen: 18; + const CdKeyInUse: 19; + const SelectDifficultySP: 20; + const MainMenuConnecting: 21; + const InvalidCdKey: 22; + const CharSelectConnecting: 23; + const ServerDown: 24; + const LobbyPleaseWait: 25; + const GameNameExists: 26; + const GatewaySelect: 27; + const GameDoesNotExist: 28; + const CharacterCreate: 29; + const OkCenteredErrorPopUp: 30; + const TermsOfUse: 31; + const CreateNewAccount: 32; + const PleaseRead: 33; + const RegisterEmail: 34; + const Credits: 35; + const Cinematics: 36; + const CharChangeRealm: 37; + const GameIsFull: 38; + const OtherMultiplayer: 39; + const TcpIp: 40; + const TcpIpEnterIp: 41; + const CharSelectNoChars: 42; + const CharSelectChangeRealm: 43; + const TcpIpUnableToConnect: 44; + } + } + + export namespace colors { + const White: "ÿc0"; + const Red: "ÿc1"; + const NeonGreen: "ÿc2"; + const Blue: "ÿc3"; + const DarkGold: "ÿc4"; + const Gray: "ÿc5"; + const Black: "ÿc6"; + const LightGold: "ÿc7"; + const Orange: "ÿc8"; + const Yellow: "ÿc9"; + const DarkGreen: "ÿconst c:"; + const Purple: "ÿc;"; + const Green: "ÿc<"; + namespace D2Bot { + const Black: 0; + const Blue: 4; + const Green: 5; + const Gold: 6; + const DarkGold: 7; + const Orange: 8; + const Red: 9; + const Gray: 10 + } + } + + export namespace keys { + const Backspace: 8; + const Tab: 9; + const Enter: 13; + const Shift: 16; + const Ctrl: 17; + const Alt: 18; + const PauseBreak: 19; + const CapsLock: 20; + const Escape: 27; + const Spacebar: 32; + const PageUp: 33; + const PageDown: 34; + const End: 35; + const Home: 36; + const LeftArrow: 37; + const UpArrow: 38; + const RightArrow: 39; + const DownArrow: 40; + const Insert: 45; + const Delete: 46; + const Zero: 48; + const One: 49; + const Two: 50; + const Three: 51; + const Four: 52; + const Five: 53; + const Six: 54; + const Seven: 55; + const Eight: 56; + const Nine: 57; + const LeftWindowKey: 91; + const RightWindowKey: 92; + const SelectKey: 93; + const Numpad0: 96; + const Numpad1: 97; + const Numpad2: 98; + const Numpad3: 99; + const Numpad4: 100; + const Numpad5: 101; + const Numpad6: 102; + const Numpad7: 103; + const Numpad8: 104; + const Numpad9: 105; + const NumpadStar: 106; + const NumpadPlus: 107; + const NumpadDash: 109; + const NumpadDecimal: 110; + const NumpadSlash: 111; + const F1: 112; + const F2: 113; + const F3: 114; + const F4: 115; + const F5: 116; + const F6: 117; + const F7: 118; + const F8: 119; + const F9: 120; + const F10: 121; + const F11: 122; + const F12: 123; + const NumLock: 144; + const ScrollLock: 145; + const SemiColon: 186; + const EqualSign: 187; + const Comma: 188; + const Dash: 189; + const Period: 190; + const ForwardSlash: 191; + const GraveAccent: 192; + const OpenBracket: 219; + const BackSlash: 220; + const CloseBracket: 221; + const SingleQuote: 222; + namespace code { + const Backspace: 0x08; + const Tab: 0x09; + const Clear: 0x0C; + const Enter: 0x0D; + const Shift: 0x10; + const Ctrl: 0x11; + const Alt: 0x12; + const PauseBreak: 0x13; + const CapsLock: 0x14; + const Esc: 0x1B; + const Space: 0x20; + const PageUp: 0x21; + const PageDown: 0x22; + const End: 0x23; + const Home: 0x24; + const LeftArrow: 0x25; + const UpArrow: 0x26; + const RightArrow: 0x27; + const DownArrow: 0x28; + const Select: 0x29; + const Print: 0x2A; + const PrintScreen: 0x2C; + const Insert: 0x2D; + const Delete: 0x2E; + } + } + + export namespace controls { + const TextBox: 1; + const Image1: 2; + const Image2: 3; + const LabelBox: 4; + const ScrollBar: 5; + const Button: 6; + const List: 7; + const Timer: 8; + const Smack: 9; + const ProgressBar: 10; + const Popup: 11; + const AccountList: 12 + } + + export namespace packets { + namespace send { + const WalkToLocation: 0x01; + const WalkToEntity: 0x02; + const RunToLocation: 0x03; + const RunToEntity: 0x04; + const LeftSkillOnLocation: 0x05; + const LeftSkillOnEntity: 0x06; + const LeftSkillOnEntityEx: 0x07; + const LeftSkillOnLocationEx: 0x08; + const LeftSkillOnEntityEx2: 0x09; + const LeftSkillOnEntityEx3: 0x0A; + const RightSkillOnLocation: 0x0C; + const RightSkillOnEntity: 0x0D; + const RightSkillOnEntityEx: 0x0E; + const RightSkillOnLocationEx: 0x0F; + const RightSkillOnEntityEx2: 0x10; + const RightSkillOnEntityEx3: 0x11; + const SetInfernoState: 0x12; + const InteractWithEntity: 0x13; + const OverheadMessage: 0x14; + const Chat: 0x15; + const PickupItem: 0x16; + const DropItem: 0x17; + const ItemToBuffer: 0x18; + const PickupBufferItem: 0x19; + const ItemToBody: 0x1A; + const Swap2HandedItem: 0x1B; + const PickupBodyItem: 0x1C; + const SwitchBodyItem: 0x1D; + const Switch1HandWith2Hand: 0x1E; + const SwitchInventoryItem: 0x1F; + const UseItem: 0x20; + const StackItem: 0x21; + const RemoveStackItem: 0x22; + const ItemToBelt: 0x23; + const RemoveBeltItem: 0x24; + const SwitchBeltItem: 0x25; + const UseBeltItem: 0x26; + const IndentifyItem: 0x27; + const InsertSocketItem: 0x28; + const ScrollToMe: 0x29; + const ItemToCube: 0x2A; + const NPCInit: 0x2F; + const NPCCancel: 0x30; + const QuestMessage: 0x31; + const NPCBuy: 0x32; + const NPCSell: 0x33; + const NPCIndentifyItems: 0x34; + const Repair: 0x35; + const HireMerc: 0x36; + const IndentifyGamble: 0x37; + const EntityAction: 0x38; + const AddStat: 0x3A; + const AddSkill: 0x3B; + const SelectSkill: 0x3C; + const ActivateItem: 0x3E; + const CharacterPhrase: 0x3F; + const UpdateQuests: 0x40; + const Resurrect: 0x41; + const StaffInOrifice: 0x44; + const MercInteract: 0x46; + const MercMove: 0x47; + const BusyStateOff: 0x48; + const Waypoint: 0x49; + const RequestEntityUpdate: 0x4B; + const Transmorgify: 0x4C; + const PlayNPCMessage: 0x4D; + const ClickButton: 0x4F; + const DropGold: 0x50; + const BindHotkey: 0x51; + const StaminaOn: 0x53; + const StaminaOff: 0x54; + const QuestCompleted: 0x58; + const MakeEntityMove: 0x59; + const SquelchHostile: 0x5D; + const Party: 0x5E; + const UpdatePlayerPos: 0x5F; + const SwapWeapon: 0x60; + const MercItem: 0x61; + const MercRessurect: 0x62; + const LeaveGame: 0x69; + } + namespace recv { + const GameExit: 0x06; + const MapReveal: 0x07; + const MapHide: 0x08; + const ReassignPlayer: 0x15; + const SetSkill: 0x23; + const Chat: 0x26; + const UniqueEvents: 0x89; + const WeaponSwitch: 0x97; + } + } + } +} +export {}; diff --git a/d2bs/kolbot/threads/AntiHostile.js b/d2bs/kolbot/threads/AntiHostile.js new file mode 100644 index 000000000..136c3e863 --- /dev/null +++ b/d2bs/kolbot/threads/AntiHostile.js @@ -0,0 +1,446 @@ +/** + * @filename AntiHostile.js + * @author kolton + * @desc handle hostile threats + * + */ +js_strict(true); +include("critical.js"); // required + +// globals needed for core gameplay +includeCoreLibs(); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +include("oog/ShitList.js"); + +function main() { + // Variables and functions + let player, attackCount, prevPos, check, missile, outside; + let hostiles = []; + + /** + * Handles game events for AntiHostile. + * @const + * @param {string} msg - The message received from the game event. + */ + const scriptEvent = function (msg) { + if (!msg || typeof msg !== "string") return; + + switch (msg.split(" ")[0]) { + case "remove": // Remove a hostile player that left the game + if (hostiles.indexOf(msg.split(" ")[1]) > -1) { + hostiles.splice(hostiles.indexOf(msg.split(" ")[1]), 1); + } + break; + case "mugshot": // Take a screenshot and log the kill + D2Bot.printToConsole(msg.split(" ")[1] + " has been neutralized.", sdk.colors.D2Bot.Blue); + hideConsole(); + delay(500); + takeScreenshot(); + break; + } + }; + + /** + * Find all hostile players and add their names to the 'hostiles' list + * @returns {boolean} + */ + const findHostiles = function () { + let party = getParty(); + + if (party) { + do { + if (hostiles.includes(party.name) && !getPlayerFlag(me.gid, party.gid, 8)) { + hostiles.splice(hostiles.indexOf(party.name), 1); + + continue; + } + if (party.name !== me.name && getPlayerFlag(me.gid, party.gid, 8) && hostiles.indexOf(party.name) === -1) { + D2Bot.printToConsole( + party.name + " (Level " + party.level + " " + sdk.player.class.nameOf(party.classid) + ")" + " has declared hostility.", + sdk.colors.D2Bot.Orange + ); + hostiles.push(party.name); + if (Config.ShitList) { + ShitList.add(party.name); + } + } + } while (party.getNext()); + } + + return true; + }; + + /** + * Pause default so actions don't conflict + */ + const pause = function () { + let script = getScript("default.dbj"); + + if (script && script.running) { + console.log("ÿc1Pausing."); + script.pause(); + } + }; + + /** + * Resume default + */ + const resume = function () { + let script = getScript("default.dbj"); + + if (script && !script.running) { + console.log("ÿc2Resuming."); + script.resume(); + scriptBroadcast("hostileEventEnded"); + } + }; + + /** + * Find hostile player Units + * @returns {Player | boolean} + */ + const findPlayer = function () { + for (let i = 0; i < hostiles.length; i += 1) { + let player = Game.getPlayer(hostiles[i]); + + if (player) { + do { + if (!player.dead && getPlayerFlag(me.gid, player.gid, 8) && !player.inTown && !me.inTown) { + return player; + } + } while (player.getNext()); + } + } + + return false; + }; + + /** + * Find a missile type + * @param {Player} owner + * @param {number} id + * @param {number} range + * @returns {Missile | boolean} + */ + const findMissile = function (owner, id, range) { + range === undefined && (range = 999); + + let missile = Game.getMissile(id); + if (!missile) return false; + + do { + if (missile.owner === owner.gid && getDistance(owner, missile) < range) { + return missile; + } + } while (missile.getNext()); + + return false; + }; + + /** + * @param {Player} player + * @returns {boolean} + */ + const checkSummons = function (player) { + if (!player) return false; + let name = player.name; + let unit = Game.getMonster(); + + if (unit) { + do { + // Revives and spirit wolves + if ( + unit.getParent() + && unit.getParent().name === name + && (unit.getState(sdk.states.Revive) || unit.classid === sdk.monsters.Wolf2) + ) { + return true; + } + } while (unit.getNext()); + } + + return false; + }; + + // Init config and attacks + D2Bot.init(); + Config.init(); + Attack.init(); + Storage.Init(); + + // Use PVP range for attacks + Skill.usePvpRange = true; + + // Attack sequence adjustments - this only affects the AntiHostile thread + if ( + Skill.canUse(sdk.skills.MindBlast) + && [sdk.skills.FireBlast, sdk.skills.ShockWeb].includes(Config.AttackSkill[1]) + ) { + Config.AttackSkill[1] = sdk.skills.MindBlast; + ClassAttack.trapRange = 40; + } + + /** + * A simple but fast player dodge function + * @param {Player | Monster} unit + * @param {number} range + * @returns {boolean} + */ + const moveAway = function (unit, range) { + let angle = Math.round((Math.atan2(me.y - unit.y, me.x - unit.x) * 180) / Math.PI); + let angles = [0, 45, -45, 90, -90, 135, -135, 180]; + + for (let i = 0; i < angles.length; i += 1) { + // Avoid the position where the player actually tries to move to + let coordx = Math.round(Math.cos(((angle + angles[i]) * Math.PI) / 180) * range + unit.x); // unit.targetx + let coordy = Math.round(Math.sin(((angle + angles[i]) * Math.PI) / 180) * range + unit.y); // unit.targety + + if (Attack.validSpot(coordx, coordy)) { + return Pather.moveTo(coordx, coordy); + } + } + + return false; + }; + + addEventListener("scriptmsg", scriptEvent); + console.log("ÿc2Anti-Hostile thread loaded."); + + // Main Loop + while (true) { + if (me.gameReady) { + // Scan for hostiles + findHostiles(); + + if (hostiles.length > 0 && (Config.HostileAction === 0 || (Config.HostileAction === 1 && me.inTown))) { + if (Config.TownOnHostile) { + console.log("ÿc1Hostility detected, going to town."); + pause(); + + if (!me.inTown) { + outside = true; + } + + try { + Town.goToTown(); + } catch (e) { + console.error(e + " Failed to go to town. Quitting."); + scriptBroadcast("quit"); // quit if failed to go to town + } + + while (hostiles.length > 0) { + findHostiles(); + delay(500); + } + + if (outside) { + outside = false; + Pather.usePortal(null, me.name); + } + + resume(); + } else { + scriptBroadcast("quit"); + } + + delay(500); + + continue; + } + + // Mode 3 - Spam entrance (still experimental) + if (Config.HostileAction === 3 && hostiles.length > 0 && me.inArea(sdk.areas.ThroneofDestruction)) { + switch (me.classid) { + case sdk.player.class.Sorceress: + prevPos = { x: me.x, y: me.y }; + pause(); + Pather.moveTo(15103, 5247); + + while (!findPlayer() && hostiles.length > 0) { + if (!me.skillDelay) { + Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), 15099, 5237); + } else { + if (Config.AttackSkill[2] > -1) { + Skill.cast(Config.AttackSkill[2], Skill.getHand(Config.AttackSkill[2]), 15099, 5237); + } else { + while (me.skillDelay) { + delay(40); + } + } + } + } + + break; + case sdk.player.class.Druid: + // Don't bother if it's not a tornado druid + if (Config.AttackSkill[1] !== sdk.skills.Tornado) { + break; + } + + prevPos = { x: me.x, y: me.y }; + pause(); + Pather.moveTo(15103, 5247); + + while (!findPlayer() && hostiles.length > 0) { + // Tornado path is a function of target x. Slight randomization will make sure it can't always miss + Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), 15099 + rand(-2, 2), 5237); + } + + break; + case sdk.player.class.Assassin: + prevPos = { x: me.x, y: me.y }; + pause(); + Pather.moveTo(15103, 5247); + + while (!findPlayer() && hostiles.length > 0) { + if (Config.UseTraps) { + check = ClassAttack.checkTraps({ x: 15099, y: 5242, classid: 544 }); + + if (check) { + ClassAttack.placeTraps({ x: 15099, y: 5242, classid: 544 }, 5); + } + } + + Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), 15099, 5237); + + while (me.skillDelay) { + delay(40); + } + } + + break; + } + } + + // Player left, return to old position + if (!hostiles.length && prevPos) { + Pather.moveTo(prevPos.x, prevPos.y); + resume(); + + // Reset position + prevPos = false; + } + + player = findPlayer(); + + if (player) { + // Mode 1 - Quit if hostile player is nearby + if (Config.HostileAction === 1) { + if (Config.TownOnHostile) { + console.log("ÿc1Hostile player nearby, going to town."); + pause(); + + if (!me.inTown) { + outside = true; + } + + try { + Town.goToTown(); + } catch (e) { + console.log(e + " Failed to go to town. Quitting."); + scriptBroadcast("quit"); // quit if failed to go to town + } + + while (hostiles.length > 0) { + delay(500); + } + + if (outside) { + outside = false; + Pather.usePortal(null, me.name); + } + + resume(); + } else { + scriptBroadcast("quit"); + } + + delay(500); + + continue; + } + + // Kill the hostile player + if (!prevPos) { + prevPos = { x: me.x, y: me.y }; + } + + pause(); + + Config.UseMerc = false; // Don't go revive the merc mid-fight + attackCount = 0; + + while (attackCount < 100) { + // Invalidated Unit (out of getUnit range) or player in town + if (!copyUnit(player).x || player.inTown || me.mode === sdk.player.mode.Dead) { + break; + } + + ClassAttack.doAttack(player, false); + + // Specific attack additions + switch (me.classid) { + case sdk.player.class.Sorceress: + case sdk.player.class.Necromancer: + // Dodge missiles - experimental + missile = Game.getMissile(); + + if (missile) { + do { + if ( + getPlayerFlag(me.gid, missile.owner, 8) + && (getDistance(me, missile) < 15 + || (missile.targetx && getDistance(me, missile.targetx, missile.targety) < 15)) + ) { + moveAway(missile, Skill.getRange(Config.AttackSkill[1])); + + break; + } + } while (missile.getNext()); + } + + // Move away if the player is too close or if he tries to move too close (telestomp) + if ( + Skill.getRange(Config.AttackSkill[1]) > 20 + && (getDistance(me, player) < 30 + || (player.targetx && getDistance(me, player.targetx, player.targety) < 15)) + ) { + moveAway(player, Skill.getRange(Config.AttackSkill[1])); + } + + break; + case sdk.player.class.Paladin: + // Smite summoners + if (Config.AttackSkill[1] === sdk.skills.BlessedHammer && Skill.canUse(sdk.skills.Smite)) { + if ( + [sdk.player.class.Necromancer, sdk.player.class.Druid].includes(player.classid) + && getDistance(me, player) < 4 + && checkSummons(player) + ) { + Skill.cast(sdk.skills.Smite, sdk.skills.hand.Left, player); + } + } + + break; + } + + attackCount += 1; + + if (player.dead) { + break; + } + } + + Pather.moveTo(prevPos.x, prevPos.y); + resume(); + } + } + + delay(200); + } +} diff --git a/d2bs/kolbot/threads/AntiIdle.js b/d2bs/kolbot/threads/AntiIdle.js new file mode 100644 index 000000000..3fde5a8fb --- /dev/null +++ b/d2bs/kolbot/threads/AntiIdle.js @@ -0,0 +1,25 @@ +/** +* @filename AntiIdle.js +* @author theBGuy +* @desc Prevent Idle diconnect +* +*/ +js_strict(true); +include("critical.js"); +include("core/Util.js"); +include("core/Packet.js"); + +function main () { + console.log("ÿc3Start AntiIdle"); + let idleTick = Time.seconds(getTickCount() + rand(1200, 1500)); + + while (true) { + if (me.ingame && me.gameReady) { + if (getTickCount() - idleTick > 0) { + Packet.questRefresh(); + idleTick += Time.seconds(rand(1200, 1500)); + console.log("Sent anti-idle packet, next refresh in: (" + Time.format(idleTick - getTickCount()) + ")"); + } + } + } +} diff --git a/d2bs/kolbot/threads/AreaWatcher.js b/d2bs/kolbot/threads/AreaWatcher.js new file mode 100644 index 000000000..cfa63c63f --- /dev/null +++ b/d2bs/kolbot/threads/AreaWatcher.js @@ -0,0 +1,33 @@ +/** +* @filename AreaWatcher.js +* @author dzik, theBGuy +* @desc suicide walk prevention +* +*/ +js_strict(true); +include("critical.js"); +includeCoreLibs(); + +/** + * @todo redo this, feels messy + */ + +function main() { + let _default = getScript("default.dbj"); + console.log("ÿc3Start AreaWatcher"); + + while (true) { + try { + if (me.gameReady && me.ingame && !me.inTown) { + // additonal check for wierd behavior - it shouldn't be possbile to run out of town in less than 30 seconds in game + if (getTickCount() - me.gamestarttime < Time.seconds(30)) continue; + !!_default && _default.stop(); + D2Bot.printToConsole("Saved from suicide walk!"); + !!_default && !_default.running ? quit() : D2Bot.restart(); + } + } catch (e) { + console.warn("AreaWatcher failed somewhere. ", e); + } + delay(1000); + } +} diff --git a/d2bs/kolbot/threads/AutoBuildThread.js b/d2bs/kolbot/threads/AutoBuildThread.js new file mode 100644 index 000000000..7623387f9 --- /dev/null +++ b/d2bs/kolbot/threads/AutoBuildThread.js @@ -0,0 +1,270 @@ +/* eslint-disable max-len */ +/** +* @filename AutoBuildThread.js +* @author alogwe +* @desc Helper thread for AutoBuild.js that monitors changes in character level +* +*/ +js_strict(true); +include("critical.js"); // required + +// globals needed for core gameplay +includeCoreLibs(); +include("core/Auto/AutoBuild.js"); +include("core/Auto/AutoSkill.js"); +include("core/Auto/AutoStat.js"); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +Config.init(); // includes libs/core/AutoBuild.js + +const debug = !!Config.AutoBuild.DebugMode; +const SPEND_POINTS = true; // For testing, it actually allows skill and stat point spending. +const STAT_ID_TO_NAME = [ + getLocaleString(sdk.locale.text.Strength), + getLocaleString(sdk.locale.text.Energy), + getLocaleString(sdk.locale.text.Dexterity), + getLocaleString(sdk.locale.text.Vitality) +]; +let prevLevel = me.charlvl; + +// Will check if value exists in an Array +Array.prototype.contains = (val) => this.indexOf(val) > -1; + +function skillInValidRange (id) { + switch (me.classid) { + case sdk.player.class.Amazon: + return sdk.skills.MagicArrow <= id && id <= sdk.skills.LightningFury; + case sdk.player.class.Sorceress: + return sdk.skills.FireBolt <= id && id <= sdk.skills.ColdMastery; + case sdk.player.class.Necromancer: + return sdk.skills.AmplifyDamage <= id && id <= sdk.skills.Revive; + case sdk.player.class.Paladin: + return sdk.skills.Sacrifice <= id && id <= sdk.skills.Salvation; + case sdk.player.class.Barbarian: + return sdk.skills.Bash <= id && id <= sdk.skills.BattleCommand; + case sdk.player.class.Druid: + return sdk.skills.Raven <= id && id <= sdk.skills.Hurricane; + case sdk.player.class.Assassin: + return sdk.skills.FireBlast <= id && id <= sdk.skills.PhoenixStrike; + default: + return false; + } +} + +const gainedLevels = () => me.charlvl - prevLevel; + +function canSpendPoints () { + let unusedStatPoints = me.getStat(sdk.stats.StatPts); + let haveUnusedStatpoints = unusedStatPoints >= 5; // We spend 5 stat points per level up + let unusedSkillPoints = me.getStat(sdk.stats.NewSkills); + let haveUnusedSkillpoints = unusedSkillPoints >= 1; // We spend 1 skill point per level up + debug && AutoBuild.print("Stat points:", unusedStatPoints, " Skill points:", unusedSkillPoints); + return haveUnusedStatpoints && haveUnusedSkillpoints; +} + +function spendStatPoint (id) { + let unusedStatPoints = me.getStat(sdk.stats.StatPts); + if (SPEND_POINTS) { + useStatPoint(id); + AutoBuild.print("useStatPoint(" + id + "): " + STAT_ID_TO_NAME[id]); + } else { + AutoBuild.print("Fake useStatPoint(" + id + "): " + STAT_ID_TO_NAME[id]); + } + delay(100); // TODO: How long should we wait... if at all? + return (unusedStatPoints - me.getStat(sdk.stats.StatPts) === 1); // Check if we spent one point +} + +// TODO: What do we do if it fails? report/ignore/continue? +function spendStatPoints () { + let stats = AutoBuildTemplate[me.charlvl].StatPoints; + let errorMessage = "\nInvalid stat point set in build template " + getTemplateFilename() + " at level " + me.charlvl; + let spentEveryPoint = true; + let unusedStatPoints = me.getStat(sdk.stats.StatPts); + let len = stats.length; + + if (Config.AutoStat.Enabled) { + return spentEveryPoint; + } + + if (len > unusedStatPoints) { + len = unusedStatPoints; + AutoBuild.print("Warning: Number of stats specified in your build template at level " + me.charlvl + " exceeds the available unused stat points" + + "\nOnly the first " + len + " stats " + stats.slice(0, len).join(", ") + " will be added"); + } + + // We silently ignore stats set to -1 + for (let i = 0; i < len; i++) { + let id = stats[i]; + let statIsValid = (typeof id === "number") && (sdk.stats.Strength <= id && id <= sdk.stats.Vitality); + + if (id === -1) { + continue; + } else if (statIsValid) { + let preStatValue = me.getStat(id); + let pointSpent = spendStatPoint(id); + if (SPEND_POINTS) { + if (!pointSpent) { + spentEveryPoint = false; + AutoBuild.print("Attempt to spend point " + (i + 1) + " in " + STAT_ID_TO_NAME[id] + " may have failed!"); + } else if (debug) { + AutoBuild.print("Stat (" + (i + 1) + "/" + len + ") Increased " + STAT_ID_TO_NAME[id] + " from " + preStatValue + " to " + me.getStat(id)); + } + } + } else { + throw new Error("Stat id must be one of the following:\n0:" + STAT_ID_TO_NAME[0] + + ",\t1:" + STAT_ID_TO_NAME[1] + ",\t2:" + STAT_ID_TO_NAME[2] + ",\t3:" + STAT_ID_TO_NAME[3] + errorMessage); + } + } + + return spentEveryPoint; +} + +function getTemplateFilename () { + let buildType = Config.AutoBuild.Template; + let templateFilename = "config/Builds/" + sdk.player.class.nameOf(me.classid) + "." + buildType + ".js"; + return templateFilename; +} + +function getRequiredSkills (id) { + function searchSkillTree (id) { + let results = []; + let skillTreeRight = getBaseStat("skills", id, sdk.stats.PreviousSkillRight); + let skillTreeMiddle = getBaseStat("skills", id, sdk.stats.PreviousSkillMiddle); + let skillTreeLeft = getBaseStat("skills", id, sdk.stats.PreviousSkillLeft); + + results.push(skillTreeRight); + results.push(skillTreeMiddle); + results.push(skillTreeLeft); + + for (let i = 0; i < results.length; i++) { + let skill = results[i]; + let skillInValidRange = (sdk.skills.Attack < skill && skill <= sdk.skills.PhoenixStrike) && (![sdk.skills.IdentifyScroll, sdk.skills.BookofIdentify, sdk.skills.TownPortalScroll, sdk.skills.BookofTownPortal].contains(skill)); + let hardPointsInSkill = me.getSkill(skill, sdk.skills.subindex.HardPoints); + + if (skillInValidRange && !hardPointsInSkill) { + requirements.push(skill); + searchSkillTree(skill); // search children; + } + } + } + + let requirements = []; + searchSkillTree(id); + const increasing = () => a - b; + return requirements.sort(increasing); +} + +function spendSkillPoint (id) { + let unusedSkillPoints = me.getStat(sdk.stats.NewSkills); + let skillName = getSkillById(id) + " (" + id + ")"; + if (SPEND_POINTS) { + useSkillPoint(id); + AutoBuild.print("useSkillPoint(): " + skillName); + } else { + AutoBuild.print("Fake useSkillPoint(): " + skillName); + } + delay(200); // TODO: How long should we wait... if at all? + return (unusedSkillPoints - me.getStat(sdk.stats.NewSkills) === 1); // Check if we spent one point +} + +function spendSkillPoints () { + let skills = AutoBuildTemplate[me.charlvl].SkillPoints; + let errInvalidSkill = "\nInvalid skill point set in build template " + getTemplateFilename() + " for level " + me.charlvl; + let spentEveryPoint = true; + let unusedSkillPoints = me.getStat(sdk.stats.NewSkills); + let len = skills.length; + + if (Config.AutoSkill.Enabled) { + return spentEveryPoint; + } + + if (len > unusedSkillPoints) { + len = unusedSkillPoints; + AutoBuild.print("Warning: Number of skills specified in your build template at level " + me.charlvl + " exceeds the available unused skill points" + + "\nOnly the first " + len + " skills " + skills.slice(0, len).join(", ") + " will be added"); + } + + // We silently ignore skills set to -1 + for (let i = 0; i < len; i++) { + let id = skills[i]; + + if (id === -1) { + continue; + } else if (!skillInValidRange(id)) { + throw new Error("Skill id " + id + " is not a skill for your character class" + errInvalidSkill); + } + + let skillName = getSkillById(id) + " (" + id + ")"; + let requiredSkills = getRequiredSkills(id); + if (requiredSkills.length > 0) { + throw new Error("You need prerequisite skills " + requiredSkills.join(", ") + " before adding " + skillName + errInvalidSkill); + } + + let requiredLevel = getBaseStat("skills", id, sdk.stats.MinimumRequiredLevel); + if (me.charlvl < requiredLevel) { + throw new Error("You need to be at least level " + requiredLevel + " before you get " + skillName + errInvalidSkill); + } + + let pointSpent = spendSkillPoint(id); + + if (SPEND_POINTS) { + if (!pointSpent) { + spentEveryPoint = false; + AutoBuild.print("Attempt to spend skill point " + (i + 1) + " in " + skillName + " may have failed!"); + } else if (debug) { + let actualSkillLevel = me.getSkill(id, sdk.skills.subindex.SoftPoints); + AutoBuild.print("Skill (" + (i + 1) + "/" + len + ") Increased " + skillName + " by one (level: ", actualSkillLevel + ")"); + } + } + + delay(200); // TODO: How long should we wait... if at all? + } + + return spentEveryPoint; +} + +/* +* TODO: determine if changes need to be made for +* the case of gaining multiple levels at once so as +* not to bombard the d2bs event system +*/ + +function main () { + try { + AutoBuild.print("Loaded helper thread"); + + while (true) { + let levels = gainedLevels(); + + if (levels > 0 && (canSpendPoints() || Config.AutoSkill.Enabled || Config.AutoStat.Enabled)) { + scriptBroadcast("toggleQuitlist"); + AutoBuild.print("Level up detected (", prevLevel, "-->", me.charlvl, ")"); + spendSkillPoints(); + spendStatPoints(); + Config.AutoSkill.Enabled && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save); + Config.AutoStat.Enabled && AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk); + scriptBroadcast({ event: "level up" }); + AutoBuild.applyConfigUpdates(); // scriptBroadcast() won't trigger listener on this thread. + + debug && AutoBuild.print("Incrementing cached character level to", prevLevel + 1); + // prevLevel doesn't get set to me.charlvl because + // we may have gained multiple levels at once + prevLevel += 1; + + scriptBroadcast("toggleQuitlist"); + } + + delay(1e3); + } + } catch (err) { + print("Something broke!"); + print("Error:" + err.toSource()); + print("Stack trace: \n" + err.stack); + + return false; + } +} diff --git a/d2bs/kolbot/threads/CloneKilla.js b/d2bs/kolbot/threads/CloneKilla.js new file mode 100644 index 000000000..c6f8324e1 --- /dev/null +++ b/d2bs/kolbot/threads/CloneKilla.js @@ -0,0 +1,49 @@ +/** +* @filename CloneKilla.js +* @author kolton +* @desc Kill Diablo Clone when he walks in game. Uses Fire Eye location. +* @todo +* - handle if fire eye location isn't possible +* +*/ +js_strict(true); +include("critical.js"); + +// globals needed for core gameplay +includeCoreLibs(); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +function main() { + D2Bot.init(); + Config.init(); + Pickit.init(); + Attack.init(); + Storage.Init(); + CraftingSystem.buildLists(); + Runewords.init(); + Cubing.init(); + include("scripts/KillDclone.js"); + + if (typeof KillDclone === "function") { + try { + D2Bot.printToConsole("Trying to kill DClone.", sdk.colors.D2Bot.DarkGold); + KillDclone.call(); + } catch (e) { + Misc.errorReport(e, "CloneKilla.js"); + } + } + + try { + quit(); + } finally { + while (me.ingame) { + delay(100); + } + } + + return true; +} diff --git a/d2bs/kolbot/threads/HeartBeat.js b/d2bs/kolbot/threads/HeartBeat.js new file mode 100644 index 000000000..dd0efc209 --- /dev/null +++ b/d2bs/kolbot/threads/HeartBeat.js @@ -0,0 +1,53 @@ +/** +* @filename HeartBeat.js +* @author kolton +* @desc Keep a link with d2bot#. If it's lost, the d2 window is killed +* +*/ + +function main () { + include("critical.js"); // required + D2Bot.init(); + console.log("Heartbeat loaded"); + + function togglePause () { + let script = getScript(); + + if (script) { + do { + if (script.name.includes(".dbj")) { + if (script.running) { + console.log("ÿc1Pausing ÿc0" + script.name); + script.pause(); + } else { + console.log("ÿc2Resuming ÿc0" + script.name); + script.resume(); + } + } + } while (script.getNext()); + } + + return true; + } + + // Event functions + function KeyEvent (key) { + switch (key) { + case sdk.keys.PauseBreak: + if (me.ingame) { + break; + } + + togglePause(); + + break; + } + } + + addEventListener("keyup", KeyEvent); + + while (true) { + D2Bot.heartBeat(); + delay(1000); + } +} diff --git a/d2bs/kolbot/threads/Party.js b/d2bs/kolbot/threads/Party.js new file mode 100644 index 000000000..3cb328b28 --- /dev/null +++ b/d2bs/kolbot/threads/Party.js @@ -0,0 +1,246 @@ +/** +* @filename Party.js +* @author kolton, theBGuy +* @desc handle party procedure ingame +* +*/ +js_strict(true); +include("critical.js"); + +// globals needed for core gameplay +includeCoreLibs(); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +// party thread specific +include("oog/ShitList.js"); + +function main () { + Config.init(); + + /** @type {string[][]} */ + let [shitList, scriptList] = [[], []]; + let myPartyId, player, currScript; + let playerLevels = {}; + let partyTick = getTickCount(); + + if (!me.gameserverip) { + console.log("Shutting down party thread, it's not needed on single player"); + return true; + } + + /** + * Format the event message here to prevent repetitive code + * @param {string[]} arr + * @param {Player | string} player + * @param {string} [killer] + */ + const eventMsg = (arr, player, killer) => { + try { + typeof player === "string" && (player = getParty(player)); + + if (!player || player.name === me.name) return ""; + return (arr + .random() + .format( + ["$name", player.name], + ["$level", player.level], + ["$class", sdk.player.class.nameOf(player.classid)], + ["$killer", killer] + ) + ); + } catch (e1) { + return ""; + } + }; + + const gameEvent = function (mode, param1, param2, name1, name2) { + let msg = ""; + + switch (mode) { + case 0x02: // "%Name1(%Name2) joined our world. Diablo's minions grow stronger." + if (Config.Greetings.length > 0) { + msg = eventMsg(Config.Greetings, name1); + } + + break; + case 0x06: // "%Name1 was Slain by %Name2" + if (Config.DeathMessages.length > 0) { + msg = eventMsg(Config.DeathMessages, name1, name2); + } + + break; + } + + if (msg) { + say(msg); + } + }; + + if (Config.Greetings.length > 0 || Config.DeathMessages.length > 0) { + addEventListener("gameevent", gameEvent); + } + + let quitting = false; + // let partyCheck = false; + + const scriptEvent = function (msg) { + if (!!msg && typeof msg === "string") { + switch (msg) { + case "hostileCheck": + // partyCheck = true; + + break; + case "quit": + console.debug("Quiting"); + quitting = true; + + break; + default: + let obj; + + try { + obj = JSON.parse(msg); + } catch (e) { + return; + } + + if (obj && obj.hasOwnProperty("currScript")) { + currScript = obj.currScript; + } + + break; + } + } + }; + + addEventListener("scriptmsg", scriptEvent); + + console.log("ÿc2Party thread loaded. Mode: " + (Config.PublicMode === 2 ? "Accept" : "Invite")); + + if (Config.ShitList || Config.UnpartyShitlisted) { + shitList = ShitList.read(); + + console.log(shitList.length + " entries in shit list."); + } + + if (Config.PartyAfterScript) { + scriptList = []; + + for (let i in Scripts) { + if (Scripts.hasOwnProperty(i) && !!Scripts[i]) { + scriptList.push(i); + } + } + } + + // Main loop + while (true) { + if (quitting) { + // we intercepted quit message to toolsthread, go ahead an shut down + return true; + } + + /** + * @todo if we are already partied with everyone in game, then this doesn't need to keep checking unless an event happens + * e.g. someone joins/leaves game or someone declares hostility + * the exception to that is if we are running with Config.Congratulations, in which case we do need to constantly monitor changes + */ + if (me.gameReady + && (!Config.PartyAfterScript || scriptList.indexOf(currScript) > scriptList.indexOf(Config.PartyAfterScript))) { + player = getParty(); + + if (player) { + myPartyId = player.partyid; + + while (player.getNext()) { + switch (Config.PublicMode) { + case 1: // Invite others + case 3: // Invite others but never accept + if (getPlayerFlag(me.gid, player.gid, sdk.player.flag.Hostile)) { + if (Config.ShitList && shitList.indexOf(player.name) === -1) { + say(player.name + " has been shitlisted."); + shitList.push(player.name); + ShitList.add(player.name); + } + + if (player.partyflag === sdk.party.flag.Cancel) { + clickParty(player, sdk.party.controls.InviteOrCancel); // cancel invitation + delay(100); + } + + break; + } + + if (Config.ShitList && shitList.includes(player.name)) { + break; + } + + if (player.partyflag !== sdk.party.flag.Cancel + && player.partyflag !== sdk.party.flag.Accept + && player.partyid === sdk.party.NoParty) { + clickParty(player, sdk.party.controls.InviteOrCancel); + delay(100); + } + + if (Config.PublicMode === 3) { + break; + } + // eslint-disable-next-line no-fallthrough + case 2: // Accept invites + if (myPartyId === sdk.party.NoParty) { + if (Config.Leader && player.name !== Config.Leader) { + break; + } + + if (player.partyflag === sdk.party.flag.Accept + && (getTickCount() - partyTick >= 2000 || Config.FastParty)) { + clickParty(player, sdk.party.controls.InviteOrCancel); + delay(100); + } + } + + break; + } + + if (Config.UnpartyShitlisted) { + // Add new hostile players to temp shitlist, leader should have Config.ShitList set to true to update the permanent list. + if (getPlayerFlag(me.gid, player.gid, sdk.player.flag.Hostile) && shitList.indexOf(player.name) === -1) { + shitList.push(player.name); + } + + if (shitList.includes(player.name) && myPartyId !== sdk.party.NoParty && player.partyid === myPartyId) { + // Only the one sending invites should say this. + if ([1, 3].includes(Config.PublicMode)) { + say(player.name + " is shitlisted. Do not invite them."); + } + + clickParty(player, sdk.party.controls.Leave); + delay(100); + } + } + + if (Config.Congratulations.length > 0) { + if (player.name !== me.name) { + if (!playerLevels[player.name]) { + playerLevels[player.name] = player.level; + } + + if (player.level > playerLevels[player.name]) { + let msg = eventMsg(Config.Congratulations, player); + msg && say(msg); + + playerLevels[player.name] = player.level; + } + } + } + } + } + } + + delay(500); + } +} diff --git a/d2bs/kolbot/threads/RushThread.js b/d2bs/kolbot/threads/RushThread.js new file mode 100644 index 000000000..3d3d01a55 --- /dev/null +++ b/d2bs/kolbot/threads/RushThread.js @@ -0,0 +1,1172 @@ +/* eslint-disable max-len */ +/** +* @filename RushThread.js +* @author kolton, theBGuy +* @desc Second half of the Rusher script +* +*/ +js_strict(true); +include("critical.js"); + +// globals needed for core gameplay +includeCoreLibs(); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +const Overrides = require("../libs/modules/Override"); + +let count = 0; +let silentNameTracker = []; +let wpsToGive = Pather.nonTownWpAreas.slice(0).filter(function (area) { + if (area === sdk.areas.HallsofPain) return false; + if (me.classic && area >= sdk.areas.Harrogath) return false; + return true; +}); + +function wpEvent (who, msg) { + if (typeof msg === "string" && msg === "gotwp" || msg === "Failed to get wp") { + count++; + !silentNameTracker.includes(who) && silentNameTracker.push(who); + } +} + +function giveWP () { + let wp = Game.getObject("waypoint"); + let success = false; + if (wp && !me.inTown && wpsToGive.includes(me.area)) { + try { + addEventListener("chatmsg", wpEvent); + let playerCount = Misc.getPartyCount(); + let mobCount = getUnits(sdk.unittype.Monster).filter(mon => mon.distance <= 15 && mon.attackable).length; + mobCount > 0 && Attack.securePosition(me.x, me.y, 15, Time.seconds(30), true); + wp.distance > 5 && Pather.moveToUnit(wp); + Pather.makePortal(); + say("wp"); + let tick = getTickCount(); + while (getTickCount() - tick < Time.minutes(2)) { + let player = Game.getPlayer(); + if (player) { + do { + if (player.name !== me.name && !silentNameTracker.includes(player.name)) { + silentNameTracker.push(player.name); + } + } while (player.getNext()); + } + if (count === playerCount || (silentNameTracker.length === playerCount && Misc.getNearbyPlayerCount() === 0)) { + wpsToGive.remove(me.area); + success = true; + break; + } + delay(50); + } + } catch (e) { + console.error(e); + Config.LocalChat.Enabled && say("Failed to give wp"); + } finally { + removeEventListener("chatmsg", wpEvent); + silentNameTracker = []; + count = 0; + } + return success; + } + + return false; +} + +new Overrides.Override(Pather, Pather.useWaypoint, function(orignal, targetArea, check) { + if (orignal(targetArea, check)) { + return (Config.Rusher.GiveWps && giveWP()) || true; + } else { + print("failed"); + + return false; + } +}).apply(); + +function main () { + let tick; + + this.log = function (msg = "", sayMsg = true) { + console.log(msg); + sayMsg && say(msg); + }; + + this.playerIn = function (area) { + !area && (area = me.area); + + let party = getParty(); + + if (party) { + do { + if (party.name !== me.name && party.area === area) { + return true; + } + } while (party.getNext()); + } + + return false; + }; + + this.bumperCheck = function () { + let bumperLevelReq = [20, 40, 60][me.diff]; + return Misc.checkPartyLevel(bumperLevelReq); + }; + + this.playersInAct = function (act) { + !act && (act = me.act); + + let area = sdk.areas.townOfAct(act); + let party = getParty(); + + if (party) { + do { + if (party.name !== me.name && party.area !== area) { + return false; + } + } while (party.getNext()); + } + + return true; + }; + + this.andariel = function () { + this.log("starting andariel"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.CatacombsLvl2, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true) + || !Pather.moveTo(22582, 9612)) { + throw new Error("andy failed"); + } + + Pather.makePortal(); + Attack.securePosition(me.x, me.y, 40, 3000, true); + this.log("1"); + + while (!this.playerIn()) { + Pather.moveTo(22582, 9612); + delay(250); + } + + Attack.kill(sdk.monsters.Andariel); + this.log("2"); + Pather.moveTo(22582, 9612); + + while (this.playerIn()) { + delay(250); + } + + Pather.usePortal(null, me.name); + this.log("a2"); + Town.goToTown(2); + + while (!this.playersInAct(2)) { + delay(250); + } + + return true; + }; + + this.cube = function () { + if (me.normal) { + this.log("starting cube"); + Pather.useWaypoint(sdk.areas.HallsoftheDeadLvl2, true); + Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.HallsoftheDeadLvl3, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest)) { + throw new Error("cube failed"); + } + + Pather.makePortal(); + Attack.securePosition(me.x, me.y, 30, 3000, true); + this.log("1"); + + while (!this.playerIn()) { + delay(100); + } + + while (this.playerIn()) { + delay(100); + } + + Pather.usePortal(null, me.name); + } + + return true; + }; + + this.amulet = function () { + this.log("starting amulet"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.LostCity, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2], true) + || !Pather.moveTo(15044, 14045)) { + throw new Error("amulet failed"); + } + + Pather.makePortal(); + Attack.securePosition(me.x, me.y, 25, 3000, me.hell, me.hell); + + this.log("1"); + + while (!this.playerIn()) { + delay(100); + } + + while (this.playerIn()) { + delay(100); + } + + Pather.usePortal(null, me.name); + + return true; + }; + + this.staff = function () { + this.log("starting staff"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.FarOasis, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3], true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest)) { + throw new Error("staff failed"); + } + + Pather.makePortal(); + Attack.securePosition(me.x, me.y, 30, 3000, true); + this.log("1"); + + while (!this.playerIn()) { + delay(100); + } + + while (this.playerIn()) { + delay(100); + } + + Pather.usePortal(null, me.name); + + return true; + }; + + this.summoner = function () { + // right up 25449 5081 (25431, 5011) + // left up 25081 5446 (25011, 5446) + // right down 25830 5447 (25866, 5431) + // left down 25447 5822 (25431, 5861) + + this.log("starting summoner"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.ArcaneSanctuary, true) && Precast.doPrecast(true); + + let preset = Game.getPresetObject(sdk.areas.ArcaneSanctuary, sdk.quest.chest.Journal); + let spot = {}; + + switch (preset.roomx * 5 + preset.x) { + case 25011: + spot = { x: 25081, y: 5446 }; + break; + case 25866: + spot = { x: 25830, y: 5447 }; + break; + case 25431: + switch (preset.roomy * 5 + preset.y) { + case 5011: + spot = { x: 25449, y: 5081 }; + break; + case 5861: + spot = { x: 25447, y: 5822 }; + break; + } + + break; + } + + if (!Pather.moveToUnit(spot)) { + throw new Error("summoner failed"); + } + + Pather.makePortal(); + Attack.securePosition(me.x, me.y, 25, 3000); + this.log("1"); + + while (!this.playerIn()) { + Pather.moveToUnit(spot); + Attack.securePosition(me.x, me.y, 25, 500); + delay(250); + } + + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal); + Attack.kill(sdk.monsters.Summoner); + this.log("2"); + + while (this.playerIn()) { + delay(100); + } + + Pickit.pickItems(); + Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal); + + let redPortal = Game.getObject(sdk.objects.RedPortal); + + if (!redPortal || !this.usePortal(null, null, redPortal)) { + if (!Misc.poll(() => { + let journal = Game.getObject(sdk.quest.chest.Journal); + + if (journal && journal.interact()) { + delay(1000); + me.cancel(); + } + + redPortal = Pather.getPortal(sdk.areas.CanyonofMagic); + + return (redPortal && Pather.usePortal(null, null, redPortal)); + })) throw new Error("summoner failed"); + } + + return true; + }; + + this.duriel = function () { + this.log("starting duriel"); + + if (me.inTown) { + Town.doChores(); + Pather.useWaypoint(sdk.areas.CanyonofMagic, true); + } else { + giveWP(); + } + + Precast.doPrecast(true); + + if (!Pather.moveToExit(getRoom().correcttomb, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder)) { + throw new Error("duriel failed"); + } + + Pather.makePortal(); + Attack.securePosition(me.x, me.y, 30, 3000, true, me.hell); + this.log("1"); + + while (!this.playerIn()) { + delay(100); + } + + while (this.playerIn()) { + delay(100); + } + + while (!Game.getObject(sdk.objects.PortaltoDurielsLair)) { + delay(500); + } + + Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair); + Attack.kill(sdk.monsters.Duriel); + Pickit.pickItems(); + + Pather.teleport = false; + + Pather.moveTo(22579, 15706); + + Pather.teleport = true; + + Pather.moveTo(22577, 15649, 10); + Pather.moveTo(22577, 15609, 10); + Pather.makePortal(); + this.log("1"); + + while (!this.playerIn()) { + delay(100); + } + + if (!Pather.usePortal(null, me.name)) { + Town.goToTown(); + } + + Pather.useWaypoint(sdk.areas.PalaceCellarLvl1); + Pather.moveToExit([sdk.areas.HaremLvl2, sdk.areas.HaremLvl1], true); + Pather.moveTo(10022, 5047); + this.log("a3"); + Town.goToTown(3); + Town.doChores(); + + while (!this.playersInAct(3)) { + delay(250); + } + + return true; + }; + + // re-write to prevent fail to complete quest due to killing council from to far away + this.travincal = function () { + this.log("starting travincal"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.Travincal, true) && Precast.doPrecast(true); + + let coords = [me.x, me.y]; + + Pather.moveTo(coords[0] + 23, coords[1] - 102); + Pather.makePortal(); + Attack.securePosition(me.x, me.y, 40, 3000); + this.log("1"); + + while (!this.playerIn()) { + delay(250); + } + + Pather.moveTo(coords[0] + 30, coords[1] - 134); + Pather.moveTo(coords[0] + 86, coords[1] - 130); + Pather.moveTo(coords[0] + 71, coords[1] - 94); + Attack.securePosition(me.x, me.y, 40, 3000); + + Pather.moveTo(coords[0] + 23, coords[1] - 102); + Pather.makePortal(); + this.log("2"); + Pather.usePortal(null, me.name); + + return true; + }; + + this.mephisto = function () { + this.log("starting mephisto"); + + Town.doChores(); + Pather.useWaypoint(sdk.areas.DuranceofHateLvl2, true) && Precast.doPrecast(true); + Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true) && Pather.moveTo(17692, 8023) && Pather.makePortal(); + delay(2000); + this.log("1"); + + while (!this.playerIn()) { + delay(250); + } + + Pather.moveTo(17591, 8070); + Attack.kill(sdk.monsters.Mephisto); + Pickit.pickItems(); + Pather.moveTo(17692, 8023) && Pather.makePortal(); + this.log("2"); + + while (this.playerIn()) { + delay(250); + } + + Pather.moveTo(17591, 8070) && Attack.securePosition(me.x, me.y, 40, 3000); + + let hydra = Game.getMonster(getLocaleString(sdk.locale.monsters.Hydra)); + + if (hydra) { + do { + while (!hydra.dead && hydra.hp > 0) { + delay(500); + } + } while (hydra.getNext()); + } + + Pather.makePortal(); + Pather.moveTo(17581, 8070); + this.log("1"); + + while (!this.playerIn()) { + delay(250); + } + + this.log("a4"); + + while (!this.playersInAct(4)) { + delay(250); + } + + delay(2000); + Pather.usePortal(null); + + return true; + }; + + this.diablo = function () { + include("core/Common/Diablo.js"); + this.log("starting diablo"); + + function inviteIn () { + Pather.moveTo(7763, 5267) && Pather.makePortal(); + Pather.moveTo(7727, 5267); + this.log("1"); + + while (!this.playerIn()) { + delay(250); + } + + return true; + } + + Town.doChores(); + Pather.useWaypoint(sdk.areas.RiverofFlame); + Precast.doPrecast(true); + if (!Pather.moveToExit(sdk.areas.ChaosSanctuary, true) && !Pather.moveTo(7790, 5544)) throw new Error("Failed to move to Chaos Sanctuary"); + + Common.Diablo.initLayout(); + Config.Diablo.Fast = true; + Config.Diablo.SealLeader = false; + + try { + Common.Diablo.runSeals(Config.Diablo.SealOrder); + print("Attempting to find Diablo"); + inviteIn() && Common.Diablo.diabloPrep(); + } catch (error) { + print("Diablo wasn't found. Checking seals."); + Common.Diablo.runSeals(Config.Diablo.SealOrder); + inviteIn() && Common.Diablo.diabloPrep(); + } + + Attack.kill(sdk.monsters.Diablo); + this.log("2"); + + if (me.expansion) { + this.log("a5"); + + while (!this.playersInAct(5)) { + delay(250); + } + } + + Pickit.pickItems(); + !Pather.usePortal(null, me.name) && Town.goToTown(); + + return true; + }; + + this.ancients = function () { + if (me.hell && !Config.Rusher.HellAncients) { + if (!Config.Rusher.GiveWps) { + this.log("Hell rush complete~"); + delay(500); + quit(); + } + + return false; + } + + if (!this.bumperCheck()) { + if (!Config.Rusher.GiveWps) { + this.log("No eligible bumpers detected. Rush complete~"); + delay(500); + quit(); + } + + return false; + } + + include("core/Common/Ancients.js"); + this.log("starting ancients"); + + Town.doChores(); + Pather.useWaypoint(sdk.areas.AncientsWay, true) && Precast.doPrecast(true); + + if (!Pather.moveToExit(sdk.areas.ArreatSummit, true)) { + throw new Error("Failed to go to Ancients way."); + } + + Pather.moveTo(10089, 12622); + Pather.makePortal(); + this.log("3"); + + while (!this.playerIn()) { + delay(250); + } + + Pather.moveTo(10048, 12628); + Common.Ancients.touchAltar(); + Common.Ancients.startAncients(); + + Pather.moveTo(10089, 12622); + me.cancel(); + Pather.makePortal(); + + while (this.playerIn()) { + delay(100); + } + + !Pather.usePortal(null, me.name) && Town.goToTown(); + + return true; + }; + + this.baal = function () { + if (me.hell) { + if (!Config.Rusher.GiveWps) { + this.log("Baal not done in Hell ~Hell rush complete~"); + delay(500); + quit(); + } + wpsToGive.remove(sdk.areas.WorldstoneLvl2); + + return false; + } + + if (!this.bumperCheck()) { + if (!Config.Rusher.GiveWps) { + this.log("No eligible bumpers detected. ~Rush complete~"); + delay(500); + quit(); + } + wpsToGive.remove(sdk.areas.WorldstoneLvl2); + + return false; + } + + include("core/Common/Baal.js"); + this.log("starting baal"); + + if (me.inTown) { + Town.doChores(); + Pather.useWaypoint(sdk.areas.WorldstoneLvl2) && Precast.doPrecast(true); + + if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], true)) { + throw new Error("Failed to move to Throne of Destruction."); + } + } + + Pather.moveTo(15113, 5040); + Attack.clear(15); + Common.Baal.clearThrone(); + + if (!Common.Baal.clearWaves()) { + throw new Error("Couldn't clear baal waves"); + } + + Common.Baal.clearThrone(); + me.checkForMobs({ range: 30 }) && this.clearWaves(); // ensure waves are actually done + Pather.moveTo(15090, 5008); + delay(5000); + Precast.doPrecast(true); + Misc.poll(() => !Game.getMonster(sdk.monsters.ThroneBaal), Time.minutes(3), 1000); + + let portal = Game.getObject(sdk.objects.WorldstonePortal); + + if (portal) { + Pather.usePortal(null, null, portal); + } else { + throw new Error("Couldn't find portal."); + } + + Pather.moveTo(15213, 5908); + Pather.makePortal(); + Pather.moveTo(15170, 5950); + delay(1000); + this.log("3"); + + while (!this.playerIn()) { + delay(250); + } + + Pather.moveTo(15134, 5923); + Attack.kill(sdk.monsters.Baal); + Pickit.pickItems(); + + return true; + }; + + this.clearArea = function (area) { + Pather.journeyTo(area); + Attack.clearLevel(0); + this.log("Done clearing area: " + area); + }; + + // Quests + this.cain = function () { + if (!Config.Rusher.Cain) return true; + + this.log("starting cain"); + Town.doChores(); + Pather.useWaypoint(sdk.areas.DarkWood, true) && Precast.doPrecast(true); + + if (!Pather.moveToPreset(sdk.areas.DarkWood, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) { + throw new Error("Failed to move to Tree of Inifuss"); + } + + let tree = Game.getObject(sdk.quest.chest.InifussTree); + !!tree && tree.distance > 5 && Pather.moveToUnit(tree); + Attack.securePosition(me.x, me.y, 40, 3000, true); + !!tree && tree.distance > 5 && Pather.moveToUnit(tree); + Pather.makePortal(); + this.log("1"); + tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(2)) { + if (tree.mode) { + break; + } + Attack.securePosition(me.x, me.y, 20, 1000); + } + + Pather.usePortal(1) || Town.goToTown(); + Pather.useWaypoint(sdk.areas.StonyField, true); + Precast.doPrecast(true); + Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 10, 10, false, true); + Attack.securePosition(me.x, me.y, 40, 3000, true); + Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Object, sdk.quest.chest.StoneAlpha, null, null, true); + Pather.makePortal(); + this.log("1"); + + tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(2)) { + if (Pather.usePortal(sdk.areas.Tristram)) { + break; + } + Attack.securePosition(me.x, me.y, 35, 1000); + } + + if (me.inArea(sdk.areas.Tristram)) { + Pather.moveTo(me.x, me.y + 6); + let gibbet = Game.getObject(sdk.quest.chest.CainsJail); + + if (gibbet && !gibbet.mode) { + if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.CainsJail, 0, 0, true, true)) { + throw new Error("Failed to move to Cain's Jail"); + } + + Attack.securePosition(gibbet.x, gibbet.y, 20, 3000); + Pather.makePortal(); + this.log("1"); + + tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(2)) { + if (gibbet.mode) { + break; + } + Attack.securePosition(me.x, me.y, 10, 1000); + } + } + } + + return true; + }; + + this.radament = function () { + if (!Config.Rusher.Radament) return false; + + this.log("starting radament"); + + let moveIntoPos = function (unit, range) { + let coords = []; + let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); + let angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 105, -105, 120, -120, 135, -135, 150, -150, 180]; + + for (let i = 0; i < angles.length; i += 1) { + let coordx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * range + unit.x); + let coordy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * range + unit.y); + + try { + if (!(getCollision(unit.area, coordx, coordy) & 0x1)) { + coords.push({ + x: coordx, + y: coordy + }); + } + } catch (e) { + continue; + } + } + + if (coords.length > 0) { + coords.sort(Sort.units); + + return Pather.moveToUnit(coords[0]); + } + + return false; + }; + + Pather.useWaypoint(sdk.areas.A2SewersLvl2, true) && Precast.doPrecast(false); + Pather.moveToExit(sdk.areas.A2SewersLvl3, true); + + let radaPreset = Game.getPresetObject(sdk.areas.A2SewersLvl3, sdk.quest.chest.HoradricScrollChest); + let radaCoords = { + area: sdk.areas.A2SewersLvl3, + x: radaPreset.roomx * 5 + radaPreset.x, + y: radaPreset.roomy * 5 + radaPreset.y + }; + + moveIntoPos(radaCoords, 50); + let rada = Misc.poll(() => Game.getMonster(sdk.monsters.Radament), 1500, 500); + + rada ? moveIntoPos(rada, 60) : print("radament unit not found"); + Attack.securePosition(me.x, me.y, 35, 3000); + Pather.makePortal(); + this.log("1"); + + while (!this.playerIn()) { + delay(200); + } + + Attack.kill(sdk.monsters.Radament); + + let returnSpot = { + x: me.x, + y: me.y + }; + + this.log("2"); + Pickit.pickItems(); + Attack.securePosition(me.x, me.y, 30, 3000); + + while (this.playerIn()) { + delay(200); + } + + Pather.moveToUnit(returnSpot); + Pather.makePortal(); + this.log("all in"); + + while (!this.playerIn()) { + delay(200); + } + + Misc.poll(() => !Game.getItem(sdk.quest.item.BookofSkill), 30000, 1000); + + while (this.playerIn()) { + delay(200); + } + + Pather.usePortal(null, null); + + return true; + }; + + this.lamesen = function () { + if (!Config.Rusher.LamEsen) return false; + + this.log("starting lamesen"); + + if (!Town.goToTown() || !Pather.useWaypoint(sdk.areas.KurastBazaar, true)) { + throw new Error("Lam Essen quest failed"); + } + + Precast.doPrecast(false); + + if (!Pather.moveToExit(sdk.areas.RuinedTemple, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.LamEsensTomeHolder)) { + throw new Error("Lam Essen quest failed"); + } + + Attack.securePosition(me.x, me.y, 30, 2000); + Pather.makePortal(); + this.log("1"); + + while (!this.playerIn()) { + delay(200); + } + + while (this.playerIn()) { + delay(200); + } + + Pather.usePortal(null, null); + + return true; + }; + + this.izual = function () { + if (!Config.Rusher.Izual) return false; + + this.log("starting izual"); + + let moveIntoPos = function (unit, range) { + let coords = []; + let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); + let angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 105, -105, 120, -120, 135, -135, 150, -150, 180]; + + for (let i = 0; i < angles.length; i += 1) { + let coordx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * range + unit.x); + let coordy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * range + unit.y); + + try { + if (!(getCollision(unit.area, coordx, coordy) & 0x1)) { + coords.push({ + x: coordx, + y: coordy + }); + } + } catch (e) { + continue; + } + } + + if (coords.length > 0) { + coords.sort(Sort.units); + + return Pather.moveToUnit(coords[0]); + } + + return false; + }; + + Pather.useWaypoint(sdk.areas.CityoftheDamned, true) && Precast.doPrecast(false); + Pather.moveToExit(sdk.areas.PlainsofDespair, true); + + let izualPreset = Game.getPresetMonster(sdk.areas.PlainsofDespair, sdk.monsters.Izual); + let izualCoords = { + area: sdk.areas.PlainsofDespair, + x: izualPreset.roomx * 5 + izualPreset.x, + y: izualPreset.roomy * 5 + izualPreset.y + }; + + moveIntoPos(izualCoords, 50); + let izual = Misc.poll(() => Game.getMonster(sdk.monsters.Izual), 1500, 500); + + izual ? moveIntoPos(izual, 60) : print("izual unit not found"); + + let returnSpot = { + x: me.x, + y: me.y + }; + + Attack.securePosition(me.x, me.y, 30, 3000); + Pather.makePortal(); + this.log("1"); + + while (!this.playerIn()) { + delay(200); + } + + Attack.kill(sdk.monsters.Izual); + Pickit.pickItems(); + this.log("2"); + Pather.moveToUnit(returnSpot); + + while (this.playerIn()) { + delay(200); + } + + Pather.usePortal(null, null); + + return true; + }; + + this.shenk = function () { + if (!Config.Rusher.Shenk) return false; + + this.log("starting shenk"); + + Pather.useWaypoint(sdk.areas.FrigidHighlands, true) && Precast.doPrecast(false); + Pather.moveTo(3846, 5120); + Attack.securePosition(me.x, me.y, 30, 3000); + Pather.makePortal(); + this.log("1"); + + while (!this.playerIn()) { + delay(200); + } + + Attack.kill(getLocaleString(sdk.locale.monsters.ShenktheOverseer)); + Pickit.pickItems(); + Pather.moveTo(3846, 5120); + this.log("2"); + + while (this.playerIn()) { + delay(200); + } + + Pather.usePortal(null, null); + + return true; + }; + + this.anya = function () { + if (!Config.Rusher.Anya) return false; + + !me.inTown && Town.goToTown(); + + this.log("starting anya"); + + if (!Pather.useWaypoint(sdk.areas.CrystalizedPassage, true)) { + throw new Error("Anya quest failed"); + } + + Precast.doPrecast(false); + + if (!Pather.moveToExit(sdk.areas.FrozenRiver, true) + || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform)) { + throw new Error("Anya quest failed"); + } + + Attack.securePosition(me.x, me.y, 30, 2000); + + let anya = Game.getObject(sdk.objects.FrozenAnya); + + if (anya) { + Pather.moveToUnit(anya); + // Rusher should be able to interact so quester can get the potion without entering + Packet.entityInteract(anya); + delay(1000 + me.ping); + me.cancel(); + } + + Pather.makePortal(); + this.log("1"); + + while (!this.playerIn()) { + delay(200); + } + + Misc.poll(() => !Game.getObject(sdk.objects.FrozenAnya), 30000, 1000); + + this.log("2"); // Mainly for non-questers to know when to get the scroll of resistance + + while (this.playerIn()) { + delay(200); + } + + Pather.usePortal(null, null); + + return true; + }; + + this.givewps = function () { + if (!Config.Rusher.GiveWps) return false; + + let wpsLeft = wpsToGive.slice(0); + console.log(JSON.stringify(wpsLeft)); + + wpsLeft.forEach(function (wp) { + me.checkScrolls(sdk.items.TomeofTownPortal) <= 5 && (Pather.useWaypoint(sdk.areas.townOf(me.area)) || Town.goToTown()) && Town.doChores(); + Pather.useWaypoint(wp); + }); + + return true; + }; + + console.log(sdk.colors.LightGold + "Loading RushThread"); + + let command = ""; + let current = 0; + let commandsplit = ""; + let check = -1; + let sequence = [ + "cain", "andariel", "radament", "cube", "amulet", "staff", "summoner", "duriel", "lamesen", + "travincal", "mephisto", "izual", "diablo", "shenk", "anya", "ancients", "baal", "givewps" + ]; + + this.scriptEvent = function (msg) { + if (typeof msg === "string") { + if (!msg.startsWith("rush-")) return; + command = msg.substring(5); + } + }; + + addEventListener("scriptmsg", this.scriptEvent); + + // START + Config.init(false); + Pickit.init(false); + Attack.init(); + Storage.Init(); + CraftingSystem.buildLists(); + Runewords.init(); + Cubing.init(); + + Config.MFLeader = false; + + while (true) { + if (command) { + switch (command) { + case "go": + // End run if entire sequence is done or if Config.Rusher.LastRun is done + if (current >= sequence.length || (Config.Rusher.LastRun && current > sequence.indexOf(Config.Rusher.LastRun))) { + delay(3000); + this.log("bye ~"); + console.log("Current sequence length: " + current + " sequence length: " + sequence.length); + + while (Misc.getPlayerCount() > 1) { + delay(1000); + } + + scriptBroadcast("quit"); + + break; + } + + Town.doChores(); + + try { + this[sequence[current]](); + } catch (sequenceError) { + this.log(sequenceError.message); + this.log("2"); + Town.goToTown(); + } + + current += 1; + command = "go"; + + break; + default: + if (typeof command === "string") { + if (command.split(" ")[0] !== undefined && command.split(" ")[0] === "skiptoact") { + if (!isNaN(parseInt(command.split(" ")[1], 10))) { + switch (parseInt(command.split(" ")[1], 10)) { + case 2: + current = sequence.indexOf("andariel") + 1; + Town.goToTown(2); + + break; + case 3: + current = sequence.indexOf("duriel") + 1; + Town.goToTown(3); + + break; + case 4: + current = sequence.indexOf("mephisto") + 1; + Town.goToTown(4); + + break; + case 5: + current = sequence.indexOf("diablo") + 1; + Town.goToTown(5); + + break; + } + } + + command = ""; + } else if (command.split(" ")[0] !== undefined && command.split(" ")[0] === "clear") { + this.clearArea(Number(command.split(" ")[1])); + Town.goToTown(); + + command = "go"; + } else if (command.split(" ")[0] !== undefined && command.split(" ")[0] === "highestquest") { + command.split(" ")[1] !== undefined && (commandsplit = command.split(" ")[1]); + check = sequence.findIndex(i => i === commandsplit); + check > -1 && (current = check + 1); + + command = ""; + } else { + for (let i = 0; i < sequence.length; i += 1) { + if (command && sequence[i].match(command, "gi")) { + current = i; + + break; + } + } + + Town.goToTown(); + + command = "go"; + + break; + } + } + + break; + } + } + + delay(100); + } +} diff --git a/d2bs/kolbot/threads/ToolsThread.js b/d2bs/kolbot/threads/ToolsThread.js new file mode 100644 index 000000000..bef539a2b --- /dev/null +++ b/d2bs/kolbot/threads/ToolsThread.js @@ -0,0 +1,492 @@ +/* eslint-disable max-len */ +/** +* @filename ToolsThread.js +* @author kolton, theBGuy +* @desc several tools to help the player - potion use, chicken, Diablo clone stop, map reveal, quit with player +* +*/ +js_strict(true); +include("critical.js"); // required + +// globals needed for core gameplay +includeCoreLibs(); +include("core/Common/Tools.js"); + +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +let Overrides = require("../libs/modules/Override"); + +new Overrides.Override(Attack, Attack.getNearestMonster, function (orignal) { + let monster = orignal({ skipBlocked: false, skipImmune: false }); + return (monster ? " to " + monster.name : ""); +}).apply(); + +function main () { + // getUnit test + getUnit(-1) === null && console.warn("getUnit bug detected"); + + let ironGolem, debugInfo = { area: 0, currScript: "no entry" }; + let [quitFlag, antiIdle, townChicken] = [false, false, false]; + let quitListDelayTime; + let idleTick = 0; + let canQuit = true; + + console.log("ÿc3Start ToolsThread script"); + D2Bot.init(); + Config.init(false); + Pickit.init(false); + Attack.init(); + Storage.Init(); + CraftingSystem.buildLists(); + Runewords.init(); + Cubing.init(); + + for (let i = 0; i < 5; i += 1) { + Common.Toolsthread.timerLastDrink[i] = 0; + } + + // Reset core chicken + me.chickenhp = -1; + me.chickenmp = -1; + + // General functions + Common.Toolsthread.pauseScripts = [ + "default.dbj", + "threads/autobuildthread.js", + "threads/antihostile.js", + "threads/party.js", + "threads/rushthread.js" + ]; + Common.Toolsthread.stopScripts = [ + "default.dbj", + "threads/autobuildthread.js", + "threads/antihostile.js", + "threads/party.js", + "threads/rushthread.js", + "libs\\\\modules\\workers\\guard.js" // why? + ]; + + // Event functions + const keyEvent = function (key) { + switch (key) { + case sdk.keys.PauseBreak: // pause running threads + Common.Toolsthread.togglePause(townChicken); + + break; + case sdk.keys.Delete: // quit current game + Common.Toolsthread.exit(); + + break; + case sdk.keys.End: // stop profile and log character + MuleLogger.logChar(); + delay(rand(Time.seconds(Config.QuitListDelay[0]), Time.seconds(Config.QuitListDelay[1]))); + D2Bot.printToConsole(me.profile + " - end run " + me.gamename); + D2Bot.stop(me.profile, true); + + break; + case sdk.keys.Insert: // reveal level + me.overhead("Revealing " + getAreaName(me.area)); + revealLevel(true); + + break; + case sdk.keys.NumpadPlus: // log stats + showConsole(); + + console.log("ÿc8My stats :: " + Common.Toolsthread.getStatsString(me)); + let merc = me.getMerc(); + !!merc && console.log("ÿc8Merc stats :: " + Common.Toolsthread.getStatsString(merc)); + + break; + case sdk.keys.Numpad5: // force automule check + if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo")) { + if (AutoMule.getMuleItems().length > 0) { + console.log("ÿc2Mule triggered"); + scriptBroadcast("mule"); + Common.Toolsthread.exit(); + } else { + me.overhead("No items to mule."); + } + } else { + me.overhead("Profile not enabled for muling."); + } + + break; + case sdk.keys.Numpad6: // log character to char viewer + MuleLogger.logChar(); + me.overhead("Logged char: " + me.name); + + break; + case sdk.keys.NumpadDash: // log our items to item log ? should this try to get nearest player? Isn't that what it was meant for + { + // check if we are hovering the mouse over somebody + let selectedUnit = Game.getSelectedUnit(); + if (selectedUnit && selectedUnit.isPlayer) { + me.overhead("logging " + selectedUnit.name); + // the unit is a valid player lets log thier stuff...muhahaha + Misc.spy(selectedUnit.name); + } else { + me.overhead("logging my stuff"); + // just log ourselves + Misc.spy(me.name); + } + } + + break; + case sdk.keys.NumpadDecimal: // dump item info + { + let itemString = ""; + let generalString = ""; + let itemToCheck = Game.getSelectedUnit(); + + if (!!itemToCheck) { + Cubing.update(); + Runewords.buildLists(); + CraftingSystem.buildLists(); + + let pResult = Pickit.checkItem(itemToCheck); + let pString = "ÿc4Pickit: ÿc0" + pResult.result + " ÿc7Line: ÿc0" + pResult.line + "\n"; + let nResult = NTIP.CheckItem(itemToCheck, false, true); + let nString = "ÿc4NTIP.CheckItem: ÿc0" + nResult.result + " ÿc7Line: ÿc0" + nResult.line + "\n"; + + itemString = ( + "ÿc4ItemName: ÿc0" + itemToCheck.prettyPrint + + "\nÿc4ItemType: ÿc0" + itemToCheck.itemType + + "| ÿc4Classid: ÿc0" + itemToCheck.classid + + "| ÿc4Quality: ÿc0" + itemToCheck.quality + + "| ÿc4Gid: ÿc0" + itemToCheck.gid + + "\nÿc4ItemMode: ÿc0" + itemToCheck.mode + + "| ÿc4Location: ÿc0" + itemToCheck.location + + "| ÿc4Bodylocation: ÿc0" + itemToCheck.bodylocation + ); + generalString = pString + nString + + "\nÿc4Cubing Item: ÿc0" + Cubing.keepItem(itemToCheck) + + " | ÿc4Runeword Item: ÿc0" + Runewords.keepItem(itemToCheck) + + " | ÿc4Crafting Item: ÿc0" + CraftingSystem.keepItem(itemToCheck); + } + + console.log("ÿc2*************Item Info Start*************"); + console.log(itemString); + console.log("ÿc2Systems Info Start"); + console.log(generalString); + console.log("ÿc1****************Info End****************"); + } + + break; + case sdk.keys.Numpad9: // get nearest preset unit id + console.log(Common.Toolsthread.getNearestPreset()); + + break; + case sdk.keys.NumpadStar: // precast + Precast.doPrecast(true); + + break; + case sdk.keys.NumpadSlash: // re-load default + console.log("ÿc8ToolsThread :: " + sdk.colors.Red + "Stopping threads and waiting 5 seconds to restart"); + Common.Toolsthread.stopDefault() && delay(Time.seconds(5)); + console.log("Starting default.dbj"); + load("default.dbj"); + + break; + } + }; + + const gameEvent = function (mode, param1, param2, name1, name2) { + switch (mode) { + case 0x00: // "%Name1(%Name2) dropped due to time out." + case 0x01: // "%Name1(%Name2) dropped due to errors." + case 0x03: // "%Name1(%Name2) left our world. Diablo's minions weaken." + Config.DebugMode.Stack && mode === 0 && D2Bot.printToConsole(name1 + " timed out, check their logs"); + + if (Config.QuitList.includes(name1) || Config.QuitList.some(str => String.isEqual(str, "all"))) { + console.log(name1 + (mode === 0 ? " timed out" : " left")); + + if (typeof quitListDelayTime === "undefined" && Config.QuitListDelay.length > 0) { + let [min, max] = Config.QuitListDelay.sort((a, b) => a - b).map(s => Time.seconds(s)); + + quitListDelayTime = getTickCount() + rand(min, max); + } else { + quitListDelayTime = getTickCount(); + } + + quitFlag = true; + } + + if (Config.AntiHostile) { + scriptBroadcast("remove " + name1); + } + + break; + case 0x06: // "%Name1 was Slain by %Name2" + if (Config.AntiHostile && param2 === 0x00 && name2 === me.name) { + scriptBroadcast("mugshot " + name1); + } + + break; + case 0x07: // "%Player has declared hostility towards you." + if (Config.AntiHostile && param2 === 0x03) { + scriptBroadcast("findHostiles"); + } + + if (Config.PublicMode) { + scriptBroadcast("hostileCheck"); + } + + break; + case 0x11: // "%Param1 Stones of Jordan Sold to Merchants" + if (Config.DCloneQuit === 2) { + D2Bot.printToConsole("SoJ sold in game. Leaving."); + + quitFlag = true; + + break; + } + + if (Config.SoJWaitTime && me.expansion) { + !!me.realm && D2Bot.printToConsole(param1 + " Stones of Jordan Sold to Merchants on IP " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold); + Messaging.sendToScript("default.dbj", "soj"); + } + + break; + case 0x12: // "Diablo Walks the Earth" + if (Config.DCloneQuit > 0) { + D2Bot.printToConsole("Diablo walked in game. Leaving."); + + quitFlag = true; + + break; + } + + if (Config.StopOnDClone && me.expansion) { + D2Bot.printToConsole("Diablo Walks the Earth", sdk.colors.D2Bot.DarkGold); + Common.Toolsthread.cloneWalked = true; + + Common.Toolsthread.togglePause(); + Town.goToTown(); + showConsole(); + console.log("ÿc4Diablo Walks the Earth"); + + me.maxgametime = 0; + + if (Config.KillDclone && load("threads/clonekilla.js")) { + break; + } else { + antiIdle = true; + } + } + + break; + case 0x0f: // "Realm going down in %Param1 minutes." + { + let realmDownStr = getLocaleString(sdk.locale.text.RealmGoingDownInXMinutes).replace("%d", param1); + D2Bot.printToConsole(realmDownStr, sdk.colors.D2Bot.DarkGold); + } + break; + } + }; + + const scriptEvent = function (msg) { + if (!!msg && typeof msg === "string") { + switch (msg) { + case "toggleQuitlist": + canQuit = !canQuit; + + return; + case "quit": + console.debug("Quiting"); + quitFlag = true; + Common.Toolsthread.stopDefault(); + + return; + case "reload": + console.log("ÿc8ToolsThread :: " + sdk.colors.Red + "Stopping threads and waiting 5 seconds to restart"); + Common.Toolsthread.stopDefault() && delay(Time.seconds(5)); + console.log("Starting default.dbj"); + load("default.dbj"); + + return; + case "datadump": + console.log("ÿc8Systems Data Dump: ÿc2Start"); + console.log("ÿc8Cubing"); + console.log("ÿc9Cubing Valid Itemsÿc0", Cubing.validIngredients); + console.log("ÿc9Cubing Needed Itemsÿc0", Cubing.neededIngredients); + console.log("ÿc8Runeword"); + console.log("ÿc9Runeword Valid Itemsÿc0", Runewords.validGids); + console.log("ÿc9Runeword Needed Itemsÿc0", Runewords.needList); + console.log("ÿc8Systems Data Dump: ÿc1****************Info End****************"); + + return; + // ignore common scriptBroadcast messages that aren't relevent to this thread + case "mule": + case "muleTorch": + case "muleAnni": + case "torch": + case "crafting": + case "getMuleMode": + case "pingquit": + case "townCheck": + return; + default: + let obj; + + try { + obj = JSON.parse(msg); + } catch (e) { + return; + } + + if (obj) { + obj.hasOwnProperty("currScript") && (debugInfo.currScript = obj.currScript); + obj.hasOwnProperty("lastAction") && (debugInfo.lastAction = obj.lastAction); + + DataFile.updateStats("debugInfo", JSON.stringify(debugInfo)); + } + return; + } + } + }; + + // Cache variables to prevent a bug where d2bs loses the reference to Config object + Config = copyObj(Config); + let tick = getTickCount(); + + addEventListener("keyup", keyEvent); + addEventListener("gameevent", gameEvent); + addEventListener("scriptmsg", scriptEvent); + + Config.QuitListMode > 0 && Common.Toolsthread.initQuitList(); + !Array.isArray(Config.QuitList) && (Config.QuitList = [Config.QuitList]); // make it an array for simpler checks + // console.debug("QuitList", Config.QuitList); + + // Start + while (true) { + try { + if (me.gameReady && !me.inTown) { + if (Config.UseHP > 0 && me.hpPercent < Config.UseHP) { + Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Health); + } + if (Config.UseRejuvHP > 0 && me.hpPercent < Config.UseRejuvHP) { + Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); + } + + /** + * Feel like potting and lifechicken should actually be seperate threads + */ + if (Config.LifeChicken > 0 && me.hpPercent <= Config.LifeChicken && !me.inTown) { + // takes a moment sometimes for townchicken to actually get to town so re-check that we aren't in town before quitting + if (!me.inTown) { + D2Bot.printToConsole( + "Life Chicken (" + me.hp + "/" + me.hpmax + ")" + Attack.getNearestMonster() + + " in " + getAreaName(me.area) + ". Ping: " + me.ping, + sdk.colors.D2Bot.Red + ); + + break; + } + } + + if (Config.UseMP > 0 && me.mpPercent < Config.UseMP) { + Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Mana); + } + if (Config.UseRejuvMP > 0 && me.mpPercent < Config.UseRejuvMP) { + Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); + } + + if (Config.ManaChicken > 0 && me.mpPercent <= Config.ManaChicken) { + D2Bot.printToConsole("Mana Chicken: (" + me.mp + "/" + me.mpmax + ") in " + getAreaName(me.area), sdk.colors.D2Bot.Red); + + break; + } + + if (Config.IronGolemChicken > 0 && me.necromancer) { + if (!ironGolem || copyUnit(ironGolem).x === undefined) { + ironGolem = Common.Toolsthread.getIronGolem(); + } + + if (ironGolem) { + // ironGolem.hpmax is bugged with BO + if (ironGolem.hp <= Math.floor(128 * Config.IronGolemChicken / 100)) { + D2Bot.printToConsole("Iron Golem Chicken in " + getAreaName(me.area), sdk.colors.D2Bot.Red); + + break; + } + } + } + + if (Config.UseMerc) { + let merc = me.getMerc(); + if (!!merc) { + let mercHP = getMercHP(); + + if (mercHP > 0 && merc.mode !== sdk.monsters.mode.Dead) { + if (mercHP < Config.MercChicken) { + D2Bot.printToConsole("Merc Chicken in " + getAreaName(me.area), sdk.colors.D2Bot.Red); + + break; + } + + if (mercHP < Config.UseMercHP) { + Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercHealth); + } + if (mercHP < Config.UseMercRejuv) { + Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercRejuv); + } + } + } + } + + if (Config.ViperCheck && getTickCount() - tick >= 250) { + Common.Toolsthread.checkVipers() && (quitFlag = true); + + tick = getTickCount(); + } + + Common.Toolsthread.checkPing(true) && (quitFlag = true); + } + + if (antiIdle) { + tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(Config.DCloneWaitTime)) { + if (getTickCount() - idleTick > 0) { + Packet.questRefresh(); + idleTick += rand(1200, 1500) * 1000; + let timeStr = Time.format(idleTick - getTickCount()); + me.overhead("Diablo Walks the Earth! - Next packet in: (" + timeStr + ")"); + console.log("Sent anti-idle packet, next refresh in: (" + timeStr + ")"); + } + } + } + } catch (e) { + Misc.errorReport(e, "ToolsThread"); + + quitFlag = true; + } + + if (debugInfo.area !== getAreaName(me.area)) { + debugInfo.area = getAreaName(me.area); + DataFile.updateStats("debugInfo", JSON.stringify(debugInfo)); + } + + if (quitFlag && canQuit) { + if (typeof quitListDelayTime !== "undefined" && getTickCount() < quitListDelayTime) { + // should there be a check if we are in the middle of interacting with an npc? Seems quitting game in the middle causes crashes + // only ancedotal evidence currently + if (getTickCount() < quitListDelayTime - 4000) { + me.overhead("Quitting in " + Math.round((quitListDelayTime - getTickCount()) / 1000) + " Seconds"); + } + } else { + Common.Toolsthread.checkPing(false); // In case of quitlist triggering first + + break; + } + } + + delay(20); + } + Common.Toolsthread.exit(); + + return true; +} diff --git a/d2bs/kolbot/tools/AntiHostile.js b/d2bs/kolbot/tools/AntiHostile.js deleted file mode 100644 index b2f350bc1..000000000 --- a/d2bs/kolbot/tools/AntiHostile.js +++ /dev/null @@ -1,384 +0,0 @@ -/** -* @filename AntiHostile.js -* @author kolton -* @desc handle hostile threats -* -*/ -js_strict(true); - -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("Gambling.js"); -include("CraftingSystem.js"); -include("common/util.js"); - -includeCommonLibs(); - -function main() { - // Variables and functions - let player, attackCount, prevPos, check, missile, outside; - let charClass = ["Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin"]; - let hostiles = []; - - // AntiHostile gets game event info from ToolsThread - this.scriptEvent = function (msg) { - switch (msg.split(" ")[0]) { - case "remove": // Remove a hostile player that left the game - if (hostiles.indexOf(msg.split(" ")[1]) > -1) { - hostiles.splice(hostiles.indexOf(msg.split(" ")[1]), 1); - } - - break; - case "mugshot": // Take a screenshot and log the kill - D2Bot.printToConsole(msg.split(" ")[1] + " has been neutralized.", sdk.colors.D2Bot.Blue); - hideConsole(); - delay(500); - takeScreenshot(); - - break; - } - }; - - // Find all hostile players and add their names to the 'hostiles' list - this.findHostiles = function () { - let party = getParty(); - - if (party) { - do { - if (party.name !== me.name && getPlayerFlag(me.gid, party.gid, 8) && hostiles.indexOf(party.name) === -1) { - D2Bot.printToConsole(party.name + " (Level " + party.level + " " + charClass[party.classid] + ")" + " has declared hostility.", sdk.colors.D2Bot.Orange); - hostiles.push(party.name); - } - } while (party.getNext()); - } - - return true; - }; - - // Pause default so actions don't conflict - this.pause = function () { - let script = getScript("default.dbj"); - - if (script && script.running) { - print("ÿc1Pausing."); - script.pause(); - } - }; - - // Resume default - this.resume = function () { - let script = getScript("default.dbj"); - - if (script && !script.running) { - print("ÿc2Resuming."); - script.resume(); - } - }; - - // Find hostile player Units - this.findPlayer = function () { - for (let i = 0; i < hostiles.length; i += 1) { - let player = Game.getPlayer(hostiles[i]); - - if (player) { - do { - if (!player.dead && getPlayerFlag(me.gid, player.gid, 8) && !player.inTown && !me.inTown) { - return player; - } - } while (player.getNext()); - } - } - - return false; - }; - - // Find a missile type - this.findMissile = function (owner, id, range) { - range === undefined && (range = 999); - - let missile = Game.getMissile(id); - if (!missile) return false; - - do { - if (missile.owner === owner.gid && getDistance(owner, missile) < range) { - return missile; - } - } while (missile.getNext()); - - return false; - }; - - this.checkSummons = function (player) { - if (!player) return false; - let name = player.name; - let unit = Game.getMonster(); - - if (unit) { - do { - // Revives and spirit wolves - if (unit.getParent() && unit.getParent().name === name && (unit.getState(sdk.states.Revive) || unit.classid === sdk.monsters.Wolf2)) { - return true; - } - } while (unit.getNext()); - } - - return false; - }; - - // Init config and attacks - D2Bot.init(); - Config.init(); - Attack.init(); - Storage.Init(); - - // Use PVP range for attacks - Skill.usePvpRange = true; - - // Attack sequence adjustments - this only affects the AntiHostile thread - if (Skill.canUse(sdk.skills.MindBlast) && [sdk.skills.FireBlast, sdk.skills.ShockWeb].includes(Config.AttackSkill[1])) { - Config.AttackSkill[1] = sdk.skills.MindBlast; - ClassAttack.trapRange = 40; - } - - // A simple but fast player dodge function - this.moveAway = function (unit, range) { - let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); - let angles = [0, 45, -45, 90, -90, 135, -135, 180]; - - for (let i = 0; i < angles.length; i += 1) { - // Avoid the position where the player actually tries to move to - let coordx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * range + unit.x); // unit.targetx - let coordy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * range + unit.y); // unit.targety - - if (Attack.validSpot(coordx, coordy)) { - return Pather.moveTo(coordx, coordy); - } - } - - return false; - }; - - addEventListener("scriptmsg", this.scriptEvent); - print("ÿc2Anti-Hostile thread loaded."); - - // Main Loop - while (true) { - if (me.gameReady) { - // Scan for hostiles - this.findHostiles(); - - if (hostiles.length > 0 && (Config.HostileAction === 0 || (Config.HostileAction === 1 && me.inTown))) { - if (Config.TownOnHostile) { - print("ÿc1Hostility detected, going to town."); - this.pause(); - - if (!me.inTown) { - outside = true; - } - - try { - Town.goToTown(); - } catch (e) { - print(e + " Failed to go to town. Quitting."); - scriptBroadcast("quit"); // quit if failed to go to town - } - - while (hostiles.length > 0) { - delay(500); - } - - if (outside) { - outside = false; - Pather.usePortal(null, me.name); - } - - this.resume(); - } else { - scriptBroadcast("quit"); - } - - delay(500); - - continue; - } - - // Mode 3 - Spam entrance (still experimental) - if (Config.HostileAction === 3 && hostiles.length > 0 && me.inArea(sdk.areas.ThroneofDestruction)) { - switch (me.classid) { - case sdk.player.class.Sorceress: - prevPos = {x: me.x, y: me.y}; - this.pause(); - Pather.moveTo(15103, 5247); - - while (!this.findPlayer() && hostiles.length > 0) { - if (!me.skillDelay) { - Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), 15099, 5237); - } else { - if (Config.AttackSkill[2] > -1) { - Skill.cast(Config.AttackSkill[2], Skill.getHand(Config.AttackSkill[2]), 15099, 5237); - } else { - while (me.skillDelay) { - delay(40); - } - } - } - } - - break; - case sdk.player.class.Druid: - // Don't bother if it's not a tornado druid - if (Config.AttackSkill[1] !== sdk.skills.Tornado) { - break; - } - - prevPos = {x: me.x, y: me.y}; - this.pause(); - Pather.moveTo(15103, 5247); - - while (!this.findPlayer() && hostiles.length > 0) { - // Tornado path is a function of target x. Slight randomization will make sure it can't always miss - Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), 15099 + rand(-2, 2), 5237); - } - - break; - case sdk.player.class.Assassin: - prevPos = {x: me.x, y: me.y}; - this.pause(); - Pather.moveTo(15103, 5247); - - while (!this.findPlayer() && hostiles.length > 0) { - if (Config.UseTraps) { - check = ClassAttack.checkTraps({x: 15099, y: 5242, classid: 544}); - - if (check) { - ClassAttack.placeTraps({x: 15099, y: 5242, classid: 544}, 5); - } - } - - Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), 15099, 5237); - - while (me.skillDelay) { - delay(40); - } - } - - break; - } - } - - // Player left, return to old position - if (!hostiles.length && prevPos) { - Pather.moveTo(prevPos.x, prevPos.y); - this.resume(); - - // Reset position - prevPos = false; - } - - player = this.findPlayer(); - - if (player) { - // Mode 1 - Quit if hostile player is nearby - if (Config.HostileAction === 1) { - if (Config.TownOnHostile) { - print("ÿc1Hostile player nearby, going to town."); - this.pause(); - - if (!me.inTown) { - outside = true; - } - - try { - Town.goToTown(); - } catch (e) { - print(e + " Failed to go to town. Quitting."); - scriptBroadcast("quit"); // quit if failed to go to town - } - - while (hostiles.length > 0) { - delay(500); - } - - if (outside) { - outside = false; - Pather.usePortal(null, me.name); - } - - this.resume(); - } else { - scriptBroadcast("quit"); - } - - delay(500); - - continue; - } - - // Kill the hostile player - if (!prevPos) { - prevPos = {x: me.x, y: me.y}; - } - - this.pause(); - - Config.UseMerc = false; // Don't go revive the merc mid-fight - attackCount = 0; - - while (attackCount < 100) { - // Invalidated Unit (out of getUnit range) or player in town - if (!copyUnit(player).x || player.inTown || me.mode === sdk.player.mode.Dead) { - break; - } - - ClassAttack.doAttack(player, false); - - // Specific attack additions - switch (me.classid) { - case sdk.player.class.Sorceress: - case sdk.player.class.Necromancer: - // Dodge missiles - experimental - missile = Game.getMissile(); - - if (missile) { - do { - if (getPlayerFlag(me.gid, missile.owner, 8) && (getDistance(me, missile) < 15 || (missile.targetx && getDistance(me, missile.targetx, missile.targety) < 15))) { - this.moveAway(missile, Skill.getRange(Config.AttackSkill[1])); - - break; - } - } while (missile.getNext()); - } - - // Move away if the player is too close or if he tries to move too close (telestomp) - if (Skill.getRange(Config.AttackSkill[1]) > 20 && (getDistance(me, player) < 30 || (player.targetx && getDistance(me, player.targetx, player.targety) < 15))) { - this.moveAway(player, Skill.getRange(Config.AttackSkill[1])); - } - - break; - case sdk.player.class.Paladin: - // Smite summoners - if (Config.AttackSkill[1] === sdk.skills.BlessedHammer && Skill.canUse(sdk.skills.Smite)) { - if ([sdk.player.class.Necromancer, sdk.player.class.Druid].includes(player.classid) && getDistance(me, player) < 4 && this.checkSummons(player)) { - Skill.cast(sdk.skills.Smite, sdk.skills.hand.Left, player); - } - } - - break; - } - - attackCount += 1; - - if (player.dead) { - break; - } - } - - Pather.moveTo(prevPos.x, prevPos.y); - this.resume(); - } - } - - delay(200); - } -} diff --git a/d2bs/kolbot/tools/AntiIdle.js b/d2bs/kolbot/tools/AntiIdle.js deleted file mode 100644 index 9f526a1ec..000000000 --- a/d2bs/kolbot/tools/AntiIdle.js +++ /dev/null @@ -1,24 +0,0 @@ -/** -* @filename AntiIdle.js -* @author theBGuy -* @desc Prevent Idle diconnect -* -*/ - -include("common/Prototypes.js"); -include("common/Misc.js"); - -function main () { - console.log("ÿc3Start AntiIdle"); - let idleTick = Time.seconds(getTickCount() + rand(1200, 1500)); - - while (true) { - if (me.ingame && me.gameReady) { - if (getTickCount() - idleTick > 0) { - Packet.questRefresh(); - idleTick += Time.seconds(rand(1200, 1500)); - console.log("Sent anti-idle packet, next refresh in: (" + Time.format(idleTick - getTickCount()) + ")"); - } - } - } -} diff --git a/d2bs/kolbot/tools/AreaWatcher.js b/d2bs/kolbot/tools/AreaWatcher.js deleted file mode 100644 index 6837da895..000000000 --- a/d2bs/kolbot/tools/AreaWatcher.js +++ /dev/null @@ -1,26 +0,0 @@ -/** -* @filename AreaWatcher.js -* @author dzik, theBGuy -* @desc suicide walk prevention -* -*/ -include("OOG.js"); -include("common/Prototypes.js"); - -function main() { - let _default = getScript("default.dbj"); - print("ÿc3Start AreaWatcher"); - - while (true) { - try { - if (me.gameReady && me.ingame && !me.inTown) { - !!_default && _default.stop(); - D2Bot.printToConsole("Saved from suicide walk!"); - !!_default && !_default.running ? quit() : D2Bot.restart(); - } - } catch (e) { - console.warn("AreaWatcher failed somewhere. ", e); - } - delay(1000); - } -} diff --git a/d2bs/kolbot/tools/AutoBuildThread.js b/d2bs/kolbot/tools/AutoBuildThread.js deleted file mode 100644 index 3e58ec9ff..000000000 --- a/d2bs/kolbot/tools/AutoBuildThread.js +++ /dev/null @@ -1,265 +0,0 @@ -/** -* @filename AutoBuildThread.js -* @author alogwe -* @desc Helper thread for AutoBuild.js that monitors changes in character level -* -*/ -js_strict(true); - -!isIncluded("common/AutoSkill.js") && include("common/AutoSkill.js"); -!isIncluded("common/AutoStat.js") && include("common/AutoStat.js"); -!isIncluded("common/Config.js") && include("common/Config.js"); -!isIncluded("common/Cubing.js") && include("common/Cubing.js"); -!isIncluded("common/Prototypes.js") && include("common/Prototypes.js"); -!isIncluded("common/Runewords.js") && include("common/Runewords.js"); -!isIncluded("common/Town.js") && include("common/Town.js"); - -Config.init(); // includes libs/common/AutoBuild.js - -const debug = !!Config.AutoBuild.DebugMode; -const SPEND_POINTS = true; // For testing, it actually allows skill and stat point spending. -const STAT_ID_TO_NAME = [ - getLocaleString(sdk.locale.text.Strength), - getLocaleString(sdk.locale.text.Energy), - getLocaleString(sdk.locale.text.Dexterity), - getLocaleString(sdk.locale.text.Vitality) -]; -let prevLevel = me.charlvl; - -// Will check if value exists in an Array -Array.prototype.contains = (val) => this.indexOf(val) > -1; - -function skillInValidRange (id) { - switch (me.classid) { - case sdk.player.class.Amazon: - return sdk.skills.MagicArrow <= id && id <= sdk.skills.LightningFury; - case sdk.player.class.Sorceress: - return sdk.skills.FireBolt <= id && id <= sdk.skills.ColdMastery; - case sdk.player.class.Necromancer: - return sdk.skills.AmplifyDamage <= id && id <= sdk.skills.Revive; - case sdk.player.class.Paladin: - return sdk.skills.Sacrifice <= id && id <= sdk.skills.Salvation; - case sdk.player.class.Barbarian: - return sdk.skills.Bash <= id && id <= sdk.skills.BattleCommand; - case sdk.player.class.Druid: - return sdk.skills.Raven <= id && id <= sdk.skills.Hurricane; - case sdk.player.class.Assassin: - return sdk.skills.FireBlast <= id && id <= sdk.skills.PhoenixStrike; - default: - return false; - } -} - -const gainedLevels = () => me.charlvl - prevLevel; - -function canSpendPoints () { - let unusedStatPoints = me.getStat(sdk.stats.StatPts); - let haveUnusedStatpoints = unusedStatPoints >= 5; // We spend 5 stat points per level up - let unusedSkillPoints = me.getStat(sdk.stats.NewSkills); - let haveUnusedSkillpoints = unusedSkillPoints >= 1; // We spend 1 skill point per level up - debug && AutoBuild.print("Stat points:", unusedStatPoints, " Skill points:", unusedSkillPoints); - return haveUnusedStatpoints && haveUnusedSkillpoints; -} - -function spendStatPoint (id) { - let unusedStatPoints = me.getStat(sdk.stats.StatPts); - if (SPEND_POINTS) { - useStatPoint(id); - AutoBuild.print("useStatPoint(" + id + "): " + STAT_ID_TO_NAME[id]); - } else { - AutoBuild.print("Fake useStatPoint(" + id + "): " + STAT_ID_TO_NAME[id]); - } - delay(100); // TODO: How long should we wait... if at all? - return (unusedStatPoints - me.getStat(sdk.stats.StatPts) === 1); // Check if we spent one point -} - -// TODO: What do we do if it fails? report/ignore/continue? -function spendStatPoints () { - let stats = AutoBuildTemplate[me.charlvl].StatPoints; - let errorMessage = "\nInvalid stat point set in build template " + getTemplateFilename() + " at level " + me.charlvl; - let spentEveryPoint = true; - let unusedStatPoints = me.getStat(sdk.stats.StatPts); - let len = stats.length; - - if (Config.AutoStat.Enabled) { - return spentEveryPoint; - } - - if (len > unusedStatPoints) { - len = unusedStatPoints; - AutoBuild.print("Warning: Number of stats specified in your build template at level " + me.charlvl + " exceeds the available unused stat points" - + "\nOnly the first " + len + " stats " + stats.slice(0, len).join(", ") + " will be added"); - } - - // We silently ignore stats set to -1 - for (let i = 0; i < len; i++) { - let id = stats[i]; - let statIsValid = (typeof id === "number") && (sdk.stats.Strength <= id && id <= sdk.stats.Vitality); - - if (id === -1) { - continue; - } else if (statIsValid) { - let preStatValue = me.getStat(id); - let pointSpent = spendStatPoint(id); - if (SPEND_POINTS) { - if (!pointSpent) { - spentEveryPoint = false; - AutoBuild.print("Attempt to spend point " + (i + 1) + " in " + STAT_ID_TO_NAME[id] + " may have failed!"); - } else if (debug) { - AutoBuild.print("Stat (" + (i + 1) + "/" + len + ") Increased " + STAT_ID_TO_NAME[id] + " from " + preStatValue + " to " + me.getStat(id)); - } - } - } else { - throw new Error("Stat id must be one of the following:\n0:" + STAT_ID_TO_NAME[0] - + ",\t1:" + STAT_ID_TO_NAME[1] + ",\t2:" + STAT_ID_TO_NAME[2] + ",\t3:" + STAT_ID_TO_NAME[3] + errorMessage); - } - } - - return spentEveryPoint; -} - -function getTemplateFilename () { - let buildType = Config.AutoBuild.Template; - let templateFilename = "config/Builds/" + sdk.player.class.nameOf(me.classid) + "." + buildType + ".js"; - return templateFilename; -} - -function getRequiredSkills (id) { - function searchSkillTree (id) { - let results = []; - let skillTreeRight = getBaseStat("skills", id, sdk.stats.PreviousSkillRight); - let skillTreeMiddle = getBaseStat("skills", id, sdk.stats.PreviousSkillMiddle); - let skillTreeLeft = getBaseStat("skills", id, sdk.stats.PreviousSkillLeft); - - results.push(skillTreeRight); - results.push(skillTreeMiddle); - results.push(skillTreeLeft); - - for (let i = 0; i < results.length; i++) { - let skill = results[i]; - let skillInValidRange = (sdk.skills.Attack < skill && skill <= sdk.skills.PhoenixStrike) && (![sdk.skills.IdentifyScroll, sdk.skills.BookofIdentify, sdk.skills.TownPortalScroll, sdk.skills.BookofTownPortal].contains(skill)); - let hardPointsInSkill = me.getSkill(skill, sdk.skills.subindex.HardPoints); - - if (skillInValidRange && !hardPointsInSkill) { - requirements.push(skill); - searchSkillTree(skill); // search children; - } - } - } - - let requirements = []; - searchSkillTree(id); - const increasing = () => a - b; - return requirements.sort(increasing); -} - -function spendSkillPoint (id) { - let unusedSkillPoints = me.getStat(sdk.stats.NewSkills); - let skillName = getSkillById(id) + " (" + id + ")"; - if (SPEND_POINTS) { - useSkillPoint(id); - AutoBuild.print("useSkillPoint(): " + skillName); - } else { - AutoBuild.print("Fake useSkillPoint(): " + skillName); - } - delay(200); // TODO: How long should we wait... if at all? - return (unusedSkillPoints - me.getStat(sdk.stats.NewSkills) === 1); // Check if we spent one point -} - -function spendSkillPoints () { - let skills = AutoBuildTemplate[me.charlvl].SkillPoints; - let errInvalidSkill = "\nInvalid skill point set in build template " + getTemplateFilename() + " for level " + me.charlvl; - let spentEveryPoint = true; - let unusedSkillPoints = me.getStat(sdk.stats.NewSkills); - let len = skills.length; - - if (Config.AutoSkill.Enabled) { - return spentEveryPoint; - } - - if (len > unusedSkillPoints) { - len = unusedSkillPoints; - AutoBuild.print("Warning: Number of skills specified in your build template at level " + me.charlvl + " exceeds the available unused skill points" + - "\nOnly the first " + len + " skills " + skills.slice(0, len).join(", ") + " will be added"); - } - - // We silently ignore skills set to -1 - for (let i = 0; i < len; i++) { - let id = skills[i]; - - if (id === -1) { - continue; - } else if (!skillInValidRange(id)) { - throw new Error("Skill id " + id + " is not a skill for your character class" + errInvalidSkill); - } - - let skillName = getSkillById(id) + " (" + id + ")"; - let requiredSkills = getRequiredSkills(id); - if (requiredSkills.length > 0) { - throw new Error("You need prerequisite skills " + requiredSkills.join(", ") + " before adding " + skillName + errInvalidSkill); - } - - let requiredLevel = getBaseStat("skills", id, sdk.stats.MinimumRequiredLevel); - if (me.charlvl < requiredLevel) { - throw new Error("You need to be at least level " + requiredLevel + " before you get " + skillName + errInvalidSkill); - } - - let pointSpent = spendSkillPoint(id); - - if (SPEND_POINTS) { - if (!pointSpent) { - spentEveryPoint = false; - AutoBuild.print("Attempt to spend skill point " + (i + 1) + " in " + skillName + " may have failed!"); - } else if (debug) { - let actualSkillLevel = me.getSkill(id, sdk.skills.subindex.SoftPoints); - AutoBuild.print("Skill (" + (i + 1) + "/" + len + ") Increased " + skillName + " by one (level: ", actualSkillLevel + ")"); - } - } - - delay(200); // TODO: How long should we wait... if at all? - } - - return spentEveryPoint; -} - -/* -* TODO: determine if changes need to be made for -* the case of gaining multiple levels at once so as -* not to bombard the d2bs event system -*/ - -function main () { - try { - AutoBuild.print("Loaded helper thread"); - - while (true) { - let levels = gainedLevels(); - - if (levels > 0 && (canSpendPoints() || Config.AutoSkill.Enabled || Config.AutoStat.Enabled)) { - scriptBroadcast("toggleQuitlist"); - AutoBuild.print("Level up detected (", prevLevel, "-->", me.charlvl, ")"); - spendSkillPoints(); - spendStatPoints(); - Config.AutoSkill.Enabled && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save); - Config.AutoStat.Enabled && AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk); - scriptBroadcast({event: "level up"}); - AutoBuild.applyConfigUpdates(); // scriptBroadcast() won't trigger listener on this thread. - - debug && AutoBuild.print("Incrementing cached character level to", prevLevel + 1); - // prevLevel doesn't get set to me.charlvl because - // we may have gained multiple levels at once - prevLevel += 1; - - scriptBroadcast("toggleQuitlist"); - } - - delay(1e3); - } - } catch (err) { - print("Something broke!"); - print("Error:" + err.toSource()); - print("Stack trace: \n" + err.stack); - - return false; - } -} diff --git a/d2bs/kolbot/tools/CloneKilla.js b/d2bs/kolbot/tools/CloneKilla.js deleted file mode 100644 index 6f1d5bf69..000000000 --- a/d2bs/kolbot/tools/CloneKilla.js +++ /dev/null @@ -1,42 +0,0 @@ -/** -* @filename CloneKilla.js -* @author kolton -* @desc Kill Diablo Clone when he walks in game. Uses Fire Eye location. -* -*/ -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("AutoMule.js"); -include("craftingsystem.js"); -include("Gambling.js"); -include("TorchSystem.js"); -include("MuleLogger.js"); -include("common/util.js"); - -includeCommonLibs(); - -function main() { - D2Bot.init(); - Config.init(); - Pickit.init(); - Attack.init(); - Storage.Init(); - CraftingSystem.buildLists(); - Runewords.init(); - Cubing.init(); - include("bots/KillDclone.js"); - - if (typeof KillDclone === "function") { - try { - D2Bot.printToConsole("Trying to kill DClone.", sdk.colors.D2Bot.DarkGold); - KillDclone.call(); - } catch (e) { - Misc.errorReport(e, "CloneKilla.js"); - } - } - - quit(); - - return true; -} diff --git a/d2bs/kolbot/tools/HeartBeat.js b/d2bs/kolbot/tools/HeartBeat.js deleted file mode 100644 index a5f0d22f8..000000000 --- a/d2bs/kolbot/tools/HeartBeat.js +++ /dev/null @@ -1,56 +0,0 @@ -/** -* @filename HeartBeat.js -* @author kolton -* @desc Keep a link with d2bot#. If it's lost, the d2 window is killed -* -*/ - -function main() { - include("oog.js"); - include("json2.js"); - include("common/misc.js"); - include("common/util.js"); - D2Bot.init(); - print("Heartbeat loaded"); - - function togglePause() { - let script = getScript(); - - if (script) { - do { - if (script.name.includes(".dbj")) { - if (script.running) { - print("ÿc1Pausing ÿc0" + script.name); - script.pause(); - } else { - print("ÿc2Resuming ÿc0" + script.name); - script.resume(); - } - } - } while (script.getNext()); - } - - return true; - } - - // Event functions - function KeyEvent(key) { - switch (key) { - case sdk.keys.PauseBreak: - if (me.ingame) { - break; - } - - togglePause(); - - break; - } - } - - addEventListener("keyup", KeyEvent); - - while (true) { - D2Bot.heartBeat(); - delay(1000); - } -} diff --git a/d2bs/kolbot/tools/Party.js b/d2bs/kolbot/tools/Party.js deleted file mode 100644 index 602711757..000000000 --- a/d2bs/kolbot/tools/Party.js +++ /dev/null @@ -1,191 +0,0 @@ -/** -* @filename Party.js -* @author kolton -* @desc handle party procedure ingame -* -*/ - -function main() { - include("OOG.js"); - include("json2.js"); - include("common/Config.js"); - include("common/Cubing.js"); - include("common/Runewords.js"); - include("common/misc.js"); - include("common/util.js"); - include("common/Prototypes.js"); - include("common/Town.js"); - - Config.init(); - - let myPartyId, player, shitList, currScript, scriptList; - let classes = ["Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin"]; - let playerLevels = {}; - let partyTick = getTickCount(); - - addEventListener("gameevent", - function (mode, param1, param2, name1, name2) { - let player; - - switch (mode) { - case 0x02: // "%Name1(%Name2) joined our world. Diablo's minions grow stronger." - if (Config.Greetings.length > 0) { - try { - player = getParty(name1); - } catch (e1) { - break; - } - - if (player && player.name !== me.name) { - say(Config.Greetings[rand(0, Config.Greetings.length - 1)].replace("$name", player.name).replace("$level", player.level).replace("$class", classes[player.classid])); - } - } - - break; - case 0x06: // "%Name1 was Slain by %Name2" - if (Config.DeathMessages.length > 0) { - try { - player = getParty(name1); - } catch (e2) { - break; - } - - if (player && player.name !== me.name) { - say(Config.DeathMessages[rand(0, Config.DeathMessages.length - 1)].replace("$name", player.name).replace("$level", player.level).replace("$class", classes[player.classid]).replace("$killer", name2)); - } - } - - break; - } - }); - addEventListener("scriptmsg", - function (msg) { - let obj; - - try { - obj = JSON.parse(msg); - - if (obj && obj.hasOwnProperty("currScript")) { - currScript = obj.currScript; - } - } catch (e3) { - return; - } - }); - - print("ÿc2Party thread loaded. Mode: " + (Config.PublicMode === 2 ? "Accept" : "Invite")); - - if (Config.ShitList || Config.UnpartyShitlisted) { - shitList = ShitList.read(); - - print(shitList.length + " entries in shit list."); - } - - if (Config.PartyAfterScript) { - scriptList = []; - - for (let i in Scripts) { - if (Scripts.hasOwnProperty(i) && !!Scripts[i]) { - scriptList.push(i); - } - } - } - - // Main loop - while (true) { - if (me.gameReady && (!Config.PartyAfterScript || scriptList.indexOf(currScript) > scriptList.indexOf(Config.PartyAfterScript))) { - player = getParty(); - - if (player) { - myPartyId = player.partyid; - - while (player.getNext()) { - switch (Config.PublicMode) { - case 1: // Invite others - case 3: // Invite others but never accept - if (getPlayerFlag(me.gid, player.gid, 8)) { - if (Config.ShitList && shitList.indexOf(player.name) === -1) { - say(player.name + " has been shitlisted."); - shitList.push(player.name); - ShitList.add(player.name); - } - - if (player.partyflag === 4) { - clickParty(player, 2); // cancel invitation - delay(100); - } - - break; - } - - if (Config.ShitList && shitList.indexOf(player.name) > -1) { - break; - } - - if (player.partyflag !== 4 && player.partyflag !== 2 && player.partyid === sdk.party.NoParty) { - clickParty(player, 2); - delay(100); - } - - if (Config.PublicMode === 3) { - break; - } - // eslint-disable-next-line no-fallthrough - case 2: // Accept invites - if (myPartyId === sdk.party.NoParty) { - if (Config.Leader && player.name !== Config.Leader) { - break; - } - - if (player.partyflag === 2 && (getTickCount() - partyTick >= 2000 || Config.FastParty)) { - clickParty(player, 2); - delay(100); - } - } - - break; - } - - if (Config.UnpartyShitlisted) { - // Add new hostile players to temp shitlist, leader should have Config.ShitList set to true to update the permanent list. - if (getPlayerFlag(me.gid, player.gid, 8) && shitList.indexOf(player.name) === -1) { - shitList.push(player.name); - } - - if (shitList.indexOf(player.name) > -1 && myPartyId !== sdk.party.NoParty && player.partyid === myPartyId) { - // Only the one sending invites should say this. - if ([1, 3].indexOf(Config.PublicMode) > -1) { - say(player.name + " is shitlisted. Do not invite them."); - } - - clickParty(player, 3); - delay(100); - } - } - } - } - - if (Config.Congratulations.length > 0) { - player = getParty(); - - if (player) { - do { - if (player.name !== me.name) { - if (!playerLevels[player.name]) { - playerLevels[player.name] = player.level; - } - - if (player.level > playerLevels[player.name]) { - say(Config.Congratulations[rand(0, Config.Congratulations.length - 1)].replace("$name", player.name).replace("$level", player.level).replace("$class", classes[player.classid])); - - playerLevels[player.name] = player.level; - } - } - } while (player.getNext()); - } - } - } - - delay(500); - } -} diff --git a/d2bs/kolbot/tools/RushThread.js b/d2bs/kolbot/tools/RushThread.js deleted file mode 100644 index 0ac714c48..000000000 --- a/d2bs/kolbot/tools/RushThread.js +++ /dev/null @@ -1,1168 +0,0 @@ -/** -* @filename RushThread.js -* @author kolton, theBGuy -* @desc Second half of the Rusher script -* -*/ -js_strict(true); - -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("Gambling.js"); -include("AutoMule.js"); -include("CraftingSystem.js"); -include("TorchSystem.js"); -include("common/util.js"); -includeCommonLibs(); - -let Overrides = require("../modules/Override"); - -let count = 0; -let silentNameTracker = []; -let wpsToGive = Pather.nonTownWpAreas.slice(0).filter(function (area) { - if (area === sdk.areas.HallsofPain) return false; - if (me.classic && area >= sdk.areas.Harrogath) return false; - return true; -}); - -function wpEvent (who, msg) { - if (typeof msg === "string" && msg === "gotwp" || msg === "Failed to get wp") { - count++; - !silentNameTracker.includes(who) && silentNameTracker.push(who); - } -} - -function giveWP () { - let wp = Game.getObject("waypoint"); - let success = false; - if (wp && !me.inTown && wpsToGive.includes(me.area)) { - try { - addEventListener("chatmsg", wpEvent); - let playerCount = Misc.getPartyCount(); - let mobCount = getUnits(sdk.unittype.Monster).filter(mon => mon.distance <= 15 && mon.attackable).length; - mobCount > 0 && Attack.securePosition(me.x, me.y, 15, Time.seconds(30), true); - wp.distance > 5 && Pather.moveToUnit(wp); - Pather.makePortal(); - say("wp"); - let tick = getTickCount(); - while (getTickCount() - tick < Time.minutes(2)) { - let player = Game.getPlayer(); - if (player) { - do { - if (player.name !== me.name && !silentNameTracker.includes(player.name)) { - silentNameTracker.push(player.name); - } - } while (player.getNext()); - } - if (count === playerCount || (silentNameTracker.length === playerCount && Misc.getNearbyPlayerCount() === 0)) { - wpsToGive.remove(me.area); - success = true; - break; - } - delay(50); - } - } catch (e) { - console.error(e); - Config.LocalChat.Enabled && say("Failed to give wp"); - } finally { - removeEventListener("chatmsg", wpEvent); - silentNameTracker = []; - count = 0; - } - return success; - } - - return false; -} - -new Overrides.Override(Pather, Pather.useWaypoint, function(orignal, targetArea, check) { - if (orignal(targetArea, check)) { - return (Config.Rusher.GiveWps && giveWP()) || true; - } else { - print("failed"); - - return false; - } -}).apply(); - -function main () { - let tick; - - this.log = function (msg = "", sayMsg = true) { - console.log(msg); - sayMsg && say(msg); - }; - - this.playerIn = function (area) { - !area && (area = me.area); - - let party = getParty(); - - if (party) { - do { - if (party.name !== me.name && party.area === area) { - return true; - } - } while (party.getNext()); - } - - return false; - }; - - this.bumperCheck = function () { - let bumperLevelReq = [20, 40, 60][me.diff]; - return Misc.checkPartyLevel(bumperLevelReq); - }; - - this.playersInAct = function (act) { - !act && (act = me.act); - - let area = sdk.areas.townOfAct(act); - let party = getParty(); - - if (party) { - do { - if (party.name !== me.name && party.area !== area) { - return false; - } - } while (party.getNext()); - } - - return true; - }; - - this.andariel = function () { - this.log("starting andariel"); - Town.doChores(); - Pather.useWaypoint(sdk.areas.CatacombsLvl2, true) && Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true) - || !Pather.moveTo(22582, 9612)) { - throw new Error("andy failed"); - } - - Pather.makePortal(); - Attack.securePosition(me.x, me.y, 40, 3000, true); - this.log("1"); - - while (!this.playerIn()) { - Pather.moveTo(22582, 9612); - delay(250); - } - - Attack.kill(sdk.monsters.Andariel); - this.log("2"); - Pather.moveTo(22582, 9612); - - while (this.playerIn()) { - delay(250); - } - - Pather.usePortal(null, me.name); - this.log("a2"); - Town.goToTown(2); - - while (!this.playersInAct(2)) { - delay(250); - } - - return true; - }; - - this.cube = function () { - if (me.normal) { - this.log("starting cube"); - Pather.useWaypoint(sdk.areas.HallsoftheDeadLvl2, true); - Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.HallsoftheDeadLvl3, true) - || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest)) { - throw new Error("cube failed"); - } - - Pather.makePortal(); - Attack.securePosition(me.x, me.y, 30, 3000, true); - this.log("1"); - - while (!this.playerIn()) { - delay(100); - } - - while (this.playerIn()) { - delay(100); - } - - Pather.usePortal(null, me.name); - } - - return true; - }; - - this.amulet = function () { - this.log("starting amulet"); - Town.doChores(); - Pather.useWaypoint(sdk.areas.LostCity, true) && Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2], true) - || !Pather.moveTo(15044, 14045)) { - throw new Error("amulet failed"); - } - - Pather.makePortal(); - Attack.securePosition(me.x, me.y, 25, 3000, me.hell, me.hell); - - this.log("1"); - - while (!this.playerIn()) { - delay(100); - } - - while (this.playerIn()) { - delay(100); - } - - Pather.usePortal(null, me.name); - - return true; - }; - - this.staff = function () { - this.log("starting staff"); - Town.doChores(); - Pather.useWaypoint(sdk.areas.FarOasis, true) && Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3], true) - || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest)) { - throw new Error("staff failed"); - } - - Pather.makePortal(); - Attack.securePosition(me.x, me.y, 30, 3000, true); - this.log("1"); - - while (!this.playerIn()) { - delay(100); - } - - while (this.playerIn()) { - delay(100); - } - - Pather.usePortal(null, me.name); - - return true; - }; - - this.summoner = function () { - // right up 25449 5081 (25431, 5011) - // left up 25081 5446 (25011, 5446) - // right down 25830 5447 (25866, 5431) - // left down 25447 5822 (25431, 5861) - - this.log("starting summoner"); - Town.doChores(); - Pather.useWaypoint(sdk.areas.ArcaneSanctuary, true) && Precast.doPrecast(true); - - let preset = Game.getPresetObject(sdk.areas.ArcaneSanctuary, sdk.quest.chest.Journal); - let spot = {}; - - switch (preset.roomx * 5 + preset.x) { - case 25011: - spot = {x: 25081, y: 5446}; - break; - case 25866: - spot = {x: 25830, y: 5447}; - break; - case 25431: - switch (preset.roomy * 5 + preset.y) { - case 5011: - spot = {x: 25449, y: 5081}; - break; - case 5861: - spot = {x: 25447, y: 5822}; - break; - } - - break; - } - - if (!Pather.moveToUnit(spot)) { - throw new Error("summoner failed"); - } - - Pather.makePortal(); - Attack.securePosition(me.x, me.y, 25, 3000); - this.log("1"); - - while (!this.playerIn()) { - Pather.moveToUnit(spot); - Attack.securePosition(me.x, me.y, 25, 500); - delay(250); - } - - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal); - Attack.kill(sdk.monsters.Summoner); - this.log("2"); - - while (this.playerIn()) { - delay(100); - } - - Pickit.pickItems(); - Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.Journal); - - let redPortal = Game.getObject(sdk.objects.RedPortal); - - if (!redPortal || !this.usePortal(null, null, redPortal)) { - if (!Misc.poll(() => { - let journal = Game.getObject(sdk.quest.chest.Journal); - - if (journal && journal.interact()) { - delay(1000); - me.cancel(); - } - - redPortal = Pather.getPortal(sdk.areas.CanyonofMagic); - - return (redPortal && Pather.usePortal(null, null, redPortal)); - })) throw new Error("summoner failed"); - } - - return true; - }; - - this.duriel = function () { - this.log("starting duriel"); - - if (me.inTown) { - Town.doChores(); - Pather.useWaypoint(sdk.areas.CanyonofMagic, true); - } else { - giveWP(); - } - - Precast.doPrecast(true); - - if (!Pather.moveToExit(getRoom().correcttomb, true) - || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricStaffHolder)) { - throw new Error("duriel failed"); - } - - Pather.makePortal(); - Attack.securePosition(me.x, me.y, 30, 3000, true, me.hell); - this.log("1"); - - while (!this.playerIn()) { - delay(100); - } - - while (this.playerIn()) { - delay(100); - } - - while (!Game.getObject(sdk.objects.PortaltoDurielsLair)) { - delay(500); - } - - Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair); - Attack.kill(sdk.monsters.Duriel); - Pickit.pickItems(); - - Pather.teleport = false; - - Pather.moveTo(22579, 15706); - - Pather.teleport = true; - - Pather.moveTo(22577, 15649, 10); - Pather.moveTo(22577, 15609, 10); - Pather.makePortal(); - this.log("1"); - - while (!this.playerIn()) { - delay(100); - } - - if (!Pather.usePortal(null, me.name)) { - Town.goToTown(); - } - - Pather.useWaypoint(sdk.areas.PalaceCellarLvl1); - Pather.moveToExit([sdk.areas.HaremLvl2, sdk.areas.HaremLvl1], true); - Pather.moveTo(10022, 5047); - this.log("a3"); - Town.goToTown(3); - Town.doChores(); - - while (!this.playersInAct(3)) { - delay(250); - } - - return true; - }; - - // re-write to prevent fail to complete quest due to killing council from to far away - this.travincal = function () { - this.log("starting travincal"); - Town.doChores(); - Pather.useWaypoint(sdk.areas.Travincal, true) && Precast.doPrecast(true); - - let coords = [me.x, me.y]; - - Pather.moveTo(coords[0] + 23, coords[1] - 102); - Pather.makePortal(); - Attack.securePosition(me.x, me.y, 40, 3000); - this.log("1"); - - while (!this.playerIn()) { - delay(250); - } - - Pather.moveTo(coords[0] + 30, coords[1] - 134); - Pather.moveTo(coords[0] + 86, coords[1] - 130); - Pather.moveTo(coords[0] + 71, coords[1] - 94); - Attack.securePosition(me.x, me.y, 40, 3000); - - Pather.moveTo(coords[0] + 23, coords[1] - 102); - Pather.makePortal(); - this.log("2"); - Pather.usePortal(null, me.name); - - return true; - }; - - this.mephisto = function () { - this.log("starting mephisto"); - - Town.doChores(); - Pather.useWaypoint(sdk.areas.DuranceofHateLvl2, true) && Precast.doPrecast(true); - Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true) && Pather.moveTo(17692, 8023) && Pather.makePortal(); - delay(2000); - this.log("1"); - - while (!this.playerIn()) { - delay(250); - } - - Pather.moveTo(17591, 8070); - Attack.kill(sdk.monsters.Mephisto); - Pickit.pickItems(); - Pather.moveTo(17692, 8023) && Pather.makePortal(); - this.log("2"); - - while (this.playerIn()) { - delay(250); - } - - Pather.moveTo(17591, 8070) && Attack.securePosition(me.x, me.y, 40, 3000); - - let hydra = Game.getMonster(getLocaleString(sdk.locale.monsters.Hydra)); - - if (hydra) { - do { - while (!hydra.dead && hydra.hp > 0) { - delay(500); - } - } while (hydra.getNext()); - } - - Pather.makePortal(); - Pather.moveTo(17581, 8070); - this.log("1"); - - while (!this.playerIn()) { - delay(250); - } - - this.log("a4"); - - while (!this.playersInAct(4)) { - delay(250); - } - - delay(2000); - Pather.usePortal(null); - - return true; - }; - - this.diablo = function () { - this.log("starting diablo"); - - function inviteIn () { - Pather.moveTo(7763, 5267) && Pather.makePortal(); - Pather.moveTo(7727, 5267); - this.log("1"); - - while (!this.playerIn()) { - delay(250); - } - - return true; - } - - Town.doChores(); - Pather.useWaypoint(sdk.areas.RiverofFlame); - Precast.doPrecast(true); - if (!Pather.moveToExit(sdk.areas.ChaosSanctuary, true) && !Pather.moveTo(7790, 5544)) throw new Error("Failed to move to Chaos Sanctuary"); - - Common.Diablo.initLayout(); - Config.Diablo.Fast = true; - Config.Diablo.SealLeader = false; - - try { - Common.Diablo.runSeals(Config.Diablo.SealOrder); - print("Attempting to find Diablo"); - inviteIn() && Common.Diablo.diabloPrep(); - } catch (error) { - print("Diablo wasn't found. Checking seals."); - Common.Diablo.runSeals(Config.Diablo.SealOrder); - inviteIn() && Common.Diablo.diabloPrep(); - } - - Attack.kill(sdk.monsters.Diablo); - this.log("2"); - - if (me.expansion) { - this.log("a5"); - - while (!this.playersInAct(5)) { - delay(250); - } - } - - Pickit.pickItems(); - !Pather.usePortal(null, me.name) && Town.goToTown(); - - return true; - }; - - this.ancients = function () { - if (me.hell && !Config.Rusher.HellAncients) { - if (!Config.Rusher.GiveWps) { - this.log("Hell rush complete~"); - delay(500); - quit(); - } - - return false; - } - - if (!this.bumperCheck()) { - if (!Config.Rusher.GiveWps) { - this.log("No eligible bumpers detected. Rush complete~"); - delay(500); - quit(); - } - - return false; - } - - this.log("starting ancients"); - - Town.doChores(); - Pather.useWaypoint(sdk.areas.AncientsWay, true) && Precast.doPrecast(true); - - if (!Pather.moveToExit(sdk.areas.ArreatSummit, true)) { - throw new Error("Failed to go to Ancients way."); - } - - Pather.moveTo(10089, 12622); - Pather.makePortal(); - this.log("3"); - - while (!this.playerIn()) { - delay(250); - } - - Pather.moveTo(10048, 12628); - Common.Ancients.touchAltar(); - Common.Ancients.startAncients(); - - Pather.moveTo(10089, 12622); - me.cancel(); - Pather.makePortal(); - - while (this.playerIn()) { - delay(100); - } - - !Pather.usePortal(null, me.name) && Town.goToTown(); - - return true; - }; - - this.baal = function () { - if (me.hell) { - if (!Config.Rusher.GiveWps) { - this.log("Baal not done in Hell ~Hell rush complete~"); - delay(500); - quit(); - } - wpsToGive.remove(sdk.areas.WorldstoneLvl2); - - return false; - } - - if (!this.bumperCheck()) { - if (!Config.Rusher.GiveWps) { - this.log("No eligible bumpers detected. ~Rush complete~"); - delay(500); - quit(); - } - wpsToGive.remove(sdk.areas.WorldstoneLvl2); - - return false; - } - - this.log("starting baal"); - - if (me.inTown) { - Town.doChores(); - Pather.useWaypoint(sdk.areas.WorldstoneLvl2) && Precast.doPrecast(true); - - if (!Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], true)) { - throw new Error("Failed to move to Throne of Destruction."); - } - } - - Pather.moveTo(15113, 5040); - Attack.clear(15); - Common.Baal.clearThrone(); - - if (!Common.Baal.clearWaves()) { - throw new Error("Couldn't clear baal waves"); - } - - Common.Baal.clearThrone(); - me.checkForMobs({range: 30}) && this.clearWaves(); // ensure waves are actually done - Pather.moveTo(15090, 5008); - delay(5000); - Precast.doPrecast(true); - Misc.poll(() => !Game.getMonster(sdk.monsters.ThroneBaal), Time.minutes(3), 1000); - - let portal = Game.getObject(sdk.objects.WorldstonePortal); - - if (portal) { - Pather.usePortal(null, null, portal); - } else { - throw new Error("Couldn't find portal."); - } - - Pather.moveTo(15213, 5908); - Pather.makePortal(); - Pather.moveTo(15170, 5950); - delay(1000); - this.log("3"); - - while (!this.playerIn()) { - delay(250); - } - - Pather.moveTo(15134, 5923); - Attack.kill(sdk.monsters.Baal); - Pickit.pickItems(); - - return true; - }; - - this.clearArea = function (area) { - Pather.journeyTo(area); - Attack.clearLevel(0); - this.log("Done clearing area: " + area); - }; - - // Quests - this.cain = function () { - if (!Config.Rusher.Cain) return true; - - this.log("starting cain"); - Town.doChores(); - Pather.useWaypoint(sdk.areas.DarkWood, true) && Precast.doPrecast(true); - - if (!Pather.moveToPreset(sdk.areas.DarkWood, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) { - throw new Error("Failed to move to Tree of Inifuss"); - } - - let tree = Game.getObject(sdk.quest.chest.InifussTree); - !!tree && tree.distance > 5 && Pather.moveToUnit(tree); - Attack.securePosition(me.x, me.y, 40, 3000, true); - !!tree && tree.distance > 5 && Pather.moveToUnit(tree); - Pather.makePortal(); - this.log("1"); - tick = getTickCount(); - - while (getTickCount() - tick < Time.minutes(2)) { - if (tree.mode) { - break; - } - Attack.securePosition(me.x, me.y, 20, 1000); - } - - Pather.usePortal(1) || Town.goToTown(); - Pather.useWaypoint(sdk.areas.StonyField, true); - Precast.doPrecast(true); - Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 10, 10, false, true); - Attack.securePosition(me.x, me.y, 40, 3000, true); - Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Object, sdk.quest.chest.StoneAlpha, null, null, true); - Pather.makePortal(); - this.log("1"); - - tick = getTickCount(); - - while (getTickCount() - tick < Time.minutes(2)) { - if (Pather.usePortal(sdk.areas.Tristram)) { - break; - } - Attack.securePosition(me.x, me.y, 35, 1000); - } - - if (me.inArea(sdk.areas.Tristram)) { - Pather.moveTo(me.x, me.y + 6); - let gibbet = Game.getObject(sdk.quest.chest.CainsJail); - - if (gibbet && !gibbet.mode) { - if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.CainsJail, 0, 0, true, true)) { - throw new Error("Failed to move to Cain's Jail"); - } - - Attack.securePosition(gibbet.x, gibbet.y, 20, 3000); - Pather.makePortal(); - this.log("1"); - - tick = getTickCount(); - - while (getTickCount() - tick < Time.minutes(2)) { - if (gibbet.mode) { - break; - } - Attack.securePosition(me.x, me.y, 10, 1000); - } - } - } - - return true; - }; - - this.radament = function () { - if (!Config.Rusher.Radament) return false; - - this.log("starting radament"); - - let moveIntoPos = function (unit, range) { - let coords = []; - let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); - let angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 105, -105, 120, -120, 135, -135, 150, -150, 180]; - - for (let i = 0; i < angles.length; i += 1) { - let coordx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * range + unit.x); - let coordy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * range + unit.y); - - try { - if (!(getCollision(unit.area, coordx, coordy) & 0x1)) { - coords.push({ - x: coordx, - y: coordy - }); - } - } catch (e) { - continue; - } - } - - if (coords.length > 0) { - coords.sort(Sort.units); - - return Pather.moveToUnit(coords[0]); - } - - return false; - }; - - Pather.useWaypoint(sdk.areas.A2SewersLvl2, true) && Precast.doPrecast(false); - Pather.moveToExit(sdk.areas.A2SewersLvl3, true); - - let radaPreset = Game.getPresetObject(sdk.areas.A2SewersLvl3, sdk.quest.chest.HoradricScrollChest); - let radaCoords = { - area: sdk.areas.A2SewersLvl3, - x: radaPreset.roomx * 5 + radaPreset.x, - y: radaPreset.roomy * 5 + radaPreset.y - }; - - moveIntoPos(radaCoords, 50); - let rada = Misc.poll(() => Game.getMonster(sdk.monsters.Radament), 1500, 500); - - rada ? moveIntoPos(rada, 60) : print("radament unit not found"); - Attack.securePosition(me.x, me.y, 35, 3000); - Pather.makePortal(); - this.log("1"); - - while (!this.playerIn()) { - delay(200); - } - - Attack.kill(sdk.monsters.Radament); - - let returnSpot = { - x: me.x, - y: me.y - }; - - this.log("2"); - Pickit.pickItems(); - Attack.securePosition(me.x, me.y, 30, 3000); - - while (this.playerIn()) { - delay(200); - } - - Pather.moveToUnit(returnSpot); - Pather.makePortal(); - this.log("all in"); - - while (!this.playerIn()) { - delay(200); - } - - Misc.poll(() => !Game.getItem(sdk.quest.item.BookofSkill), 30000, 1000); - - while (this.playerIn()) { - delay(200); - } - - Pather.usePortal(null, null); - - return true; - }; - - this.lamesen = function () { - if (!Config.Rusher.LamEsen) return false; - - this.log("starting lamesen"); - - if (!Town.goToTown() || !Pather.useWaypoint(sdk.areas.KurastBazaar, true)) { - throw new Error("Lam Essen quest failed"); - } - - Precast.doPrecast(false); - - if (!Pather.moveToExit(sdk.areas.RuinedTemple, true) - || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.LamEsensTomeHolder)) { - throw new Error("Lam Essen quest failed"); - } - - Attack.securePosition(me.x, me.y, 30, 2000); - Pather.makePortal(); - this.log("1"); - - while (!this.playerIn()) { - delay(200); - } - - while (this.playerIn()) { - delay(200); - } - - Pather.usePortal(null, null); - - return true; - }; - - this.izual = function () { - if (!Config.Rusher.Izual) return false; - - this.log("starting izual"); - - let moveIntoPos = function (unit, range) { - let coords = []; - let angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI); - let angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 105, -105, 120, -120, 135, -135, 150, -150, 180]; - - for (let i = 0; i < angles.length; i += 1) { - let coordx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * range + unit.x); - let coordy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * range + unit.y); - - try { - if (!(getCollision(unit.area, coordx, coordy) & 0x1)) { - coords.push({ - x: coordx, - y: coordy - }); - } - } catch (e) { - continue; - } - } - - if (coords.length > 0) { - coords.sort(Sort.units); - - return Pather.moveToUnit(coords[0]); - } - - return false; - }; - - Pather.useWaypoint(sdk.areas.CityoftheDamned, true) && Precast.doPrecast(false); - Pather.moveToExit(sdk.areas.PlainsofDespair, true); - - let izualPreset = Game.getPresetMonster(sdk.areas.PlainsofDespair, sdk.monsters.Izual); - let izualCoords = { - area: sdk.areas.PlainsofDespair, - x: izualPreset.roomx * 5 + izualPreset.x, - y: izualPreset.roomy * 5 + izualPreset.y - }; - - moveIntoPos(izualCoords, 50); - let izual = Misc.poll(() => Game.getMonster(sdk.monsters.Izual), 1500, 500); - - izual ? moveIntoPos(izual, 60) : print("izual unit not found"); - - let returnSpot = { - x: me.x, - y: me.y - }; - - Attack.securePosition(me.x, me.y, 30, 3000); - Pather.makePortal(); - this.log("1"); - - while (!this.playerIn()) { - delay(200); - } - - Attack.kill(sdk.monsters.Izual); - Pickit.pickItems(); - this.log("2"); - Pather.moveToUnit(returnSpot); - - while (this.playerIn()) { - delay(200); - } - - Pather.usePortal(null, null); - - return true; - }; - - this.shenk = function () { - if (!Config.Rusher.Shenk) return false; - - this.log("starting shenk"); - - Pather.useWaypoint(sdk.areas.FrigidHighlands, true) && Precast.doPrecast(false); - Pather.moveTo(3846, 5120); - Attack.securePosition(me.x, me.y, 30, 3000); - Pather.makePortal(); - this.log("1"); - - while (!this.playerIn()) { - delay(200); - } - - Attack.kill(getLocaleString(sdk.locale.monsters.ShenktheOverseer)); - Pickit.pickItems(); - Pather.moveTo(3846, 5120); - this.log("2"); - - while (this.playerIn()) { - delay(200); - } - - Pather.usePortal(null, null); - - return true; - }; - - this.anya = function () { - if (!Config.Rusher.Anya) return false; - - !me.inTown && Town.goToTown(); - - this.log("starting anya"); - - if (!Pather.useWaypoint(sdk.areas.CrystalizedPassage, true)) { - throw new Error("Anya quest failed"); - } - - Precast.doPrecast(false); - - if (!Pather.moveToExit(sdk.areas.FrozenRiver, true) - || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform)) { - throw new Error("Anya quest failed"); - } - - Attack.securePosition(me.x, me.y, 30, 2000); - - let anya = Game.getObject(sdk.objects.FrozenAnya); - - if (anya) { - Pather.moveToUnit(anya); - // Rusher should be able to interact so quester can get the potion without entering - Packet.entityInteract(anya); - delay(1000 + me.ping); - me.cancel(); - } - - Pather.makePortal(); - this.log("1"); - - while (!this.playerIn()) { - delay(200); - } - - Misc.poll(() => !Game.getObject(sdk.objects.FrozenAnya), 30000, 1000); - - this.log("2"); // Mainly for non-questers to know when to get the scroll of resistance - - while (this.playerIn()) { - delay(200); - } - - Pather.usePortal(null, null); - - return true; - }; - - this.givewps = function () { - if (!Config.Rusher.GiveWps) return false; - - let wpsLeft = wpsToGive.slice(0); - console.log(JSON.stringify(wpsLeft)); - - wpsLeft.forEach(function (wp) { - Town.checkScrolls(sdk.items.TomeofTownPortal) <= 5 && (Pather.useWaypoint(sdk.areas.townOf(me.area)) || Town.goToTown()) && Town.doChores(); - Pather.useWaypoint(wp); - }); - - return true; - }; - - console.log(sdk.colors.LightGold + "Loading RushThread"); - - let command = ""; - let current = 0; - let commandsplit = ""; - let check = -1; - let sequence = [ - "cain", "andariel", "radament", "cube", "amulet", "staff", "summoner", "duriel", "lamesen", - "travincal", "mephisto", "izual", "diablo", "shenk", "anya", "ancients", "baal", "givewps" - ]; - - this.scriptEvent = function (msg) { - if (typeof msg === "string") { - command = msg; - } - }; - - addEventListener("scriptmsg", this.scriptEvent); - - // START - Config.init(false); - Pickit.init(false); - Attack.init(); - Storage.Init(); - CraftingSystem.buildLists(); - Runewords.init(); - Cubing.init(); - - Config.MFLeader = false; - - while (true) { - if (command) { - switch (command) { - case "go": - // End run if entire sequence is done or if Config.Rusher.LastRun is done - if (current >= sequence.length || (Config.Rusher.LastRun && current > sequence.indexOf(Config.Rusher.LastRun))) { - delay(3000); - this.log("bye ~"); - console.log("Current sequence length: " + current + " sequence length: " + sequence.length); - - while (Misc.getPlayerCount() > 1) { - delay(1000); - } - - scriptBroadcast("quit"); - - break; - } - - Town.doChores(); - - try { - this[sequence[current]](); - } catch (sequenceError) { - this.log(sequenceError.message); - this.log("2"); - Town.goToTown(); - } - - current += 1; - command = "go"; - - break; - default: - if (typeof command === "string") { - if (command.split(" ")[0] !== undefined && command.split(" ")[0] === "skiptoact") { - if (!isNaN(parseInt(command.split(" ")[1], 10))) { - switch (parseInt(command.split(" ")[1], 10)) { - case 2: - current = sequence.indexOf("andariel") + 1; - Town.goToTown(2); - - break; - case 3: - current = sequence.indexOf("duriel") + 1; - Town.goToTown(3); - - break; - case 4: - current = sequence.indexOf("mephisto") + 1; - Town.goToTown(4); - - break; - case 5: - current = sequence.indexOf("diablo") + 1; - Town.goToTown(5); - - break; - } - } - - command = ""; - } else if (command.split(" ")[0] !== undefined && command.split(" ")[0] === "clear") { - this.clearArea(Number(command.split(" ")[1])); - Town.goToTown(); - - command = "go"; - } else if (command.split(" ")[0] !== undefined && command.split(" ")[0] === "highestquest") { - command.split(" ")[1] !== undefined && (commandsplit = command.split(" ")[1]); - check = sequence.findIndex(i => i === commandsplit); - check > -1 && (current = check + 1); - - command = ""; - } else { - for (let i = 0; i < sequence.length; i += 1) { - if (command && sequence[i].match(command, "gi")) { - current = i; - - break; - } - } - - Town.goToTown(); - - command = "go"; - - break; - } - } - - break; - } - } - - delay(100); - } -} diff --git a/d2bs/kolbot/tools/ToolsThread.js b/d2bs/kolbot/tools/ToolsThread.js deleted file mode 100644 index 09bd37bfc..000000000 --- a/d2bs/kolbot/tools/ToolsThread.js +++ /dev/null @@ -1,429 +0,0 @@ -/** -* @filename ToolsThread.js -* @author kolton -* @desc several tools to help the player - potion use, chicken, Diablo clone stop, map reveal, quit with player -* -*/ -js_strict(true); - -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("AutoMule.js"); -include("Gambling.js"); -include("CraftingSystem.js"); -include("TorchSystem.js"); -include("MuleLogger.js"); -include("common/util.js"); - -includeCommonLibs(); - -let Overrides = require("../modules/Override"); - -new Overrides.Override(Attack, Attack.getNearestMonster, function (orignal) { - let monster = orignal({skipBlocked: false, skipImmune: false}); - return (monster ? " to " + monster.name : ""); -}).apply(); - -function main() { - let ironGolem, debugInfo = {area: 0, currScript: "no entry"}; - let quitFlag = false; - let quitListDelayTime; - let antiIdle = false; - let idleTick = 0; - let canQuit = true; - - console.log("ÿc3Start ToolsThread script"); - D2Bot.init(); - Config.init(false); - Pickit.init(false); - Attack.init(); - Storage.Init(); - CraftingSystem.buildLists(); - Runewords.init(); - Cubing.init(); - - for (let i = 0; i < 5; i += 1) { - Common.Toolsthread.timerLastDrink[i] = 0; - } - - // Reset core chicken - me.chickenhp = -1; - me.chickenmp = -1; - - // General functions - Common.Toolsthread.pauseScripts = [ - "default.dbj", "tools/townchicken.js", "tools/autobuildthread.js", "tools/antihostile.js", - "tools/party.js", "tools/rushthread.js" - ]; - Common.Toolsthread.stopScripts = [ - "default.dbj", "tools/townchicken.js", "tools/autobuildthread.js", "tools/antihostile.js", - "tools/party.js", "tools/rushthread.js", "libs//modules/guard.js" - ]; - - // Event functions - this.keyEvent = function (key) { - switch (key) { - case sdk.keys.PauseBreak: // pause default.dbj - Common.Toolsthread.togglePause(); - - break; - case sdk.keys.Delete: // quit current game - Common.Toolsthread.exit(); - - break; - case sdk.keys.End: // stop profile and log character - MuleLogger.logChar(); - delay(rand(Time.seconds(Config.QuitListDelay[0]), Time.seconds(Config.QuitListDelay[1]))); - D2Bot.printToConsole(me.profile + " - end run " + me.gamename); - D2Bot.stop(me.profile, true); - - break; - case sdk.keys.Insert: // reveal level - me.overhead("Revealing " + Pather.getAreaName(me.area)); - revealLevel(true); - - break; - case sdk.keys.NumpadPlus: // log stats - showConsole(); - - console.log("ÿc8My stats :: " + Common.Toolsthread.getStatsString(me)); - let merc = me.getMerc(); - !!merc && console.log("ÿc8Merc stats :: " + Common.Toolsthread.getStatsString(merc)); - - break; - case sdk.keys.Numpad5: // force automule check - if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo")) { - if (AutoMule.getMuleItems().length > 0) { - print("ÿc2Mule triggered"); - scriptBroadcast("mule"); - Common.Toolsthread.exit(); - } else { - me.overhead("No items to mule."); - } - } else { - me.overhead("Profile not enabled for muling."); - } - - break; - case sdk.keys.Numpad6: // log character to char viewer - MuleLogger.logChar(); - me.overhead("Logged char: " + me.name); - - break; - case sdk.keys.NumpadDash: // log our items to item log ? should this try to get nearest player? Isn't that what it was meant for - { - // check if we are hovering the mouse over somebody - let selectedUnit = Game.getSelectedUnit(); - if (selectedUnit && selectedUnit.isPlayer) { - me.overhead("logging " + selectedUnit.name); - // the unit is a valid player lets log thier stuff...muhahaha - Misc.spy(selectedUnit.name); - } else { - me.overhead("logging my stuff"); - // just log ourselves - Misc.spy(me.name); - } - } - - break; - case sdk.keys.NumpadDecimal: // dump item info - { - let itemString = ""; - let generalString = ""; - let itemToCheck = Game.getSelectedUnit(); - - if (!!itemToCheck) { - itemString = "ÿc4ItemName: ÿc0" + itemToCheck.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "") - + "\nÿc4ItemType: ÿc0" + itemToCheck.itemType + "| ÿc4Classid: ÿc0" + itemToCheck.classid + "| ÿc4Quality: ÿc0" + itemToCheck.quality + "| ÿc4Gid: ÿc0" + itemToCheck.gid - + "\nÿc4ItemMode: ÿc0" + itemToCheck.mode + "| ÿc4Location: ÿc0" + itemToCheck.location + "| ÿc4Bodylocation: ÿc0" + itemToCheck.bodylocation; - generalString = "ÿc4Pickit: ÿc0" + Pickit.checkItem(itemToCheck).result + " | ÿc4NTIP.CheckItem: ÿc0" + NTIP.CheckItem(itemToCheck, false, true).result - + "\nÿc4Cubing Item: ÿc0" + Cubing.keepItem(itemToCheck) + " | ÿc4Runeword Item: ÿc0" + Runewords.keepItem(itemToCheck) + " | ÿc4Crafting Item: ÿc0" + CraftingSystem.keepItem(itemToCheck); - } - - console.log("ÿc2*************Item Info Start*************"); - console.log(itemString); - console.log("ÿc2Systems Info Start"); - console.log(generalString); - console.log("ÿc1****************Info End****************"); - } - - break; - case sdk.keys.Numpad9: // get nearest preset unit id - console.log(Common.Toolsthread.getNearestPreset()); - - break; - case sdk.keys.NumpadStar: // precast - Precast.doPrecast(true); - - break; - case sdk.keys.NumpadSlash: // re-load default - console.log("ÿc8ToolsThread :: " + sdk.colors.Red + "Stopping threads and waiting 5 seconds to restart"); - Common.Toolsthread.stopDefault() && delay(Time.seconds(5)); - console.log("Starting default.dbj"); - load("default.dbj"); - - break; - } - }; - - this.gameEvent = function (mode, param1, param2, name1, name2) { - switch (mode) { - case 0x00: // "%Name1(%Name2) dropped due to time out." - case 0x01: // "%Name1(%Name2) dropped due to errors." - case 0x03: // "%Name1(%Name2) left our world. Diablo's minions weaken." - Config.DebugMode && mode === 0 && D2Bot.printToConsole(name1 + " timed out, check their logs"); - - if ((typeof Config.QuitList === "string" && Config.QuitList.toLowerCase() === "any") - || (Config.QuitList instanceof Array && Config.QuitList.includes(name1))) { - print(name1 + (mode === 0 ? " timed out" : " left")); - - if (typeof Config.QuitListDelay !== "undefined" && typeof quitListDelayTime === "undefined" && Config.QuitListDelay.length > 0) { - Config.QuitListDelay.sort((a, b) => a - b); - quitListDelayTime = getTickCount() + rand(Time.seconds(Config.QuitListDelay[0]), Time.seconds(Config.QuitListDelay[1])); - } else { - quitListDelayTime = getTickCount(); - } - - quitFlag = true; - } - - if (Config.AntiHostile) { - scriptBroadcast("remove " + name1); - } - - break; - case 0x06: // "%Name1 was Slain by %Name2" - if (Config.AntiHostile && param2 === 0x00 && name2 === me.name) { - scriptBroadcast("mugshot " + name1); - } - - break; - case 0x07: - if (Config.AntiHostile && param2 === 0x03) { // "%Player has declared hostility towards you." - scriptBroadcast("findHostiles"); - } - - break; - case 0x11: // "%Param1 Stones of Jordan Sold to Merchants" - if (Config.DCloneQuit === 2) { - D2Bot.printToConsole("SoJ sold in game. Leaving."); - - quitFlag = true; - - break; - } - - if (Config.SoJWaitTime && me.expansion) { - !!me.realm && D2Bot.printToConsole(param1 + " Stones of Jordan Sold to Merchants on IP " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold); - Messaging.sendToScript("default.dbj", "soj"); - } - - break; - case 0x12: // "Diablo Walks the Earth" - if (Config.DCloneQuit > 0) { - D2Bot.printToConsole("Diablo walked in game. Leaving."); - - quitFlag = true; - - break; - } - - if (Config.StopOnDClone && me.expansion) { - D2Bot.printToConsole("Diablo Walks the Earth", sdk.colors.D2Bot.DarkGold); - Common.Toolsthread.cloneWalked = true; - - Common.Toolsthread.togglePause(); - Town.goToTown(); - showConsole(); - print("ÿc4Diablo Walks the Earth"); - - me.maxgametime = 0; - - if (Config.KillDclone && load("tools/clonekilla.js")) { - break; - } else { - antiIdle = true; - } - } - - break; - } - }; - - this.scriptEvent = function (msg) { - if (!!msg && typeof msg === "string") { - switch (msg) { - case "toggleQuitlist": - canQuit = !canQuit; - - break; - case "quit": - quitFlag = true; - - break; - case "datadump": - console.log("ÿc8Systems Data Dump: ÿc2Start"); - console.log("ÿc8Cubing"); - console.log("ÿc9Cubing Valid Itemsÿc0", Cubing.validIngredients); - console.log("ÿc9Cubing Needed Itemsÿc0", Cubing.neededIngredients); - console.log("ÿc8Runeword"); - console.log("ÿc9Runeword Valid Itemsÿc0", Runewords.validGids); - console.log("ÿc9Runeword Needed Itemsÿc0", Runewords.needList); - console.log("ÿc8Systems Data Dump: ÿc1****************Info End****************"); - - break; - // ignore common scriptBroadcast messages that aren't relevent to this thread - case "mule": - case "muleTorch": - case "muleAnni": - case "torch": - case "crafting": - case "getMuleMode": - case "pingquit": - case "townCheck": - break; - default: - let obj; - - try { - obj = JSON.parse(msg); - } catch (e) { - return; - } - - if (obj) { - obj.hasOwnProperty("currScript") && (debugInfo.currScript = obj.currScript); - obj.hasOwnProperty("lastAction") && (debugInfo.lastAction = obj.lastAction); - - DataFile.updateStats("debugInfo", JSON.stringify(debugInfo)); - } - - break; - } - } - }; - - // Cache variables to prevent a bug where d2bs loses the reference to Config object - Config = Misc.copy(Config); - let tick = getTickCount(); - - addEventListener("keyup", this.keyEvent); - addEventListener("gameevent", this.gameEvent); - addEventListener("scriptmsg", this.scriptEvent); - - // Load Fastmod - patched - // Packet.changeStat(105, Config.FCR); - // Packet.changeStat(99, Config.FHR); - // Packet.changeStat(102, Config.FBR); - // Packet.changeStat(93, Config.IAS); - - Config.QuitListMode > 0 && Common.Toolsthread.initQuitList(); - - // Start - while (true) { - try { - if (me.gameReady && !me.inTown) { - Config.UseHP > 0 && me.hpPercent < Config.UseHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Health); - Config.UseRejuvHP > 0 && me.hpPercent < Config.UseRejuvHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); - - if (Config.LifeChicken > 0 && me.hpPercent <= Config.LifeChicken) { - // takes a moment sometimes for townchicken to actually get to town so re-check that we aren't in town before quitting - if (!me.inTown) { - D2Bot.printToConsole("Life Chicken (" + me.hp + "/" + me.hpmax + ")" + Attack.getNearestMonster() + " in " + Pather.getAreaName(me.area) + ". Ping: " + me.ping, sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - } - - Config.UseMP > 0 && me.mpPercent < Config.UseMP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Mana); - Config.UseRejuvMP > 0 && me.mpPercent < Config.UseRejuvMP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.Rejuv); - - if (Config.ManaChicken > 0 && me.mpPercent <= Config.ManaChicken) { - D2Bot.printToConsole("Mana Chicken: (" + me.mp + "/" + me.mpmax + ") in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - - if (Config.IronGolemChicken > 0 && me.necromancer) { - if (!ironGolem || copyUnit(ironGolem).x === undefined) { - ironGolem = Common.Toolsthread.getIronGolem(); - } - - if (ironGolem) { - // ironGolem.hpmax is bugged with BO - if (ironGolem.hp <= Math.floor(128 * Config.IronGolemChicken / 100)) { - D2Bot.printToConsole("Irom Golem Chicken in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - } - } - - if (Config.UseMerc) { - let merc = me.getMerc(); - if (!!merc) { - let mercHP = getMercHP(); - - if (mercHP > 0 && merc.mode !== sdk.monsters.mode.Dead) { - if (mercHP < Config.MercChicken) { - D2Bot.printToConsole("Merc Chicken in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red); - Common.Toolsthread.exit(true); - - break; - } - - mercHP < Config.UseMercHP && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercHealth); - mercHP < Config.UseMercRejuv && Common.Toolsthread.drinkPotion(Common.Toolsthread.pots.MercRejuv); - } - } - } - - if (Config.ViperCheck && getTickCount() - tick >= 250) { - Common.Toolsthread.checkVipers() && (quitFlag = true); - - tick = getTickCount(); - } - - Common.Toolsthread.checkPing(true) && (quitFlag = true); - } - - if (antiIdle) { - tick = getTickCount(); - - while (getTickCount() - tick < Time.minutes(Config.DCloneWaitTime)) { - if (getTickCount() - idleTick > 0) { - Packet.questRefresh(); - idleTick += rand(1200, 1500) * 1000; - let timeStr = Time.format(idleTick - getTickCount()); - me.overhead("Diablo Walks the Earth! - Next packet in: (" + timeStr + ")"); - print("Sent anti-idle packet, next refresh in: (" + timeStr + ")"); - } - } - } - } catch (e) { - Misc.errorReport(e, "ToolsThread"); - - quitFlag = true; - } - - if (quitFlag && canQuit && (typeof quitListDelayTime === "undefined" || getTickCount() >= quitListDelayTime)) { - Common.Toolsthread.checkPing(false); // In case of quitlist triggering first - Common.Toolsthread.exit(); - - break; - } - - if (debugInfo.area !== Pather.getAreaName(me.area)) { - debugInfo.area = Pather.getAreaName(me.area); - DataFile.updateStats("debugInfo", JSON.stringify(debugInfo)); - } - - delay(20); - } - - return true; -} diff --git a/d2bs/kolbot/tools/TownChicken.js b/d2bs/kolbot/tools/TownChicken.js deleted file mode 100644 index e7b73b41f..000000000 --- a/d2bs/kolbot/tools/TownChicken.js +++ /dev/null @@ -1,111 +0,0 @@ -/** -* @filename TownChicken.js -* @author kolton -* @desc handle town chicken -* -*/ -js_strict(true); - -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("Gambling.js"); -include("CraftingSystem.js"); -include("common/util.js"); - -includeCommonLibs(); - -function main() { - let townCheck = false; - - this.togglePause = function () { - let scripts = ["default.dbj", "tools/antihostile.js", "tools/rushthread.js", "tools/CloneKilla.js"]; - - for (let i = 0; i < scripts.length; i += 1) { - let script = getScript(scripts[i]); - - if (script) { - if (script.running) { - scripts[i] === "default.dbj" && print("ÿc1Pausing."); - script.pause(); - } else { - if (scripts[i] === "default.dbj") { - // resume only if clonekilla isn't running - if (!getScript("tools/clonekilla.js")) { - console.log("ÿc2Resuming."); - script.resume(); - } - } else { - script.resume(); - } - } - } - } - - return true; - }; - - addEventListener("scriptmsg", - function (msg) { - if (typeof msg === "string" && msg === "townCheck") { - townCheck = true; - } - }); - - // Init config and attacks - print("ÿc3Start TownChicken thread"); - D2Bot.init(); - Config.init(); - Pickit.init(); - Attack.init(); - Storage.Init(); - CraftingSystem.buildLists(); - Runewords.init(); - Cubing.init(); - - let checkHP = Config.TownHP > 0; - let checkMP = Config.TownMP > 0; - - // START - // test for getUnit bug - let test = Game.getMonster(); - test === null && console.warn("getUnit is bugged"); - - while (true) { - if (!me.inTown && (townCheck - // should TownHP/MP check be in toolsthread? - // We would then be able to remove all game interaction checks until we get a townCheck msg - || ((checkHP && me.hpPercent < Config.TownHP) || (checkMP && me.mpPercent < Config.TownMP)))) { - // canTpToTown should maybe be overrided here to quit if we can't tp to town but isn't just because we are in non-tp-able area - if (!Town.canTpToTown()) { - townCheck = false; - - continue; - } - this.togglePause(); - - while (!me.gameReady) { - if (me.dead) return; - - delay(100); - } - - try { - console.log("(TownChicken) :: Going to town"); - me.overhead("Going to town"); - Town.visitTown(); - } catch (e) { - Misc.errorReport(e, "TownChicken.js"); - scriptBroadcast("quit"); - - return; - } finally { - this.togglePause(); - - townCheck = false; - } - } - - delay(50); - } -} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..467e21e66 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2214 @@ +{ + "name": "trunk", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "trunk", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "eslint": "^7.32.0", + "typescript": "^4.9.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.6", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", + "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.24.6", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", + "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..e2aefb83b --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "trunk", + "version": "1.0.0", + "description": "1. D2BS, D2Bot and kolbot # are educational tools with an open source developer community. These tools are meant to be used offline or on private servers that explicitly allow them. These tools are not meant to be abused on battle.net (a Blizzard Entertainment entity).", + "main": "", + "typings": "./d2bs/kolbot/**/global.d.ts", + "scripts": { + "lint": "eslint --fix --ext .js --ext .dbj d2bs/kolbot/", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "eslint": "^7.32.0", + "typescript": "^4.9.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..23d371087 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + "module": "None", + "lib": [ + "ES2015", + "es2015.collection", + "ES2015.Promise", + "ES2016.Array.Include", + "ES2017.Object", + "ScriptHost", + ], + "target": "ES6", + "allowJs": true, + "checkJs": false, + "moduleResolution": "classic", + "allowUmdGlobalAccess": true, + "esModuleInterop": true, + "noImplicitAny": true, + "noEmit": true, + "strict": true, + // "declaration": true, + // "emitDeclarationOnly": true, + "baseUrl": "./d2bs/kolbot/", + "rootDir": "./d2bs/kolbot/", + // "outDir": "./d2bs/kolbot/sdk/types/", + "outFile": "./d2bs/kolbot/data/out.js", + "typeRoots": [ + "./d2bs/kolbot/sdk/globals.d.ts", + "./d2bs/kolbot/sdk/types/", + "./d2bs/kolbot/libs/SoloPlay/index.d.ts", + ], + }, + "include": [ + "./d2bs/kolbot/*.dbj", + "./d2bs/kolbot/**/*.js", + "**/*.dbl", + ], + "exclude": [ + "node_modules", + "**/node_modules/*", + "**/data/", + ], +}