diff --git a/+setup/automule/MuleConfig.js b/+setup/automule/MuleConfig.js new file mode 100644 index 000000000..7d2fdcaca --- /dev/null +++ b/+setup/automule/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/+setup/automule/StarterConfig.js b/+setup/automule/StarterConfig.js new file mode 100644 index 000000000..9f345b261 --- /dev/null +++ b/+setup/automule/StarterConfig.js @@ -0,0 +1,26 @@ +/** +* @filename StarterConfig.js +* @author theBGuy +* @desc Starter Configuration file for D2BotAutoMule system +* +*/ + +(function (module) { + /** + * @description D2BotAutoMule specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + const StarterConfig = { + MinGameTime: 30, // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby + MaxGameTime: 60, // Maximum game length in minutes, only for continuous muling + CreateGameDelay: 5, // Seconds to wait before creating a new game + SwitchKeyDelay: 0, // Seconds to wait before switching a used/banned key or after realm down + ExitToMenu: false, // Set to true to wait out restriction in main menu or false to wait in lobby. + VersionErrorDelay: rand(15, 30), // Seconds to wait after 'unable to identify version' message + MakeAccountOnFailure: true + }; + + module.exports = { + StarterConfig: StarterConfig + }; +})(module); diff --git a/+setup/automule/TorchAnniMules.js b/+setup/automule/TorchAnniMules.js new file mode 100644 index 000000000..b2c117b33 --- /dev/null +++ b/+setup/automule/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/+setup/autorush/RushConfig.js b/+setup/autorush/RushConfig.js new file mode 100644 index 000000000..e44544ea5 --- /dev/null +++ b/+setup/autorush/RushConfig.js @@ -0,0 +1,135 @@ +/** +* @filename RushConfig.js +* @author theBGuy +* @desc Configuration file for AutoRush system +* +*/ + +(function (module) { + // no touchy - need these imports + const { RushModes } = require("./RushConstants"); + + /** + * This is what you edit. + * + * Each key is a profile name. + * The value shape depends on the selected mode (`type`). + * + * Rushee modes: + * - `RushModes.quester` + * - `RushModes.follower` + * - `RushModes.bumper` + * + * Rusher modes: + * - `RushModes.rusher` + * - `RushModes.manual` + * + * `create` usage variants: + * - Account + character creation: provide `account`, `password`, `charName`, and `charInfo`. + * - Character-only creation: provide only `charName` and `charInfo`. You can skip charName for automatic name generation (uses soloplay's NameGen if available, otherwise will error). + * + * Rusher config defaults: + * - Quest toggles default to `true`. + * - `Wps` defaults to `false`. + * - You do not need to define every `config` property when you want default behavior. + * + * @example + * // Quester with account + character creation + * "quester-profile": { + * type: RushModes.quester, + * startProfiles: ["bumper-profile", "follower-profile", "rusher-profile"], + * create: { + * account: "myacc", + * password: "mypassword", + * charName: "quester", + * charInfo: "scl-sorc", + * }, + * config: { + * Leader: "my-rusher-char", + * }, + * } + * + * @example + * // Follower with character-only creation + * "follower-profile": { + * type: RushModes.follower, + * leader: "quester-profile", + * create: { + * charName: "follower", + * charInfo: "scl-zon", + * }, + * config: { + * Leader: "my-rusher-char", + * }, + * } + * + * @example + * // Bumper with minimal config + * "bumper-profile": { + * type: RushModes.bumper, + * leader: "quester-profile", + * config: { + * Leader: "my-rusher-char", + * }, + * } + * + * @example + * // Rusher with minimal config (uses defaults) + * "rusher-profile": { + * type: RushModes.rusher, + * leader: "quester-profile", + * config: { + * Wps: true, + * LastRun: "baal", + * }, + * } + * + * @example + * // Rusher with explicit full config + * "rusher-profile-full": { + * type: RushModes.rusher, + * leader: "quester-profile", + * playerWaitTimeout: Time.minutes(1), + * 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: "", + * }, + * } + * + * @example + * // Manual mode (same config shape as rusher) + * "manual-profile": { + * type: RushModes.manual, + * leader: "quester-profile", + * config: { + * Wps: true, + * }, + * } + * + * @type {Object.} + */ + const RushConfig = { + "quester-profile": { + type: RushModes.quester, + config: { + Leader: "my-rusher-char", + }, + } + }; + + module.exports = { + RushConfig: RushConfig, + }; +})(module); diff --git a/+setup/channel/ChannelConfig.js b/+setup/channel/ChannelConfig.js new file mode 100644 index 000000000..774cb2922 --- /dev/null +++ b/+setup/channel/ChannelConfig.js @@ -0,0 +1,65 @@ +/** +* @filename ChannelConfig.js +* @author theBGuy +* @desc Configuration file for D2BotChannel system +* +*/ + +(function (module) { + /** + * @description D2BotChannel specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + const StarterConfig = { + // JoinRetryDelay: 5, // Time in seconds to wait before next join attempt + }; + + 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, + StarterConfig: StarterConfig, + }; +})(module); diff --git a/+setup/charrefresher/RefresherConfig.js b/+setup/charrefresher/RefresherConfig.js new file mode 100644 index 000000000..5a1ece152 --- /dev/null +++ b/+setup/charrefresher/RefresherConfig.js @@ -0,0 +1,37 @@ +/** +* @filename RefresherConfig.js +* @author theBGuy +* @desc Configuration file for CharRefresher system +* +*/ + +(function (module) { + module.exports = { + LobbyTime: [15, 30], // (30, 60) to if you want to be less likely to get disconnected in the lobby + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + RefreshAccounts: { + /* Format: + "account1/password1/realm": ["charname1", "charname2 etc"], + "account2/password2/realm": ["charnameX", "charnameY etc"], + "account3/password3/realm": ["all"] + + To refresh 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"], + }, + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + + /** + * @description D2BotCharRefresher specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + StarterConfig: { + // none needed right now + } + }; +})(module); diff --git a/+setup/cleaner/CleanerConfig.js b/+setup/cleaner/CleanerConfig.js new file mode 100644 index 000000000..8bfb4824e --- /dev/null +++ b/+setup/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/config/_CustomConfig.js b/+setup/config/_CustomConfig.js similarity index 75% rename from d2bs/kolbot/libs/config/_CustomConfig.js rename to +setup/config/_CustomConfig.js index 86de0811e..b18c66200 100644 --- a/d2bs/kolbot/libs/config/_CustomConfig.js +++ b/+setup/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/+setup/crafting/TeamsConfig.js b/+setup/crafting/TeamsConfig.js new file mode 100644 index 000000000..45a0f6e55 --- /dev/null +++ b/+setup/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/d2bs.ini b/+setup/d2bs.ini similarity index 100% rename from d2bs/d2bs.ini rename to +setup/d2bs.ini diff --git a/data/cdkeys.json b/+setup/data/cdkeys.json similarity index 100% rename from data/cdkeys.json rename to +setup/data/cdkeys.json diff --git a/data/patch.json b/+setup/data/patch.json similarity index 100% rename from data/patch.json rename to +setup/data/patch.json diff --git a/data/profile.json b/+setup/data/profile.json similarity index 100% rename from data/profile.json rename to +setup/data/profile.json diff --git a/data/schedules.json b/+setup/data/schedules.json similarity index 100% rename from data/schedules.json rename to +setup/data/schedules.json diff --git a/data/server.json b/+setup/data/server.json similarity index 100% rename from data/server.json rename to +setup/data/server.json diff --git a/+setup/follow/FollowConfig.js b/+setup/follow/FollowConfig.js new file mode 100644 index 000000000..91e089fae --- /dev/null +++ b/+setup/follow/FollowConfig.js @@ -0,0 +1,37 @@ +/** +* @filename FollowConfig.js +* @author theBGuy +* @desc Configuration file for D2BotFollow system +* +*/ + +(function (module) { + /** + * @description D2BotFollow specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + const StarterConfig = { + JoinRetryDelay: 5, // Time in seconds to wait before next join attempt + }; + + /** + * @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, + StarterConfig: StarterConfig, + }; +})(module); diff --git a/+setup/gambling/TeamsConfig.js b/+setup/gambling/TeamsConfig.js new file mode 100644 index 000000000..b3fc90cee --- /dev/null +++ b/+setup/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/+setup/gameaction/GameActionConfig.js b/+setup/gameaction/GameActionConfig.js new file mode 100644 index 000000000..164909c53 --- /dev/null +++ b/+setup/gameaction/GameActionConfig.js @@ -0,0 +1,28 @@ +/** +* @filename GameActionConfig.js +* @author theBGuy +* @desc Configuration file for GameAction system +* +*/ + +(function (module) { + module.exports = { + 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 + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + /** + * @description D2BotGameAction specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + StarterConfig: { + MinGameTime: 0, // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby + CreateGameDelay: 5, // Seconds to wait before creating a new game + SwitchKeyDelay: 0, // Seconds to wait before switching a used/banned key or after realm down + }, + }; +})(module); diff --git a/+setup/lead/LeadConfig.js b/+setup/lead/LeadConfig.js new file mode 100644 index 000000000..cb6357453 --- /dev/null +++ b/+setup/lead/LeadConfig.js @@ -0,0 +1,20 @@ +/** +* @filename LeadConfig.js +* @author theBGuy +* @desc Configuration file for D2BotLead system +* +*/ + +(function (module) { + /** + * @description D2BotLead specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + const StarterConfig = { + MinGameTime: 360, // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby + }; + + module.exports = { + StarterConfig: StarterConfig, + }; +})(module); diff --git a/logs/Console.rtf b/+setup/logs/Console.rtf similarity index 100% rename from logs/Console.rtf rename to +setup/logs/Console.rtf diff --git a/logs/exceptions.log b/+setup/logs/exceptions.log similarity index 100% rename from logs/exceptions.log rename to +setup/logs/exceptions.log diff --git a/logs/keyinfo.log b/+setup/logs/keyinfo.log similarity index 100% rename from logs/keyinfo.log rename to +setup/logs/keyinfo.log diff --git a/+setup/mulelogger/LoggerConfig.js b/+setup/mulelogger/LoggerConfig.js new file mode 100644 index 000000000..883e13c62 --- /dev/null +++ b/+setup/mulelogger/LoggerConfig.js @@ -0,0 +1,46 @@ +/** +* @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"], + }, + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + + /** + * @description D2BotMuleLog specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + StarterConfig: { + 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 + CreateGameDelay: 5, // Seconds to wait before creating a new game + SwitchKeyDelay: 0, // Seconds to wait before switching a used/banned key or after realm down + } + }; +})(module); diff --git a/+setup/pubjoin/PubJoinConfig.js b/+setup/pubjoin/PubJoinConfig.js new file mode 100644 index 000000000..8b95b8fe9 --- /dev/null +++ b/+setup/pubjoin/PubJoinConfig.js @@ -0,0 +1,73 @@ +/** +* @filename PubJoinConfig.js +* @author theBGuy +* @desc Configuration file for D2BotPubJoin system +* +*/ + +(function (module) { + /** + * @description D2BotPubJoin specific settings - for global settings see libs/starter/StarterConfig.js + * @type {Partial} + */ + const StarterConfig = { + MinGameTime: 0, // Minimum game length in seconds. If a game is ended too soon, the rest of the time is waited in the lobby + ResetCount: 0, // Reset game count back to 1 every X games. + JoinDelay: 10, // Seconds to wait between join attempts + AttemptNextGame: true, // after joining a game, attempt incrementing game count and joining next game rather than looking for it in game list + AttemptNextGameRetrys: 5, + MinPlayers: 1, // Minimum players in game to join + }; + + /** + * @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 = [ + [""] + ]; + + /** + * @type {Record>, excludeFilter?: Array>}>} + */ + const profileOverides = { + // "test": { + // includeFilter: [["trist"], ["tombs"]] + // } + }; + + module.exports = { + includeFilter: includeFilter, + excludeFilter: excludeFilter, + profileOverides: profileOverides, + StarterConfig: StarterConfig + }; +})(module); diff --git a/+setup/setup.ps1 b/+setup/setup.ps1 new file mode 100644 index 000000000..f91c2a8eb --- /dev/null +++ b/+setup/setup.ps1 @@ -0,0 +1,141 @@ +function Copy-IfNotExists { + param ( + [string]$Source, + [string]$Destination, + [string]$Description + ) + if (Test-Path $Source) { + if (-not (Test-Path $Destination)) { + Copy-Item $Source $Destination -Force + Write-Host "Copied $Description" + } else { + Write-Host "$Description already exists - skipping" + } + } else { + Write-Host "WARNING: Source file $Source not found - skipping $Description" + } +} + +Write-Host "Copying setup files to their respective directories..." +Write-Host "Current directory: $PWD" +Write-Host "" + +# Check if git is available +if (-not (Get-Command git -ErrorAction SilentlyContinue)) { + Write-Host "WARNING: Git is not installed or not available in PATH" + Write-Host "Please install Git and ensure it's in your PATH to use submodules" + Write-Host "Continuing with file copying only..." + Write-Host "" +} else { + Write-Host "Git detected, initializing and updating submodules..." + git submodule update --init --recursive + if ($LASTEXITCODE -ne 0) { + Write-Host "WARNING: Failed to update submodules" + Write-Host "Please check your Git configuration and try again" + Write-Host "" + } else { + Write-Host "Submodules updated successfully!" + Write-Host "" + } +} + +# Set base directories +$SETUP_DIR = "+setup" +$DATA_DIR = "data" +$LOGS_DIR = "logs" +$D2BS_DIR = "d2bs" +$STARTER_DIR = "d2bs\kolbot\libs\starter" +$SYSTEMS_DIR = "d2bs\kolbot\libs\systems" +$CONFIG_DIR = "d2bs\kolbot\libs\config" +$SOLOPLAY_DIR = "d2bs\kolbot\libs\SoloPlay" +$SOLOPLAY_SETUP_DIR = "$SOLOPLAY_DIR\+setup" +$SOLOPLAY_SETTINGS_DIR = "$SOLOPLAY_DIR\Settings" +$SOLOPLAY_OOG_DIR = "$SOLOPLAY_DIR\OOG" + +# Create directories if they don't exist +$dirs = @( + $DATA_DIR, + $LOGS_DIR, + $D2BS_DIR, + $STARTER_DIR, + "$SYSTEMS_DIR\automule\config", + "$SYSTEMS_DIR\channel", + "$SYSTEMS_DIR\cleaner", + "$SYSTEMS_DIR\crafting", + "$SYSTEMS_DIR\follow", + "$SYSTEMS_DIR\gambling", + "$SYSTEMS_DIR\gameaction", + "$SYSTEMS_DIR\lead", + "$SYSTEMS_DIR\mulelogger", + "$SYSTEMS_DIR\pubjoin", + "$SYSTEMS_DIR\torch", + "$SYSTEMS_DIR\charrefresher" +) +foreach ($dir in $dirs) { + if (-not (Test-Path $dir)) { + New-Item -ItemType Directory -Path $dir | Out-Null + } +} + +# Copy JSON files to data directory +Write-Host "Copying JSON files to data directory..." +Copy-IfNotExists "$SETUP_DIR\data\cdkeys.json" "$DATA_DIR\cdkeys.json" "cdkeys.json to data directory" +Copy-IfNotExists "$SETUP_DIR\data\patch.json" "$DATA_DIR\patch.json" "patch.json to data directory" +Copy-IfNotExists "$SETUP_DIR\data\profile.json" "$DATA_DIR\profile.json" "profile.json to data directory" +Copy-IfNotExists "$SETUP_DIR\data\schedules.json" "$DATA_DIR\schedules.json" "schedules.json to data directory" +Copy-IfNotExists "$SETUP_DIR\data\server.json" "$DATA_DIR\server.json" "server.json to data directory" +Write-Host "JSON files processed successfully!" +Write-Host "" + +# Copy log files to logs directory +Write-Host "Copying log files to logs directory..." +Copy-IfNotExists "$SETUP_DIR\logs\Console.rtf" "$LOGS_DIR\Console.rtf" "Console.rtf to logs directory" +Copy-IfNotExists "$SETUP_DIR\logs\exceptions.log" "$LOGS_DIR\exceptions.log" "exceptions.log to logs directory" +Copy-IfNotExists "$SETUP_DIR\logs\keyinfo.log" "$LOGS_DIR\keyinfo.log" "keyinfo.log to logs directory" +Write-Host "Log files processed successfully!" +Write-Host "" + +# Copy ini file to d2bs directory +Write-Host "Copying d2bs.ini to d2bs directory..." +Copy-IfNotExists "$SETUP_DIR\d2bs.ini" "$D2BS_DIR\d2bs.ini" "d2bs.ini to d2bs directory" +Write-Host "" + +# Copy system files to their respective directories +Write-Host "Copying system configuration files..." +Copy-IfNotExists "$SETUP_DIR\automule\MuleConfig.js" "$SYSTEMS_DIR\automule\config\MuleConfig.js" "MuleConfig.js to automule config directory" +Copy-IfNotExists "$SETUP_DIR\automule\TorchAnniMules.js" "$SYSTEMS_DIR\automule\config\TorchAnniMules.js" "TorchAnniMules.js to automule config directory" +Copy-IfNotExists "$SETUP_DIR\automule\StarterConfig.js" "$SYSTEMS_DIR\automule\config\StarterConfig.js" "StarterConfig.js to automule config directory" +Copy-IfNotExists "$SETUP_DIR\channel\ChannelConfig.js" "$SYSTEMS_DIR\channel\ChannelConfig.js" "ChannelConfig.js to channel directory" +Copy-IfNotExists "$SETUP_DIR\cleaner\CleanerConfig.js" "$SYSTEMS_DIR\cleaner\CleanerConfig.js" "CleanerConfig.js to cleaner directory" +Copy-IfNotExists "$SETUP_DIR\crafting\TeamsConfig.js" "$SYSTEMS_DIR\crafting\TeamsConfig.js" "TeamsConfig.js to crafting directory" +Copy-IfNotExists "$SETUP_DIR\follow\FollowConfig.js" "$SYSTEMS_DIR\follow\FollowConfig.js" "FollowConfig.js to follow directory" +Copy-IfNotExists "$SETUP_DIR\gambling\TeamsConfig.js" "$SYSTEMS_DIR\gambling\TeamsConfig.js" "TeamsConfig.js to gambling directory" +Copy-IfNotExists "$SETUP_DIR\gameaction\GameActionConfig.js" "$SYSTEMS_DIR\gameaction\GameActionConfig.js" "GameActionConfig.js to gameaction directory" +Copy-IfNotExists "$SETUP_DIR\lead\LeadConfig.js" "$SYSTEMS_DIR\lead\LeadConfig.js" "LeadConfig.js to lead directory" +Copy-IfNotExists "$SETUP_DIR\mulelogger\LoggerConfig.js" "$SYSTEMS_DIR\mulelogger\LoggerConfig.js" "LoggerConfig.js to mulelogger directory" +Copy-IfNotExists "$SETUP_DIR\pubjoin\PubJoinConfig.js" "$SYSTEMS_DIR\pubjoin\PubJoinConfig.js" "PubJoinConfig.js to pubjoin directory" +Copy-IfNotExists "$SETUP_DIR\torch\FarmerConfig.js" "$SYSTEMS_DIR\torch\FarmerConfig.js" "FarmerConfig.js to torch directory" +Copy-IfNotExists "$SETUP_DIR\charrefresher\RefresherConfig.js" "$SYSTEMS_DIR\charrefresher\RefresherConfig.js" "RefresherConfig.js to charrefresher directory" +Copy-IfNotExists "$SETUP_DIR\autorush\RushConfig.js" "$SYSTEMS_DIR\autorush\RushConfig.js" "RushConfig.js to autorush directory" +Write-Host "System configuration files processed successfully!" +Write-Host "" + +# Copy custom config files to their respective directories +Write-Host "Copying custom configuration files..." +Copy-IfNotExists "$SETUP_DIR\config\_CustomConfig.js" "$CONFIG_DIR\_CustomConfig.js" "_CustomConfig.js to config directory" +Write-Host "" + +# Copy starter config files to their respective directories +Write-Host "Copying starter configuration files..." +Copy-IfNotExists "$SETUP_DIR\starter\AdvancedConfig.js" "$STARTER_DIR\AdvancedConfig.js" "AdvancedConfig.js to starter directory" +Copy-IfNotExists "$SETUP_DIR\starter\StarterConfig.js" "$STARTER_DIR\StarterConfig.js" "StarterConfig.js to starter directory" +Write-Host "" + +# Copy SoloPlay setup files to their respective directories +Write-Host "Copying SoloPlay configuration files..." +Copy-IfNotExists "$SOLOPLAY_SETUP_DIR\Settings.js" "$SOLOPLAY_SETTINGS_DIR\Settings.js" "Settings.js to SoloPlay Settings directory" +Copy-IfNotExists "$SOLOPLAY_SETUP_DIR\AdvancedSettings.js" "$SOLOPLAY_SETTINGS_DIR\AdvancedSettings.js" "AdvancedSettings.js to SoloPlay Settings directory" +Copy-IfNotExists "$SOLOPLAY_SETUP_DIR\StarterConfig.js" "$SOLOPLAY_OOG_DIR\StarterConfig.js" "StarterConfig.js to SoloPlay OOG directory" +Write-Host "" + +Write-Host "Setup files copied" \ No newline at end of file diff --git a/+setup/starter/AdvancedConfig.js b/+setup/starter/AdvancedConfig.js new file mode 100644 index 000000000..02683e6da --- /dev/null +++ b/+setup/starter/AdvancedConfig.js @@ -0,0 +1,47 @@ +/** +* @filename AdvancedConfig.js +* @author theBGuy +* @desc Profile specific settings for entry scripts. +* @note For general and global settings @see StarterConfig.js +* +*/ + +(function (module) { + /** @type {Record>} */ + 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/+setup/starter/StarterConfig.js b/+setup/starter/StarterConfig.js new file mode 100644 index 000000000..2a03b5cfd --- /dev/null +++ b/+setup/starter/StarterConfig.js @@ -0,0 +1,46 @@ +/** +* @filename StarterConfig.js +* @author theBGuy +* @desc Global settings for entry scripts +* @note For profile specific settings and overrides @see AdvancedConfig.js +* +*/ + +(function (module) { + /** @type {Partial} */ + 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 + + JoinGameDelay: 3, // Seconds to wait before opening the join game window after a game + 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/+setup/torch/FarmerConfig.js b/+setup/torch/FarmerConfig.js new file mode 100644 index 000000000..6290b9d07 --- /dev/null +++ b/+setup/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/.editorconfig b/.editorconfig new file mode 100644 index 000000000..d012c1426 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 120 \ 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/.gitattributes b/.gitattributes new file mode 100644 index 000000000..8fb7c07aa --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.dbj linguist-language=js \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..1b122be24 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,13 @@ +--- +applyTo: "**/*.js" +--- + +# Project general coding standards for JavaScript +- Prefer function syntax over arrow syntax +- No template literals +- Use `let` instead of `const` in for...of and for...in declarations +- No use of computed property names in object initialzer +- No setTimeout or setInterval +- Always use JSDOC +- No var declarations, always use let or const +- No optional chaining \ No newline at end of file diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md new file mode 100644 index 000000000..0251b0367 --- /dev/null +++ b/.github/instructions/typescript.instructions.md @@ -0,0 +1,6 @@ +--- +applyTo: "**/*.ts" +--- + +# Project general coding standards for TypeScript +- Prefer for...of over forEach \ No newline at end of file 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..3b1d354f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,59 @@ +# Do not track hidden files and directories by default +.* +# Track these hidden files +!.eslintrc.js +!.gitignore +!.gitmodules +!.editorconfig +!.gitkeep + +# Track these hidden directories +!.github/ + +# Track these files from vscodes +!.vscode/settings.json +!.vscode/extensions.json + +# Do not track install packages +node_modules/ + +# Do not track files denoted as private +**/__* + +# Do not track user generated data +data/*.json +images/ +logs/*.rtf +logs/*.log +d2bs/d2bs.ini d2bs/kolbot/data/secure/*.txt -d2bs/kolbot/data/*.json +d2bs/kolbot/data/**/*.json d2bs/kolbot/logs/*.json d2bs/kolbot/logs/*.txt d2bs/kolbot/logs/**/** d2bs/kolbot/mules/**/*.txt d2bs/logs/*.log +d2bs/kolbot/env.json d2bs/kolbot/libs/manualplay/config/*.*.js +d2bs/kolbot/libs/config/*.*.js +d2bs/kolbot/data/web/limedrop.json +d2bs/kolbot/libs/systems/automule/config/MuleConfig.js +d2bs/kolbot/libs/systems/channel/ChannelConfig.js +d2bs/kolbot/libs/systems/cleaner/CleanerConfig.js +d2bs/kolbot/libs/systems/torch/FarmerConfig.js +d2bs/kolbot/libs/systems/pubjoin/PubJoinConfig.js +d2bs/kolbot/libs/systems/mulelogger/LoggerConfig.js +d2bs/kolbot/libs/systems/gambling/TeamsConfig.js +d2bs/kolbot/libs/systems/follow/FollowConfig.js +d2bs/kolbot/libs/systems/crafting/TeamsConfig.js +d2bs/kolbot/libs/systems/automule/config/TorchAnniMules.js +d2bs/kolbot/libs/config/_CustomConfig.js +d2bs/kolbot/libs/starter/AdvancedConfig.js +d2bs/kolbot/libs/starter/StarterConfig.js +d2bs/kolbot/libs/systems/gameaction/GameActionConfig.js +d2bs/kolbot/libs/systems/automule/config/StarterConfig.js +d2bs/kolbot/libs/systems/charrefresher/RefresherConfig.js +d2bs/kolbot/libs/systems/lead/LeadConfig.js +/d2bs/kolbot/libs/core/Overrides +d2bs/kolbot/libs/systems/autorush/RushConfig.js diff --git a/.gitmodules b/.gitmodules index 4735c30a4..320499516 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "limedrop"] path = limedrop url = https://github.com/noah-/limedrop +[submodule "d2bs/kolbot/libs/SoloPlay"] + path = d2bs/kolbot/libs/SoloPlay + url = https://github.com/blizzhackers/kolbot-SoloPlay 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..7c602b5c4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,25 @@ +{ + "editor.rulers": [ + 120 + ], + "editor.tabSize": 2, + "editor.detectIndentation": false, + "[javascript]": { + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + }, + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "[typescript]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "biomejs.biome", + "editor.codeActionsOnSave": { + "source.organizeImports.biome": "explicit", + "source.removeUnusedImports": "always", + "quickfix.biome": "explicit", + "source.fixAll.ts": "explicit", + "source.fixAll.eslint": "never", + "source.fixAll.biome": "explicit", + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index fb209736e..ba3ce86d4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,15 @@ [**Join the Slack Channel!**](https://join.slack.com/t/blizzhackers/shared_invite/zt-qahq0w11-uzETJNgKmS9DdApJSRQqaw) +## Table of Contents + +- [General](#general) +- [Install Dependencies](#install-dependencies---do-this-first) +- [Required after Download Setup](#required-after-download-setup) +- [Getting Started](#getting-started) +- [Guides](#guides) +- [LimeDrop](#limedrop-web-based-item-manager-and-dropper) + ## General 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). @@ -19,29 +28,46 @@ * 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! - [Microsoft Visual C++ 2010 Redistributable Package (x86)](https://www.microsoft.com/en-us/download/details.aspx?id=26999) - [Microsoft .NET Framework 4.0 (or higher)](https://dotnet.microsoft.com/download/dotnet-framework) +## Required after download setup + +**kolbot will NOT work without running the setup script first!** + +After downloading kolbot, you **MUST** run the setup script before attempting to use the bot: + +1. Run `setup.bat` to copy configuration files to their correct locations +2. The script will also initialize Git submodules if Git is available +3. This step is required for kolbot to function properly + +To keep your installation updated, you can run `update.bat` at any time to pull the latest changes from the repository and update all submodules. + +### What happens if you skip setup: +- Missing configuration files +- You'll see this error: `Please view your exceptions.log file. D2Bot# will close now :(` + ## 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/biome.json b/biome.json new file mode 100644 index 000000000..7a75d5b0f --- /dev/null +++ b/biome.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": true, + "includes": ["**/*.ts"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "warn" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "lineWidth": 120 + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/d2bs/kolbot/D2BotAutoRush.dbj b/d2bs/kolbot/D2BotAutoRush.dbj new file mode 100644 index 000000000..80d4128bd --- /dev/null +++ b/d2bs/kolbot/D2BotAutoRush.dbj @@ -0,0 +1,578 @@ +/* eslint-disable no-fallthrough */ +/** +* @filename D2BotAutoRush.dbj +* @author theBGuy +* @desc Entry script for AutoRush system +* +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/autorush/index")} +*/ + +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + +// 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; + +if (DataFile.init()) { + Starter.firstRun = true; +} + +// TODO: need to handle acc creation, char creation somewhat works but will fail if name is in use + +const { + RushConfig, +} = require("./libs/systems/autorush/RushConfig"); +const { RushModes, normalizeRushProfileConfig } = require("./libs/systems/autorush/RushConstants"); +const RushProfile = RushConfig[me.profile]; + +const locationAction = (function () { + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations, + } = require("./libs/oog/Locations"); + // const Overrides = require("./libs/modules/Override"); + // const madeChars = []; + // new Overrides.Override(ControlAction, ControlAction.makeCharacter, function (orignal, ...args) { + // /** @type {ControlAction.CharacterInfo} */ + // let _charInfo = args[0]; + // if (String.isEqual(_charInfo.charName, me.name)) { + // console.debug("Need a new name"); + // } + + // return orignal.apply(this, args); + // }).apply(); + + Starter.LocationEvents.login = function () { + Starter.inGame && (Starter.inGame = false); + const pType = Profile().type; + /** @type {Map} */ + const classMap = new Map([ + ["ZON", "amazon"], + ["SOR", "sorceress"], + ["NEC", "necromancer"], + ["PAL", "paladin"], + ["BAR", "barbarian"], + ["DRU", "druid"], + ["SIN", "assassin"], + ]); + /** + * @param {RusheeConfig} rushProfile + * @returns {Partial} + */ + const getCharInfo = function (rushProfile) { + let charInfo = rushProfile.create.charInfo; + let [modePrefix, charClass] = charInfo.toUpperCase().split("-"); + if (!rushProfile.create.charName) { + // we can use soloplay's namegen if available + if (FileTools.exists("libs/SoloPlay/Modules/NameGen.js")) { + const NameGen = require("./libs/SoloPlay/Modules/NameGen"); + rushProfile.create.charName = NameGen(); + Starter.profileInfo.charName = rushProfile.create.charName; + D2Bot.setProfile(null, null, rushProfile.create.charName); + D2Bot.printToConsole("Generated character name: " + rushProfile.create.charName, sdk.colors.D2Bot.Green); + } else { + throw new Error("No character name provided and NameGen module not found"); + } + } + /** @type {Partial} */ + let info = { + charName: rushProfile.create.charName, + charClass: classMap.get(charClass.slice(0, 3)) || "sorceress", + hardcore: modePrefix.includes("HC"), + expansion: modePrefix.indexOf("CC") === -1, + ladder: modePrefix.indexOf("NL") === -1, + }; + console.debug(info); + return info; + }; + + if (getLocation() === sdk.game.locations.MainMenu && Starter.firstRun + && pType === sdk.game.profiletype.SinglePlayer + && Controls.SinglePlayer.click()) { + return; + } + + // Wrong char select screen fix + if ([sdk.game.locations.CharSelect, sdk.game.locations.CharSelectNoChars].includes(getLocation())) { + hideConsole(); // seems to fix odd crash with single-player characters if the console is open to type in + let spCheck = pType === sdk.game.profiletype.Battlenet; + let realmControl = !!Controls.CharSelectCurrentRealm.control; + if ((spCheck && !realmControl) || ((!spCheck && realmControl))) { + Controls.BottomLeftExit.click(); + + return; + } + } + + // 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 { + // make battlenet accounts/characters + D2Bot.updateStatus("Logging in"); + console.debug("ProfileInfo: ", Starter.profileInfo); + // need to make an account if we can + if (Starter.profileInfo.account === "" && pType === sdk.game.profiletype.Battlenet) { + if (RushProfile.type === RushModes.rusher) { + D2Bot.printToConsole("Cannot make accounts for rusher", sdk.colors.D2Bot.Red); + D2Bot.stop(me.profile, true); + return; + } + if (RushProfile.create.account && RushProfile.create.password) { + let { account, password, charInfo } = RushProfile.create; + // let madeAcc = false; + if (ControlAction.makeAccount(RushProfile.create)) { + console.log("Created account " + RushProfile.create.account); + D2Bot.setProfile(account, password); + // madeAcc = true; + } else if (ControlAction.loginAccount(RushProfile.create)) { + console.log("Logged into account " + RushProfile.create.account); + } + if (Starter.profileInfo.charName === "") { + D2Bot.updateStatus("Char Select - Looking for character"); + // if (!madeAcc) { + // let charInfo = DataFile.getStats().charInfo; + // if (charInfo && charInfo.charName) { + // console.log("Found Existing character " + charName); + // Starter.profileInfo.charName = charName; + // if (ControlAction.loginCharacter(Starter.profileInfo)) { + // console.debug("Logged into character " + Starter.profileInfo.charName); + // return; + // } + // } + // } + // we are going to need to make a character too + if (charInfo) { + let info = getCharInfo(); + + if (!ControlAction.findCharacter(info)) { + if (ControlAction.makeCharacter(info, true)) { + console.log("Created character " + me.charname); + info.charName = me.charname; + DataFile.updateStats("charInfo", info); + D2Bot.setProfile(account, password, me.charname); + + return; + } + } + } + } + } else { + D2Bot.printToConsole("No account information found", sdk.colors.D2Bot.Red); + D2Bot.stop(me.profile, true); + } + } else { + // existing account + try { + login(me.profile); + // need a better solution for this + if (getLocation() === sdk.game.locations.Lobby && RushProfile.type !== RushModes.rusher) { + if (RushProfile.create.charName && me.charname !== RushProfile.create.charName) { + Starter.profileInfo.charName = RushProfile.create.charName; + console.debug("Logged into wrong character ", Starter.profileInfo); + ControlAction.timeoutDelay("Character Change", 5000); + Controls.LobbyQuit.click(); + throw new Error("Character name mismatch"); + } + } + } catch (e) { + while (!getLocation()) { + delay(1000); + } + switch (getLocation()) { + case sdk.game.locations.CharSelect: + case sdk.game.locations.CharSelectNoChars: + if (Starter.loginRetry < 2) { + Starter.loginRetry++; + // let charInfo = DataFile.getStats().charInfo; + // if (charInfo) { + // try { + // charInfo = JSON.parse(charInfo); + // if (charInfo.charName) { + // console.log("Found Existing character " + charInfo.charName); + // console.debug(charInfo.charName); + // if (ControlAction.loginCharacter(charInfo)) { + // console.debug("Logged into character " + me.charname); + // return; + // } + // } + // } catch (e) { + // console.error(e); + // } + // } + 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 if (RushProfile.type !== RushModes.rusher && RushProfile.create.charInfo) { + let info = getCharInfo(RushProfile); + console.debug(info); + if (!ControlAction.findCharacter(info)) { + if (ControlAction.makeCharacter(info, true)) { + console.log("Created character " + me.charname); + info.charName = me.charname; + DataFile.updateStats("charInfo", info); + D2Bot.setProfile(null, null, info.charName); + + return; + } + } else { + ControlAction.loginCharacter(info); + } + } else { + Starter.loginRetry++; + } + } else { + ControlAction.loginCharacter(Starter.profileInfo); + // login(me.profile); + } + break; + } + } + } + } + } catch (e) { + console.log(e + " " + getLocation()); + } + }; + + if (RushProfile.type === RushModes.quester) { + locations.set(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, + "Highest", + Starter.Config.CreateGameDelay * 1000 + ); + + Starter.lastGameStatus = "pending"; + Starter.setNextGame(Starter.gameInfo); + Starter.locationTimeout(10000, location); + }); + } + + if (([ + RushModes.follower, + RushModes.bumper, + RushModes.rusher, + ].includes(RushProfile.type))) { + // Setup follower locations + let lastGameTick; + const lastGame = []; + /** @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; + }; + 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); + } + // need to handle channel join messages + } + + Starter.LocationEvents.openJoinGameWindow(); + } + ); + locations.set(sdk.game.locations.JoinGame, + function (location) { + D2Bot.updateStatus("Join Game"); + Starter.joinInfo = {}; + D2Bot.requestGame(RushProfile.leader); + delay(100); + + if (!Starter.joinInfo.hasOwnProperty("gameName") || Starter.joinInfo.gameName === "") { + delay(500); + return; + } + + 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(RushProfile.leader) + ); + D2Bot.updateRuns(); + D2Bot.requestGame(RushProfile.leader); + delay(200); + + if (!Starter.joinInfo.inGame) { + Starter.lastGameStatus = "ready"; + + return; + } + } + + if (!Starter.joinInfo.inGame) { + if (Starter.joinInfo.delay) { + ControlAction.timeoutDelay("Leader Delay", Starter.joinInfo.delay); + } + return; + } + + // 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); + + Starter.lastGameStatus = "pending"; + Starter.locationTimeout(15000, location); + + return; + } 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)) { + delay((Starter.joinInfo.inGame ? 5000 : 2000)); + } + } + } + ); + locations.set(sdk.game.locations.TcpIpEnterIp, + function () { + try { + for (let i = 0; i < 3; i++) { + D2Bot.requestGame(RushProfile.leader); + delay(1000); + + if (Object.keys(Starter.joinInfo).length && Starter.joinInfo.gameName !== "") { + break; + } + } + + 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); + 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); + } + console.debug(Starter.profileInfo); + + 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" })); + + if (!RushProfile) { + D2Bot.printToConsole("No Rush Config found for " + me.profile, sdk.colors.D2Bot.Red); + D2Bot.stop(me.profile, true); + } + + normalizeRushProfileConfig(RushProfile); + + // now that we are all setup, check if we need to start other profiles + if (RushProfile.startProfiles.length) { + for (let profile of RushProfile.startProfiles) { + if (profile !== me.profile) { + D2Bot.start(profile); + } + } + } + + 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( + 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/D2BotBlank.dbj b/d2bs/kolbot/D2BotBlank.dbj index 5e45a44c2..4c804d9dd 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 (DataFile.init()) { + Starter.firstRun = true; + } + + 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..fb920a95c 100644 --- a/d2bs/kolbot/D2BotChannel.dbj +++ b/d2bs/kolbot/D2BotChannel.dbj @@ -3,549 +3,614 @@ * @author kolton, theBGuy * @desc Entry script for following bots using channels * +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/torch/TorchSystem")} +* @typedef {import("./libs/systems/crafting/CraftingSystem")} +* @typedef {import("./libs/systems/gambling/Gambling")} */ -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"] - -// 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]); -} -let channelTick = getTickCount(); -let fListTick = 0; -let retry = 0; -let badGames = []; -let lastText; -let joinInfo = { - gameName: "", - gamePass: "", - oldGame: "", - inGame: false -}; +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +const { + ChannelConfig, +} = require("./libs/systems/channel/ChannelConfig"); -if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; -} +// the only things we really need from these are their oog checks +includeSystemLibs(); -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; - } +if (typeof Starter.AdvancedConfig[me.profile] === "object") { + Object.assign(Starter.Config, Starter.AdvancedConfig[me.profile]); } +delete Starter.AdvancedConfig; -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); - } +/** @type {Set} */ +const badGames = new Set(); +const watch = { + player: "", + status: "", +}; +/** @type {Array<{ name: string, msg: string }>} */ +const messageQueue = []; +/** @type {Array<{ name: string, game: string, time: number }>} */ +const friendsMessageQueue = []; +/** @type {Map} */ +const channelWatcher = new Map(); +const joinInfo = { + gameName: "", + gamePass: "", + oldGame: "", + inGame: false, + joinChannel: Starter.Config.JoinChannel, +}; - D2Bot.updateStatus(Starter.profileInfo.charName + " | Game: " + (me.gamename || "singleplayer") + Starter.timer(Starter.gameStart)); - } +if (DataFile.init()) { + Starter.firstRun = true; +} - delay(1000); - } +/** + * @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 }); +} - joinInfo.inGame = false; +/** + * @param {string} name + * @param {string} msg + */ +function WhisperChatHandler (name, msg) { + if (me.ingame) return; + if (!msg.includes("Your friend")) return; + let game = msg.split("game called")[1].replace(".", "").trim(); + friendsMessageQueue.push({ name: name, game: game, time: new Date().getTime() }); +} - locationAction(getLocation()); - delay(1000); - } +const locationAction = (function () { + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations, + parseControlText, + run + } = 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; + }; + + const systemsOOGCheck = function () { + return AutoMule.outOfGameCheck() + || TorchSystem.outOfGameCheck() + || Gambling.outOfGameCheck() + || CraftingSystem.outOfGameCheck(); + }; + + const friendListQueryInterval = Time.seconds(ChannelConfig.FriendListQuery || 0); + + 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 (systemsOOGCheck()) { + 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 (friendListQueryInterval && getTickCount() - fListTick >= friendListQueryInterval) { + 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; + } + } + } + } + } + + while (friendsMessageQueue.length > 0) { + let chat = friendsMessageQueue.shift(); + // lets determine if this is a player message or internal message + if (!chat.name || chat.name.split("*")[0] === me.charname) continue; + // how long ago was this message? + if (new Date().getTime() - chat.time > Time.minutes(3)) continue; + // we have a game from a friend whisper + let gameName = chat.game; + // double check that this is a valid game name + if (gameName.length > 15 || badGames.has(gameName) || exclude(gameName)) continue; + let validGame = ChannelConfig.Games.find(function (gInfo) { + return gameName.toLowerCase().includes(gInfo.game.toLowerCase()); + }); + if (!validGame) continue; + console.debug("Joining friend's game: " + gameName); + joinInfo.gameName = gameName; + joinInfo.gamePass = validGame.password || ""; + Controls.JoinGameWindow.click(); + + return; + } + + 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 { + run: run, + }; +})(); + +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); + + if (ChannelConfig.FriendListQuery > 0) { + addEventListener("whispermsg", WhisperChatHandler); + } + + 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/D2BotCharRefresher.dbj b/d2bs/kolbot/D2BotCharRefresher.dbj new file mode 100644 index 000000000..f5d758731 --- /dev/null +++ b/d2bs/kolbot/D2BotCharRefresher.dbj @@ -0,0 +1,311 @@ +/** +* @filename D2BotCharRefresher.dbj +* @author kolton, theBGuy +* @desc Entry script for CharRefresher.js +* +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/charrefresher/CharRefresher")} +*/ + +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +include("systems/charrefresher/CharRefresher.js"); + +if (DataFile.init()) { + Starter.firstRun = true; +} + +/** @type {string[]} */ +const accounts = []; +/** @type {string[]} */ +const chars = []; + +const parseInfo = function () { + if (!FileTools.exists("libs/systems/charrefresher/RefresherConfig.js")) { + console.error("Could not find RefresherConfig.js, please run setup.bat to get RefresherConfig.js and edit it to your needs."); + D2Bot.printToConsole("Could not find RefresherConfig.js, please run setup.bat to get RefresherConfig.js and edit it to your needs."); + D2Bot.stop(me.profile, true); + + return; + } + + const { RefreshAccounts, StarterConfig } = require("./libs/systems/charrefresher/RefresherConfig"); + Object.assign(Starter.Config, StarterConfig); + + for (let i in RefreshAccounts) { + if (RefreshAccounts.hasOwnProperty(i) && typeof i === "string") { + accounts.push(i); + chars.push(RefreshAccounts[i]); + } + } +}; + +const locationAction = (function () { + let currAcc; + /** @type {string[]} */ + let charList = []; + /** @type {{ currAcc: string, currChar: string }} */ + let obj = {}; + + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations, + run + } = require("./libs/oog/Locations"); + + const getLobbyTime = function() { + if (Array.isArray(CharRefresher.LobbyTime) && CharRefresher.LobbyTime.length === 2) { + return Time.seconds(rand(CharRefresher.LobbyTime[0], CharRefresher.LobbyTime[1])); + } + return Time.seconds(CharRefresher.LobbyTime); + }; + + addLocations([sdk.game.locations.Lobby, sdk.game.locations.LobbyChat, sdk.game.locations.CreateGame], + function () { + D2Bot.updateStatus("Lobby"); + + if (Starter.inGame) { + // Should never hit this as this isn't meant to go in games but just in case + 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; + } + + ControlAction.timeoutDelay( + "Character refresh delay", + getLobbyTime() + ); + + Controls.LobbyQuit.click(); + } + ); + 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) { + CharRefresher.remove(); + D2Bot.printToConsole("Done refreshing chars!"); + D2Bot.stop(me.profile, true); + + return; + } + + if (FileTools.exists("logs/CharRefresher.json")) { + /** @type {{ currAcc: string, currChar: string }} */ + obj = JSON.parse(FileTools.readText("logs/CharRefresher.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("ÿc4CharRefresherÿc2: Login account: " + currAcc[0]); + + if (!(currAcc[2] in ControlAction.realms)) { + console.log("ÿc4CharRefresherÿc2: Invalid realm: " + currAcc[2] + ", skipping account"); + D2Bot.printToConsole("Invalid realm: " + currAcc[2] + ", skipping account", sdk.colors.D2Bot.Gray); + accounts.shift(); + chars.shift(); + + return; + } + + CharRefresher.save(md5(currAcc[2].toLowerCase() + currAcc[0].toLowerCase()), currAcc[1]); + + if (ControlAction.loginAccount({ account: currAcc[0], password: currAcc[1], realm: currAcc[2] })) { + D2Bot.printToConsole("Refreshing account: " + currAcc[0], sdk.colors.D2Bot.Green); + FileTools.writeText("logs/CharRefresher.json", JSON.stringify(obj)); + accounts.shift(); // remove current account from the list + } else { + D2Bot.printToConsole("Failed to login account: " + currAcc[0], sdk.colors.D2Bot.Red); + } + } + ); + 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/CharRefresher.json")) { + /** @type {{ currAcc: string, currChar: string }} */ + obj = JSON.parse(FileTools.readText("logs/CharRefresher.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("ÿc4CharRefresherÿc2: Login character: " + currChar); + D2Bot.printToConsole("Refreshing character: " + currChar, sdk.colors.D2Bot.Gray); + FileTools.writeText("logs/CharRefresher.json", JSON.stringify(obj)); + + ControlAction.timeoutDelay( + "Character select delay", + Time.seconds(rand(1, 5)) + ); + + 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.OtherMultiplayer, + function () { + Controls.OtherMultiplayerCancel.click(); + } + ); + locations.set(sdk.game.locations.TcpIp, + function () { + Controls.TcpIpCancel.click(); + } + ); + + return { + run: run, + }; +})(); + +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 CharRefresher 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/D2BotCleaner.dbj b/d2bs/kolbot/D2BotCleaner.dbj index 7b571c313..6bbbb8fd8 100644 --- a/d2bs/kolbot/D2BotCleaner.dbj +++ b/d2bs/kolbot/D2BotCleaner.dbj @@ -4,495 +4,379 @@ * @credits Whoever did the original D2BotAccountCleaner.dbj * @desc The purpose of this entryscript is to clean/remove characters and/or files easily * -*/ -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 - -// 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... +* @typedef {import("./sdk/globals")} */ -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"); - -if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; +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"); +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + +let Controls = require("./libs/modules/Control"); + +if (DataFile.init()) { + 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/.soloplay/" + profile + "/" + profile + "-GameTime" + ".json"; + const charDataFP = "libs/SoloPlay/.soloplay/" + profile + "/" + profile + "-CharData" + ".json"; + const lvlPerfFP = "libs/SoloPlay/.soloplay/" + profile + "/" + profile + "-LevelingPerformance" + ".csv"; + const scrPerfFP = "libs/SoloPlay/.soloplay/" + 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/.soloplay/" + charType)) { + folder = dopen("libs/SoloPlay/.soloplay"); + folder.create(charType); + } + + if (!FileTools.exists("libs/SoloPlay/.soloplay/" + charType + "/" + charClass)) { + folder = dopen("libs/SoloPlay/.soloplay/" + charType); + folder.create(charClass); + } + + let files = dopen("libs/SoloPlay/.soloplay/" + charType + "/" + charClass + "/").getFolders(); + j = files.length + 1; + + // make sure folder doesn't already exist. + while (FileTools.exists("libs/SoloPlay/.soloplay/" + charType + "/" + charClass + "/" + j.toString())) { + j++; + delay(100); + } + + if (!FileTools.exists("libs/SoloPlay/.soloplay/" + charType + "/" + charClass + "/" + j.toString())) { + folder = dopen("libs/SoloPlay/.soloplay/" + charType + "/" + charClass); + folder.create(j.toString()); + } + + savePath = "libs/SoloPlay/.soloplay/" + 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/.soloplay/" + 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)) { + console.error("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 locationAction = (function () { + const { + run, + 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)); + } + ); + + return { + run: run, + }; +})(); 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.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotFollow.dbj b/d2bs/kolbot/D2BotFollow.dbj index 1d41eaec7..69b727b6a 100644 --- a/d2bs/kolbot/D2BotFollow.dbj +++ b/d2bs/kolbot/D2BotFollow.dbj @@ -3,439 +3,395 @@ * @author kolton, theBGuy * @desc Entry script for following bots running on the same pc * +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/torch/TorchSystem")} +* @typedef {import("./libs/systems/crafting/CraftingSystem")} +* @typedef {import("./libs/systems/gambling/Gambling")} */ -include("StarterConfig.js"); - -// D2BotFollow specific settings - for global settings see libs/StarterConfig.js -Starter.Config.JoinRetryDelay = 5; // Time in seconds to wait before next join attempt - -// 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"] - }; -*/ - -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]); -} -let lastGameTick, leader = "", - announced = false, - lastGame = []; +include("critical.js"); // required -if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - 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(); +// the only things we really need from these are their oog checks +includeSystemLibs(); -function joinCheck (leader) { - D2Bot.requestGame(leader); - delay(500); +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +const { + JoinSettings, + StarterConfig +} = require("./libs/systems/follow/FollowConfig"); +Object.assign(Starter.Config, StarterConfig); - if (!Starter.joinInfo.inGame || (lastGame.length && lastGame.indexOf(Starter.joinInfo.gameName) === -1)) { - D2Bot.printToConsole("Game is finished. Stopping join delay."); - return true; - } +const Overrides = require("./libs/modules/Override"); - return false; +if (typeof Starter.AdvancedConfig[me.profile] === "object") { + Object.assign(Starter.Config, Starter.AdvancedConfig[me.profile]); } +delete Starter.AdvancedConfig; -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; - } +if (DataFile.init()) { + Starter.firstRun = true; } -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)); - } +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(); - delay(1000); - } +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, + run + } = 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; + + if (typeof Starter.Config.JoinGameDelay === "number" && Starter.Config.JoinGameDelay > 0) { + ControlAction.timeoutDelay("Join Game Delay", Starter.Config.JoinGameDelay * 1e3); + } + } + + 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 { + run: run, + }; +})(); - locationAction(getLocation()); - delay(1000); - } +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("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..a4fe163d9 100644 --- a/d2bs/kolbot/D2BotGameAction.dbj +++ b/d2bs/kolbot/D2BotGameAction.dbj @@ -3,401 +3,363 @@ * @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 -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 +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +const { StarterConfig } = require("./libs/systems/gameaction/GameActionConfig"); +Object.assign(Starter.Config, StarterConfig); // 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"); - -if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; +// system libs +includeSystemLibs(); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); + +const Overrides = require("./modules/Override"); + +if (DataFile.init()) { + 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, + run + } = 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) { + const loginInfo = GameAction.getLogin(); + const accountInfo = loginInfo.account ? "for account " + loginInfo.account : ""; + GameAction.update("done", "GameAction has completed task " + accountInfo); + 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) { + const loginInfo = GameAction.getLogin(); + const accountInfo = loginInfo.account ? "for account " + loginInfo.account : ""; + GameAction.update("done", "GameAction has completed task " + accountInfo); + 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 { + run: run, + }; +})(); function main () { - addEventListener("copydata", Starter.receiveCopyData); + addEventListener("copydata", Starter.receiveCopyData); + + while (!Starter.handle) { + delay(100); + } + + DataFile.updateStats("handle", Starter.handle); + delay(500); + D2Bot.init(); + load("threads/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("tools/heartbeat.js"); + Starter.gameCount = (DataFile.getStats().runs + 1 || 1); - while (!Object.keys(Starter.gameInfo).length) { - D2Bot.requestGameInfo(); - delay(500); - } + while (!tag) { + D2Bot.getProfile(); + delay(500); + } - Starter.gameCount = (DataFile.getStats().runs + 1 || 1); + 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); - while (!tag) { - D2Bot.getProfile(); - delay(500); - } + return; + } - 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); + GameAction.init(tag); - return; - } + if (GameAction.task.action === "doConvertNL") { + GameAction.convertLadderFiles(); - GameAction.init(tag); + return; + } - if (Starter.gameInfo.error) { - if (!!DataFile.getStats().debugInfo) { - Starter.gameInfo.crashInfo = DataFile.getStats().debugInfo; + 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); - } + 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(); - } + ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); + D2Bot.updateRuns(); + } - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); + 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); + 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(); + Starter.lastGameStatus = "ingame"; + Starter.inGame = true; + Starter.gameStart = getTickCount(); - DataFile.updateStats("runs", Starter.gameCount); - } - } + DataFile.updateStats("runs", Starter.gameCount); + } + } - delay(1000); - } + delay(1000); + } - locationAction(getLocation()); - delay(1000); - } + locationAction.run(getLocation()); + delay(1000); + } } diff --git a/d2bs/kolbot/D2BotLead.dbj b/d2bs/kolbot/D2BotLead.dbj index 051da986b..6f74e20e9 100644 --- a/d2bs/kolbot/D2BotLead.dbj +++ b/d2bs/kolbot/D2BotLead.dbj @@ -1,401 +1,133 @@ -/* eslint-disable no-fallthrough */ /** * @filename D2BotLead.dbj * @author kolton, theBGuy * @desc Entry script for leader * +* @typedef {import("./sdk/globals")} */ -include("StarterConfig.js"); -// - for global settings see libs/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 +include("critical.js"); // required -// 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]); +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +// - for global settings @see libs/starter/StarterConfig.js +if (FileTools.exists("libs/systems/lead/LeadConfig.js")) { + const { StarterConfig } = require("./libs/systems/lead/LeadConfig"); + Object.assign(Starter.Config, StarterConfig); } -if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) { - Starter.firstRun = true; +if (typeof Starter.AdvancedConfig[me.profile] === "object") { + Object.assign(Starter.Config, Starter.AdvancedConfig[me.profile]); } +delete Starter.AdvancedConfig; -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; - } +if (DataFile.init()) { + Starter.firstRun = true; } -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); - } +const locationAction = (function () { + const { + run, + } = require("./libs/oog/Locations"); - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } + return { + run: run, + }; +})(); - 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); - } +function main () { + 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..f6b959a7d 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 (DataFile.init()) { + Starter.firstRun = true; + } - 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..d57fe4b11 100644 --- a/d2bs/kolbot/D2BotMule.dbj +++ b/d2bs/kolbot/D2BotMule.dbj @@ -3,1010 +3,580 @@ * @author kolton, theBGuy * @desc Entry script for AutoMule.js * +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/mulelogger/MuleLogger")} */ -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 -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 -Starter.Config.SwitchKeyDelay = 0; // Seconds to wait before switching a used/banned key or after realm down -Starter.Config.ExitToMenu = false; // Set to true to wait out restriction in main menu or false to wait in lobby. -Starter.Config.VersionErrorDelay = rand(15, 30); // Seconds to wait after 'unable to identify version' message -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: "" }; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +const { StarterConfig } = require("./libs/systems/automule/config/StarterConfig"); +Object.assign(Starter.Config, StarterConfig); -/** - * 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; - } -} +// system libs +includeSystemLibs(); +include("systems/automule/Mule.js"); +include("systems/mulelogger/MuleLogger.js"); +include("systems/gameaction/GameAction.js"); -// 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; - } +if (DataFile.init()) { + 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, + run + } = 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 { + run: run, + }; + })(); + + 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..39b11999f 100644 --- a/d2bs/kolbot/D2BotMuleLog.dbj +++ b/d2bs/kolbot/D2BotMuleLog.dbj @@ -3,360 +3,326 @@ * @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 -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 - -// 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"); - -if (!FileTools.exists("data/" + me.profile + ".json")) { - 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(); +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +const { StarterConfig } = require("./libs/systems/mulelogger/LoggerConfig"); +Object.assign(Starter.Config, StarterConfig); - return; - } +// 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"); - 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); - } +if (DataFile.init()) { + Starter.firstRun = true; +} - 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, + run + } = 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 { + run: run, + }; +})(); + +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..dc7958af3 100644 --- a/d2bs/kolbot/D2BotPubJoin.dbj +++ b/d2bs/kolbot/D2BotPubJoin.dbj @@ -3,442 +3,475 @@ * @author kolton, theBGuy * @desc Entry script for following public games * +* @typedef {import("./sdk/globals")} +* @typedef {import("./libs/systems/torch/TorchSystem")} +* @typedef {import("./libs/systems/crafting/CraftingSystem")} +* @typedef {import("./libs/systems/gambling/Gambling")} */ -include("StarterConfig.js"); -// D2BotPubJoin specific settings - for global settings see libs/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; - -// 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 = [ - [""] -]; +include("critical.js"); // required +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +const { + includeFilter, + excludeFilter, + profileOverides, + StarterConfig, +} = require("./libs/systems/pubjoin/PubJoinConfig"); // ############################################################################### +Object.assign(Starter.Config, StarterConfig); -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; + +if (typeof profileOverides[me.profile] === "object") { + if (profileOverides[me.profile].hasOwnProperty("includeFilter")) { + includeFilter.length = 0; // reset + for (let filter of profileOverides[me.profile].includeFilter) { + includeFilter.push(filter); + } + } + + if (profileOverides[me.profile].hasOwnProperty("excludeFilter")) { + excludeFilter.length = 0; // reset + for (let filter of profileOverides[me.profile].excludeFilter) { + excludeFilter.push(filter); + } + } } 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; +if (DataFile.init()) { + Starter.firstRun = true; } -let lastGameTick, retry = 0; +let lastGameTick = 0; +let 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 GameTracker = { + _path: "data/" + me.profile + "/pubjoin.json", + + init: function () { + if (!FileTools.exists("data/" + me.profile)) { + let folder = dopen("data"); + folder.create(me.profile); + } + + if (!FileTools.exists(this._path)) { + FileAction.write(this._path, JSON.stringify([])); + } + }, + + /** + * @returns {string[]} + */ + read: function () { + return FileAction.parse(this._path); + }, + + /** @param {string[]} doneGames */ + update: function (doneGames) { + return FileAction.write(this._path, JSON.stringify(doneGames)); + }, +}; + +const locationAction = (function () { + let gameToJoin; + /** @type {{ gameName: string; players: number }[] } */ + let gameList; + /** @type {string[]} */ + let doneGames = []; + + const Controls = require("./libs/modules/Control"); + const { + locations, + addLocations, + run + } = 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); + } + } + + if (Starter.Config.JoinChannel !== "" || Starter.Config.AnnounceGames) { + Controls.LobbyEnterChat.click(); + + return; + } + + Starter.LocationEvents.openJoinGameWindow(); + } + ); + 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(); + } + ); + addLocations([sdk.game.locations.WaitingInLine, sdk.game.locations.CreateGame], + function () { + Controls.CancelCreateGame.click(); + Controls.JoinGameWindow.click(); + } + ); + addLocations( + [ + 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)); + } + + D2Bot.updateStatus("Searching for Public Game"); + + 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 = GameTracker.read(); + gameToJoin = false; + + 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); + GameTracker.update(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 { + run: run, + }; +})(); + +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); + } + + GameTracker.init(); + + 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 = GameTracker.read(); + + doneGames.length >= 20 && doneGames.shift(); + if (!doneGames.includes(me.gamename)) { + doneGames.push(me.gamename); + } + GameTracker.update(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/D2BotSoloPlay.dbj b/d2bs/kolbot/D2BotSoloPlay.dbj new file mode 100644 index 000000000..5f43583e4 --- /dev/null +++ b/d2bs/kolbot/D2BotSoloPlay.dbj @@ -0,0 +1,15 @@ +/** +* @filename D2BotSoloPlay.dbj +* @author theBGuy +* @desc Entry script for SoloPlay leveling system. Actual logic is in SoloEntry.js +* +* +* @typedef {import("./sdk/globals")} +*/ + +// No touchy! +include("critical.js"); // required +if (!include("SoloPlay/OOG/SoloEntry.js")) { + D2Bot.printToConsole("SoloPlay: Failed to include SoloEntry.js"); + D2Bot.stop(); +} diff --git a/d2bs/kolbot/default.dbj b/d2bs/kolbot/default.dbj index bcaad5ad6..76171e5dc 100644 --- a/d2bs/kolbot/default.dbj +++ b/d2bs/kolbot/default.dbj @@ -1,270 +1,316 @@ /** * @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) { + console.log("ÿ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(); + me.clearBelt(); + Pather.init(); // initialize wp data + + let { x, y } = me; + Config.ClearInvOnStart && Town.clearInventory(); + [x, y].distance > 3 && Pather.moveTo(x, y); + Pickit.pickItems(); + 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 || Config.DebugMode.Path; + + // 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." + ); + } + + if (Config.UnpartyForMinGameTimeWait) { + scriptBroadcast("unparty"); + } + + 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..5dbfff031 100644 --- a/d2bs/kolbot/libs/OOG.js +++ b/d2bs/kolbot/libs/OOG.js @@ -1,2155 +1,1871 @@ +/* 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 ladderString = getLocaleString(sdk.locale.text.Ladder); + /** @param {string} text */ + const matchLadderString = function (text) { + return text.includes(ladderString); + }; + const singlePlayer = ![sdk.game.profiletype.OpenBattlenet, sdk.game.profiletype.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); + } + + // Wrong char select screen fix + if ([sdk.game.locations.CharSelect, sdk.game.locations.CharSelectNoChars].includes(getLocation())) { + hideConsole(); // seems to fix odd crash with single-player characters if the console is open to type in + let spCheck = Profile().type === sdk.game.profiletype.Battlenet; + let realmControl = !!Controls.CharSelectCurrentRealm.control; + if ((spCheck && !realmControl) || ((!spCheck && realmControl))) { + Controls.BottomLeftExit.click(); + return false; // what about a recursive call to loginCharacter? + } + } + + if (getLocation() === sdk.game.locations.CharSelectConnecting) { + if (Starter.charSelectConnectingRetry > 3 || !Starter.LocationEvents.charSelectConnecting()) { + D2Bot.printToConsole("Stuck at connecting screen"); + D2Bot.restart(); + } else { + Starter.charSelectConnectingRetry++; + } + } + + // 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 (!singlePlayer && info.ladder && !text.some(matchLadderString)) 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; + } + + let expireText = text.find(el => el.includes(expireStr)); + if (!expireText) { + return true; + } + + let daysMatch = /\d+/.exec(expireText); + let days = daysMatch ? parseInt(daysMatch[0], 10) : 0; + + // > 11 days means this char has been permed before + return days > 11; + }, + + /** + * 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; + + const openBnet = Profile().type === sdk.game.profiletype.OpenBattlenet; + const delays = [100, 150, 200, 250, 300, 350, 400, 450, 500]; + + // 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.CharSelectConnecting: + // if (Starter.charSelectConnectingRetry > 3 || !Starter.LocationEvents.charSelectConnecting()) { + // D2Bot.printToConsole("Stuck at connecting screen"); + // D2Bot.restart(); + // } else { + // Starter.charSelectConnectingRetry++; + // } + // bugged but account was technically created, so just break to char select and let caller handle it + break MainLoop; + case sdk.game.locations.LoginError: + Controls.LoginErrorOk.click(); + + return false; + default: + break; + } + + delay(delays.random()); + } + + 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: + console.log(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) { + console.log(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: {}, + /** @type {number[]} */ + lastLocation: [], + charSelectConnectingRetry: 0, + + 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; + }, + + /** + * @param {number} [len] + * @returns {string} + */ + randomNumberString: function (len) { + !len && (len = rand(2, 5)); + + let rval = ""; + const vals = "0123456789".split(""); + + for (let i = 0; i < len; i += 1) { + rval += vals.random(); + } + + 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)) { + console.log("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 { + console.log("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 (Starter.inGame) { + ControlAction.timeoutDelay("Open join game delay", 5000); + } + + 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) { + if (!Starter.lastLocation.includes(sdk.game.locations.LobbyLostConnection)) { + 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(); + } + }, + + charSelectConnecting: function () { + if (getLocation() === sdk.game.locations.CharSelectConnecting) { + // bugged? lets see if we can unbug it + // Click create char button on infinite "connecting" screen + Controls.CharSelectCreate.click() && delay(1000); + Controls.BottomLeftExit.click() && delay(1000); + + return (getLocation() !== sdk.game.locations.CharSelectConnecting); + } else { + return true; + } + } + }; + })(), + }; + + return { + ControlAction: ControlAction, + Starter: Starter, + }; +})); diff --git a/d2bs/kolbot/libs/Polyfill.js b/d2bs/kolbot/libs/Polyfill.js index ad64001b3..7e63f6d9a 100644 --- a/d2bs/kolbot/libs/Polyfill.js +++ b/d2bs/kolbot/libs/Polyfill.js @@ -1,670 +1,1297 @@ /** * @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; - } + 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; + } + }; +} - if (start + search.length > this.length) { - return false; - } else { - return this.indexOf(search, start) !== -1; - } - }; +if (!String.prototype.contains) { + String.prototype.contains = String.prototype.includes; } 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; + } + }); +} + +if (!global.hasOwnProperty("env")) { + /** + * @typedef {Object} EnvStore + * @property {function(Record): EnvStore} update - Updates environment settings + * @property {Object.} [customSettings] - Any additional custom settings + */ + + /** @type {EnvStore} */ + const envStore = {}; + let initialized = false; + + Object.defineProperty(global, "env", { + value: new Proxy({}, { + get: function (target, prop) { + if (!initialized) { + /** @param {Record} settings */ + envStore.update = function(settings) { + Object.assign(this, settings); + return this; + }; + + if (FileTools.exists(".env.json")) { + try { + let loadedEnv = FileAction.parse(".env.json"); + envStore.update(loadedEnv); + } catch (err) { + console.error(err); + } + } + + initialized = true; + } + + if (prop in envStore) { + return typeof envStore[prop] === "function" + ? envStore[prop].bind(envStore) + : envStore[prop]; + } + + return undefined; + }, + + set: function (target, prop, value) { + if (!initialized) { + this.get(target, "version"); + } + + envStore[prop] = value; + return true; + }, + + has: function (target, prop) { + if (!initialized) { + this.get(target, "version"); + } + + return prop in envStore; + }, + + ownKeys: function (target) { + if (!initialized) { + this.get(target, "version"); + } + + return Object.keys(envStore); + } + }), + writable: false, + configurable: false + }); +} + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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; }; -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); - } +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); }; -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 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); }; -if (!Object.values) { - Object.values = function (source) { - return Object.keys(source).map(function (k) { return source[k]; }); - }; +/** + * @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; +}; + +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; +}; + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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/SoloPlay b/d2bs/kolbot/libs/SoloPlay new file mode 160000 index 000000000..ada829064 --- /dev/null +++ b/d2bs/kolbot/libs/SoloPlay @@ -0,0 +1 @@ +Subproject commit ada82906406663c72f2664f052fd6965c16794f0 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..e2d49c3c2 100644 --- a/d2bs/kolbot/libs/config/Amazon.js +++ b/d2bs/kolbot/libs/config/Amazon.js @@ -15,720 +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] = 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + // ########################### // + /* ##### 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * 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..3e576b2d9 100644 --- a/d2bs/kolbot/libs/config/Assassin.js +++ b/d2bs/kolbot/libs/config/Assassin.js @@ -15,726 +15,813 @@ * 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + // ########################### // + /* ##### 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * 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..230b3c02a 100644 --- a/d2bs/kolbot/libs/config/Barbarian.js +++ b/d2bs/kolbot/libs/config/Barbarian.js @@ -15,715 +15,802 @@ * 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + // ########################### // + /* ##### 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * 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..fc66978f4 100644 --- a/d2bs/kolbot/libs/config/Druid.js +++ b/d2bs/kolbot/libs/config/Druid.js @@ -15,719 +15,806 @@ * 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + // ########################### // + /* ##### 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * 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..855458bea 100644 --- a/d2bs/kolbot/libs/config/Necromancer.js +++ b/d2bs/kolbot/libs/config/Necromancer.js @@ -15,740 +15,828 @@ * 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 + Scripts.RaiseArmy = false; // Go through pindle portal and raise an army of skeletons, then return to town. Only works if necromancer is configured to raise skeletons and/or revives. + + // ## 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + // ########################### // + /* ##### 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * 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..2619406e7 100644 --- a/d2bs/kolbot/libs/config/Paladin.js +++ b/d2bs/kolbot/libs/config/Paladin.js @@ -15,719 +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] = 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + // ########################### // + /* ##### 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * 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..8ebc82285 100644 --- a/d2bs/kolbot/libs/config/Sorceress.js +++ b/d2bs/kolbot/libs/config/Sorceress.js @@ -15,721 +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] = 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + // ########################### // + /* ##### 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * 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/Attacks.txt b/d2bs/kolbot/libs/config/Templates/Attacks.txt index 2f82ccefd..7fbdce12a 100644 --- a/d2bs/kolbot/libs/config/Templates/Attacks.txt +++ b/d2bs/kolbot/libs/config/Templates/Attacks.txt @@ -8,7 +8,7 @@ *** Single element - lightning + chain lightning - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 49; // 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] = 53; // Primary skill to others. @@ -18,7 +18,7 @@ *** Single element - fireball + meteor - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 56; // Primary skill to bosses. Config.AttackSkill[2] = 47; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. Config.AttackSkill[3] = 56; // Primary skill to others. @@ -28,7 +28,7 @@ *** Single element - blizzard + glacial spike - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 59; // Primary skill to bosses. Config.AttackSkill[2] = 55; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. Config.AttackSkill[3] = 59; // Primary skill to others. @@ -38,7 +38,7 @@ *** Dual element - frozen orb + nova (with glacial spike to lightning immune) - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 64; // Primary skill to bosses. Config.AttackSkill[2] = 48; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. Config.AttackSkill[3] = 64; // Primary skill to others. @@ -48,7 +48,7 @@ *** Dual element - blizzard + fireball (with meteor to cold immune and glacial spike to fire immune) - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 59; // Primary skill to bosses. Config.AttackSkill[2] = 47; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. Config.AttackSkill[3] = 59; // Primary skill to others. @@ -58,7 +58,7 @@ *** Tri-element - blizzard with fireball, meteor on cold immune, nova on fire immune - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 59; // Primary skill to bosses. Config.AttackSkill[2] = 47; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. Config.AttackSkill[3] = 59; // Primary skill to others. @@ -68,7 +68,7 @@ *** Tri-element - frozen orb with nova, fire wall on cold immune, glacial spike on lightning immune - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 64; // Primary skill to bosses. Config.AttackSkill[2] = 48; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. Config.AttackSkill[3] = 64; // Primary skill to others. @@ -82,7 +82,7 @@ *** Poison nova with corpse explosion and lower resist - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 92; // 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] = 92; // Primary skill to others. @@ -104,7 +104,7 @@ *** Summoner with corpse explosion, amplify damage on non-bosses and decrepify on bosses - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 500; // 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] = 500; // Primary skill to others. @@ -122,7 +122,7 @@ *** Bone spirit/spear with corpse explosion and decrepify - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 93; // 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] = 84; // Primary skill to others. @@ -146,7 +146,7 @@ *** Hammerdin with holybolt/redemption on immune - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 112; // Primary skill to bosses. Config.AttackSkill[2] = 113; // Primary aura to bosses Config.AttackSkill[3] = 112; // Primary skill to others. @@ -156,7 +156,7 @@ *** Smiter with zeal on non-bosses - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 97; // Primary skill to bosses. Config.AttackSkill[2] = 122; // Primary aura to bosses Config.AttackSkill[3] = 106; // Primary skill to others. @@ -170,7 +170,7 @@ *** Tornado druid - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 245; // 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] = 245; // Primary skill to others. @@ -186,7 +186,7 @@ *** Trap assassin using lightning sentry and death sentry as traps, shock web as timed attack and fire blast as untimed attack - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 256; // Primary skill to bosses. Config.AttackSkill[2] = 251; // Primary untimed skill to bosses. Keep at -1 if Config.AttackSkill[1] is untimed skill. Config.AttackSkill[3] = 256; // Primary skill to others. @@ -201,7 +201,7 @@ *** Whirlwind assassin using wake of fire traps - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 151; // 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] = 151; // Primary skill to others. @@ -216,7 +216,7 @@ *** Kicksin using Death Sentry for traps - Config.AttackSkill[0] = -1; // Preattack skill. Not implemented yet. + Config.AttackSkill[0] = -1; // Preattack skill. Config.AttackSkill[1] = 255; // 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] = 255; // Primary skill to others. 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..3844e1264 100644 --- a/d2bs/kolbot/libs/config/_BaseConfigFile.js +++ b/d2bs/kolbot/libs/config/_BaseConfigFile.js @@ -4,895 +4,1030 @@ * - 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 + Scripts.RaiseArmy = false; // Go through pindle portal and raise an army of skeletons, then return to town. Only works if necromancer is configured to raise skeletons and/or revives. + + // ## 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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.ClearType = 0; // Monster spectype to kill while following path to seals. 0xF = skip normal, 0x7 = champions/bosses, 0 = all + 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 + Config.DiabloHelper.HurtDiablo = 0; // Hurt Diablo to X percent health. Set to 0 to disable + 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.SoulQuit = false; // End script if Souls are found + Config.BaalHelper.DollQuit = false; // End script if Dolls (Undead Soul Killers) are found. + Config.BaalHelper.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.HurtBaal = 0; // Hurt Baal to X percent health. Set to 0 to disable + 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.Cain = true; // Rescue cain 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 + Config.ControlBot.NGVoting = true; // Allow players to vote on new game + Config.ControlBot.NGVoteCooldown = 3; // Time in minutes after a vote period a players has to wait to start a new vote + Config.ControlBot.MinGameLength = 3; // Minimum time in minutes before a ng vote can be called + + // ##### 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.TaxiChar = ""; // Name of the taxi character running OrgTorchHelper. + 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 + + Scripts.OrgTorchHelper = false; + Config.OrgTorchHelper.Taxi = false; // Taxi the killer to the area + Config.OrgTorchHelper.Helper = true; // Set to true to help attack, set false to wait in town. + Config.OrgTorchHelper.UseWalkPath = false; // Use walk path to get to the area - helpful if leader is a walker and you have tele + Config.OrgTorchHelper.SkipTp = false; // Skip and go through the red portal + Config.OrgTorchHelper.GetFade = false; // Get fade by standing in a fire. You MUST have Last Wish, Treachery, or SpiritWard on your character being worn. + + // ##### AUTO-RUSH ##### // + // Setup now uses D2BotAutoRush.dbj, and config is in systems/autorush/RushConfig.js + + // ##### 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. + Config.UnpartyForMinGameTimeWait = false; // Unparty for MinGameTime wait - can prevent players from completing q's in your game you don't want completed + + // 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) + + // Advertise settings + Config.Advertise.Enabled = true; + Config.Advertise.Message = "Kolbot is super cool"; + // Config.Advertise.Message = ["Kolbot is super cool", "Join op kolbot games!", "Kolbot FTW!"]; // Array of messages to randomly choose from + Config.Advertise.Interval = [30, 60]; // Send advert message every X to Y seconds + + // 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], preAttack?: 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]. + * The `preAttack` property can be used to specify a skill to cast before the main attack. + * + * Example: + * [ + * { + * check: function (unit) { + * return unit.getEnchant(sdk.enchant.LightningEnchanted); + * }, + * attack: [sdk.skills.Zeal, sdk.skills.Salvation], + * preAttack: sdk.skills.SlowMissiles + * }, + * ] + * + * Multiple entries are separated by commas. + */ + Config.AdvancedCustomAttack = [ + { + // check: function (unit) { + // return unit.getEnchant(sdk.enchant.LightningEnchanted); + // }, + // attack: [sdk.skills.Zeal, sdk.skills.Salvation], + // preAttack: sdk.skills.SlowMissiles + }, + ]; + + /** + * 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/core/Attack.js b/d2bs/kolbot/libs/core/Attack.js new file mode 100644 index 000000000..58c16eb91 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attack.js @@ -0,0 +1,2684 @@ +/// +/** + * @filename Attack.js + * @author kolton, theBGuy + * @desc handle player attacks + * + */ + + +const Attack = { + infinity: false, + auradin: false, + monsterObjects: new Set([ + 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 + FAILED_POSITION: 5, + }, + /** + * 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 (notify = true) { + // TODO: properly handle loading wereform and custom files so they work with LazyLoader and get the correct types + if (Config.Wereform) { + ClassAttack.load(me.classid, require("./Attacks/Wereform")); + } else if (Config.CustomClassAttack && FileTools.exists("libs/core/Attacks/" + Config.CustomClassAttack + ".js")) { + console.log("Loading custom attack file"); + ClassAttack.load(me.classid, require("./Attacks/" + Config.CustomClassAttack)); + } else { + ClassAttack.load(me.classid); + } + + if (notify && (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 el of Config.AdvancedCustomAttack) { + if (el.hasOwnProperty("check") && el.hasOwnProperty("preAttack")) { + if (typeof el.check === "function" && el.check(unit)) { + return el.preAttack; + } + } + } + + 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 {Monster | number | string} 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 {Monster | 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") + && !me.inArea(sdk.areas.UberTristram) + && Pather.makePortal()) { + say("kill " + classId); + } + + try { + while (attackCount < Config.MaxAttackCount && target.attackable && !Attack.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[me.classid].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[me.classid].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[me.classid].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} onLoop - Called on each iteration of the main loop + * @property {() => boolean} earlyExit - If returns true, exit the clearing loop. Called on each iteration of the main loop + * @property {() => void} onCleared - Called after all clearing is complete + */ + + /** + * @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 = {}) { + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { + return false; + } + + if (typeof (range) !== "number") { + throw new Error("Attack.clear: range must be a number."); + } + + while (!me.gameReady) { + delay(40); + } + + range === undefined && (range = 25); + + const { spectype, bossId, sortfunc, filter, onLoop, pickit, earlyExit, onCleared } = Object.assign({ + spectype: sdk.monsters.spectype.All, + bossId: undefined, + sortfunc: Attack.sortMonsters, + pickit: true, + filter: undefined, + onLoop: undefined, + earlyExit: undefined, + onCleared: undefined, + }, opts); + + /** + * @param {unknown} unit + * @returns {boolean} + */ + const isMonsterUnit = function (unit) { + return unit && typeof unit === "object" && unit.type === sdk.unittype.Monster; + }; + + /** @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") + // bypass UberTristram check, we can't make a portal there + && (me.inArea(sdk.areas.UberTristram) || Pather.makePortal())) { + say("clear " + (["number", "string"].includes(typeof bossId) ? bossId : bossId.name)); + } + } else { + ({ orgx, orgy } = { orgx: me.x, orgy: me.y }); + } + + /** @type {Monster[]} */ + 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()); + } + + // sometimes boss doesn't get added to monsterList due to distance but we want them in it anyway + if (boss && !monsterList.some((mon) => mon.gid === boss.gid)) { + console.log("Adding boss to monsterList"); + monsterList.push(copyUnit(boss)); + } + + while (start && monsterList.length > 0 && attackCount < Config.MaxAttackCount) { + if (me.dead) return false; + if (typeof onLoop === "function") { + onLoop(); + } + + if (typeof earlyExit === "function" && earlyExit()) { + console.log("ÿc7Cleared ÿc0:: Early exit condition met"); + break; + } + + 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)); + } + + let _currMon = attacks.get(target.gid); + const checkAttackSkill = (!!_currMon && _currMon.attacks % 15 === 0); + const result = ClassAttack[me.classid].doAttack(target, checkAttackSkill); + + 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.classid === sdk.player.class.Paladin && checkSkill === sdk.skills.BlessedHammer; + + if (Config.AttackSkill[secAttack] > -1 + && ( + !Attack.checkResist(target, checkSkill) + || (hammerCheck && !ClassAttack[me.classid].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) + ); + } + if (Config.FastFindItem && pickit && me.classid === sdk.player.class.Barbarian) { + ClassAttack[me.classid].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[me.classid].afterAttack(pickit); + Attack.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)); + } + } + + if (typeof onCleared === "function") { + onCleared(); + } + + return true; + }, + + /** + * @todo Refactor so this can accept prebuilt monsterlist, we have repeat logic with this and clearList + * @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] + * @param {(unit: Monster) => boolean} [shouldAttackCb] + * @returns {AttackResult} + * @todo change to passing an object + */ + clear: function (range, spectype, bossId, sortfunc, pickit = true, shouldAttackCb = () => true) { + while (!me.gameReady) { + delay(40); + } + + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { + return Attack.Result.FAILED; + } + + 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."); + } + + /** @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") + // bypass UberTristram check, we can't make a portal there + && (me.inArea(sdk.areas.UberTristram) || 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()); + } + + // sometimes boss doesn't get added to monsterList due to distance but we want them in it anyway + if (boss && !monsterList.some((mon) => mon.gid === boss.gid)) { + monsterList.push(copyUnit(boss)); + } + + while (start && monsterList.length > 0 && attackCount < Config.MaxAttackCount) { + if (me.dead) return Attack.Result.FAILED; + + 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 + && shouldAttackCb(target) + && ( + 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); + + if (Config.ChargeCast.skill > -1 + && Config.ChargeCast.spectype + && !(target.spectype & Config.ChargeCast.spectype)) { + // 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 be pointless + 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[me.classid].doAttack(target, checkAttackSkill); + + 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; + + if (result === this.Result.FAILED_POSITION && _currMon.attacks < 15) { + // push to the end of the list to try later + monsterList.push(monsterList.shift()); + console.debug("ÿc1Requeuing " + target.name + " " + target.gid + " " + _currMon.attacks); + continue; + } + + const isSpecial = target.isSpecial; + const secAttack = me.barbarian ? (isSpecial ? 2 : 4) : 5; + const checkSkill = Config.AttackSkill[isSpecial ? 1 : 3]; + const hammerCheck = me.classid === sdk.player.class.Paladin && checkSkill === sdk.skills.BlessedHammer; + + if (Config.AttackSkill[secAttack] > -1 + && ( + !Attack.checkResist(target, checkSkill) + || (hammerCheck && !ClassAttack[me.classid].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.useTeleport()) { + 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 + ) { + // It'd be helpful to get a position in the opposite direction of the monster move there and then move back + Packet.flash(me.gid); + } + + 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) + ); + } + if (Config.FastFindItem && pickit && me.classid === sdk.player.class.Barbarian) { + ClassAttack[me.classid].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[me.classid].afterAttack(pickit); + Misc.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 Attack.Result.FAILED; + } + } + + return attackCount > 0 ? Attack.Result.SUCCESS : Attack.Result.NOOP; + }, + + /** + * @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[me.classid].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 || 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); + } + Config.FastFindItem && pickit && ClassAttack[me.classid].findItem(); + Pickit.fastPick(); + } + } else { + if (retry++ > 3) { + monsterList.shift(); + retry = 0; + } + + Packet.flash(me.gid); + } + } else { + monsterList.shift(); + } + } + + if (attackCount > 0) { + ClassAttack[me.classid].afterAttack(true); + Misc.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 {Attack.SecurePositionOptions} [options] + * @returns {boolean} + */ + securePosition: function (x, y, options = {}) { + let tick; + + (typeof x !== "number" || typeof y !== "number") && ({ x, y } = me); + const node = new PathNode(x, y); + /** @type {Required} */ + const clearOptions = Object.assign({ + range: 15, + timer: 3000, + skipBlocked: true, + useRedemption: false, + skipIds: [], + timeout: Time.minutes(5), + }, options); + clearOptions.skipBlocked === true && (clearOptions.skipBlocked = sdk.collision.Ranged); + + const startTime = getTickCount(); + const { range, timer, skipBlocked, useRedemption, skipIds, timeout } = clearOptions; + + while (true) { + node.distance > 5 && Pather.moveTo(node.x, node.y); + + let monster = Game.getMonster(); + let monList = []; + + if (monster) { + do { + if (skipIds.includes(monster.classid)) continue; + if (getDistance(monster, node.x, node.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 true; + } + } else { + this.clearList(monList); + + // reset the timer when there's monsters in range + tick && (tick = false); + } + + if (useRedemption) { + if (me.paladin && Skill.canUse(sdk.skills.Redemption) + && Skill.setSkill(sdk.skills.Redemption, sdk.skills.hand.Right)) { + delay(1000); + } + } + + if (timeout && getTickCount() - startTime >= timeout) { + console.warn("ÿc1Attack.securePosition: Timeout reached, giving up."); + return false; + } + + delay(100); + } + }, + + /** + * @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.adaptiveSearch(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 a single room based on monster spectype + * @param {Room} room - The room to clear + * @param {number} spectype - The monster spectype to clear + * @returns {boolean} + */ + clearRoom: function (room, spectype = 0) { + function getCenter(room) { + let centerX = room.x * 5 + room.xsize / 2; + let centerY = room.y * 5 + room.ysize / 2; + + let adjusted = Pather.getNearestWalkable(centerX, centerY, 18, 3); + return adjusted ? [adjusted[0], adjusted[1]] : [centerX, centerY]; + } + + const currentArea = getArea().id; + + const myRoom = getCenter(room); + const result = Pather.getNearestWalkable(myRoom[0], myRoom[1], 18, 3); + /** @param {Monster} unit */ + const shouldAttack = function (unit) { + return CollMap.coordsInRoom(unit.x, unit.y, room); + }; + + if (result) { + if (Config.DebugMode.Path) { + CollMap.drawRoom(room, "green", true); + } + let node = new PathNode(result[0], result[1]); + + Pather.move( + node, + { retry: 3, clearSettings: { specType: spectype, clearPath: (!Pather.canTeleport()) } } + ); + + if (!this.clear(60, spectype, undefined, undefined, undefined, shouldAttack)) { + return false; + } + } else if (currentArea !== getArea().id) { + // Make sure bot does not get stuck in different area. + Pather.moveToEx( + myRoom[0], myRoom[1], + { retry: 3, clearSettings: { specType: spectype, clearPath: (!Pather.canTeleport()) } } + ); + } + + CollMap.removeHookForRoom(room); + + return true; + }, + + /** + * @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]); + } + + function _walkingRoomSort (a, b) { + let aDist = Pather.getWalkDistance(a[0], a[1], me.area, myRoom[0], myRoom[1]); + let bDist = Pather.getWalkDistance(b[0], b[1], me.area, myRoom[0], myRoom[1]); + return aDist - bDist; + } + + /** + * @param {Room} room + * @returns {[number, number]} + */ + function getCenter (room) { + let centerX = room.x * 5 + room.xsize / 2; + let centerY = room.y * 5 + room.ysize / 2; + + let adjusted = Pather.getNearestWalkable(centerX, centerY, 18, 3); + return adjusted ? [adjusted[0], adjusted[1]] : [centerX, centerY]; + } + + let room = getRoom(); + if (!room) return false; + + const canTele = Pather.canTeleport(); + const currentArea = getArea().id; + const dungeons = [ + sdk.areas.DenofEvil, + sdk.areas.HoleLvl1, + sdk.areas.HoleLvl2, + sdk.areas.PitLvl1, + sdk.areas.PitLvl2, + sdk.areas.CaveLvl1, + sdk.areas.CaveLvl2, + sdk.areas.UndergroundPassageLvl1, + sdk.areas.UndergroundPassageLvl2, + sdk.areas.TowerCellarLvl1, + sdk.areas.TowerCellarLvl2, + sdk.areas.TowerCellarLvl3, + sdk.areas.TowerCellarLvl4, + sdk.areas.TowerCellarLvl5, + sdk.areas.Crypt, + sdk.areas.Mausoleum, + sdk.areas.A2SewersLvl1, + sdk.areas.A2SewersLvl2, + sdk.areas.A2SewersLvl3, + sdk.areas.StonyTombLvl1, + sdk.areas.StonyTombLvl2, + sdk.areas.HallsoftheDeadLvl1, + sdk.areas.HallsoftheDeadLvl2, + sdk.areas.HallsoftheDeadLvl3, + sdk.areas.MaggotLairLvl1, + sdk.areas.MaggotLairLvl2, + sdk.areas.MaggotLairLvl3, + sdk.areas.AncientTunnels, + sdk.areas.ClawViperTempleLvl1, + sdk.areas.ClawViperTempleLvl2, + sdk.areas.TalRashasTomb1, + sdk.areas.TalRashasTomb2, + sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, + sdk.areas.TalRashasTomb5, + sdk.areas.TalRashasTomb6, + sdk.areas.TalRashasTomb7, + ]; + + if (!canTele && dungeons.includes(me.area) && (Config.DebugMode.Path || Config.UseExperimentalClearLevel)) { + return Attack.clearLevelWalk(spectype, cb); + } + console.time("clearLevel"); + console.info(true, getAreaName(me.area)); + + let myRoom, previousArea; + let rooms = []; + let count = 0; + /** @type {Text[]} */ + let hooks = []; + + /** @param {Text} hook */ + const clearHook = function (hook) { + hook && hook.remove(); + }; + + do { + rooms.push([...getCenter(room), copyObj(room)]); + } 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 = getCenter(room); + } + } + + rooms.sort(RoomSort); + room = rooms.shift(); + + let result = Pather.getNearestWalkable(room[0], room[1], 18, 3); + + if (result) { + if (Config.DebugMode.Path) { + CollMap.drawRoom(room[2], "green"); + hooks.push(new Text((++count).toString(), room[0], room[1], 2, 1, null, true)); + } + let node = new PathNode(result[0], result[1]); + + if (node.distance < 20 && !canTele && node.mobCount() === 0) { + if (Config.DebugMode.Path) { + console.debug("ÿc1Skipping room " + room[0] + " " + room[1]); + CollMap.drawRoom(room[2], "red", true); + } + } else { + Pather.move( + node, + { 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)); + CollMap.removeHooks(); + hooks.forEach(clearHook); + 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.has(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) { + if (e instanceof ScriptError) { + throw 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; + }, + + /** + * @deprecated - Use Misc.openChests instead + * @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); + const grid = this.buildGrid(unit.x - distance, unit.x + distance, unit.y - distance, unit.y + distance, spread); + + if (!grid.length) return false; + grid.sort(function (a, b) { + return 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"); + } + + /** @type {(PathNode & { coll: number })[]} */ + 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 {Monster} 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.length && Config.SkipException.includes(unit.name)) { + console.log("ÿc1Skip Exception: " + unit.name); + return false; + } + + if (Config.SkipId.includes(unit.classid)) { + return true; + } + + if (Config.AdvancedSkipCheck.length) { + for (let check of Config.AdvancedSkipCheck) { + if (typeof check === "function") { + if (check(unit)) { + return true; + } + } else if (typeof check === "object") { + let shouldSkip = Object.keys(check).length > 0; + + for (let key in check) { + switch (key) { + case "classid": + if (check[key] === (unit.classid)) { + shouldSkip = shouldSkip && true; + } else { + shouldSkip = false; + } + break; + case "name": + if (check[key].includes(unit.name.toLowerCase())) { + shouldSkip = shouldSkip && true; + } else { + shouldSkip = false; + } + break; + case "spectype": + if (check[key] === (unit.spectype)) { + shouldSkip = shouldSkip && true; + } else { + shouldSkip = false; + } + break; + case "enchant": + if (Array.isArray(check[key])) { + let skipEnchant = check[key].every(function (enchant) { + return unit.getEnchant(enchant); + }); + if (skipEnchant) { + shouldSkip = shouldSkip && true; + } else { + shouldSkip = false; + } + } + break; + case "aura": + if (Array.isArray(check[key])) { + let skipAura = check[key].every(function (aura) { + return unit.getState(aura); + }); + if (skipAura) { + shouldSkip = shouldSkip && true; + } else { + shouldSkip = false; + } + } + break; + case "immunity": + if (Array.isArray(check[key])) { + let skipImmune = check[key].every(function (immune) { + return !Attack.checkResist(unit, immune); + }); + if (skipImmune) { + shouldSkip = shouldSkip && true; + } else { + shouldSkip = false; + } + } + break; + } + + if (!shouldSkip) { + break; + } + } + + if (shouldSkip) { + console.log("ÿc1Skip Advanced Check: " + unit.name); + 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 {DamageType | "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 {DamageType | "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 + * @param {Monster | Player} unit + * @param {number} val + * @param {number} maxres + * @returns {boolean} + */ + 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; + } + + if (Config.ImmunityException.includes(damageType)) { + return true; + } + + // TODO: sometimes unit is out of range of conviction so need to check that + // baal in throne room doesn't have getState + if (Attack.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 ( + Attack.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 || !unit.type || !unit.isMonster) return false; + const skillElems = Config.AttackSkill.map(function (skill) { + return Attack.getSkillElement(skill); + }); + // Unique/Champion + if (unit.isSpecial) { + if (Attack.checkResist(unit, skillElems[1]) + || Attack.checkResist(unit, skillElems[2])) { + return true; + } + } else { + if (Attack.checkResist(unit, skillElems[3]) + || Attack.checkResist(unit, skillElems[4])) { + return true; + } + } + + if (skillElems.length === 7) { + if (Attack.checkResist(unit, skillElems[5]) + || Attack.checkResist(unit, skillElems[6])) { + return true; + } + } + + // Secondary if monster is immune to our existing backup skill + // i.e. Hammerdins having holybolt as main backup but using smite here as third backup + if (skillElems.length === 9) { + if (Attack.checkResist(unit, skillElems[7]) + || Attack.checkResist(unit, skillElems[8])) { + return true; + } + } + 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 canTele = !walk && Pather.useTeleport(); + const { x: orgX, y: orgY } = me; + + /** @param {PathNode} node */ + const handleMove = function (node) { + switch (walk) { + case 1: + return Pather.walkTo(node.x, node.y, 2); + case 2: + if (node.distance < 6 && !CollMap.checkColl(me, node, sdk.collision.WallOrRanged)) { + return Pather.walkTo(node.x, node.y, 2); + } else { + return Pather.move(node, { retry: 1, allowPicking: !force }); + } + default: + return Pather.move(node, { retry: 1, allowPicking: !force }); + } + }; + + for (let n = 0; n < 3; n++) { + /** @type {PathNode[]} */ + 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); + let node = new PathNode(cx, cy); + + // ignore this spot as it's too close to our current position when we are forcing a new location + if (force && node.distance < distance) continue; + if (Pather.checkSpot(node.x, node.y, sdk.collision.BlockWall, false)) { + coords.push(node); + } + } + if (!coords.length) continue; + + coords.sort(Sort.units); + + for (let coord of coords) { + // check if position is valid + if (CollMap.checkColl(coord, unit, coll, 1)) { + continue; + } + if (!canTele) { + if (Config.DebugMode.Path) { + console.debug("coord", coord, " dist", coord.distance); + new Line(coord.x - 3, coord.y, coord.x + 3, coord.y, 0x9B, true); + new Line(coord.x, coord.y - 3, coord.x, coord.y + 3, 0x9B, true); + } + + let walkDist = Pather.getWalkDistance(coord.x, coord.y); + if (walkDist > unit.distance) { + if (Config.DebugMode.Path) { + console.debug( + "Skipping position due to walk distance being too far." + + "\n - DistanceToMonster: " + unit.distance + + "\n - DistanceToPosition: " + walkDist + ); + continue; + } + } + } + if (handleMove(coord)) { + 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(function (state) { + return !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; + const cState = Skill.getState(skill); + + if (skill > 0 + && Attack.checkResist(unit, skill) + && (!cState || !unit.getState(cState)) + && (!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..f9191e597 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Amazon.js @@ -0,0 +1,275 @@ +/** +* @filename Amazon.js +* @author kolton, theBGuy +* @desc Amazon attack sequence +* +*/ + +(function (module) { + module.exports = { + classid: sdk.player.class.Amazon, + 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; + } + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Attacks/Assassin.js b/d2bs/kolbot/libs/core/Attacks/Assassin.js new file mode 100644 index 000000000..be03ede79 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Assassin.js @@ -0,0 +1,299 @@ +/** +* @filename Assassin.js +* @author kolton, theBGuy +* @desc Assassin attack sequence +* +*/ + +(function (module) { + module.exports = { + lastTrapPos: new PathNode(), + 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; + }, + + /** + * @param {Monster} unit + */ + 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? + /** + * @param {Monster | Unit} unit + * @param {number} amount + */ + placeTraps: function (unit, amount = 5) { + let traps = 0; + this.lastTrapPos.update({ 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; + }, + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Attacks/Barbarian.js b/d2bs/kolbot/libs/core/Attacks/Barbarian.js new file mode 100644 index 000000000..7372529cf --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Barbarian.js @@ -0,0 +1,302 @@ +/** +* @filename Barbarian.js +* @author kolton, theBGuy +* @desc Barbarian attack sequence +* +*/ + +(function (module) { + /** + * @todo + * - Add howl + */ + module.exports = { + /** + * @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_POSITION; + } + } + + !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_POSITION; + } + } + + !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; + } + const councilClassIds = [sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3]; + if (councilClassIds.indexOf(unit.classid) === -1 && unit.spectype === sdk.monsters.spectype.All) { + // why ignore all normal monsters? + return false; + } + + const waterWatcherIds = [sdk.monsters.WaterWatcherHead, sdk.monsters.WaterWatcherLimb]; + if (waterWatcherIds.includes(unit.classid)) { + 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); + })); + } + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Attacks/Druid.js b/d2bs/kolbot/libs/core/Attacks/Druid.js new file mode 100644 index 000000000..2d4a91bb6 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Druid.js @@ -0,0 +1,212 @@ +/** +* @filename Druid.js +* @author kolton, theBGuy +* @desc Druid attack sequence +* +*/ + +(function (module) { + module.exports = { + /** + * @param {Monster} unit + * @param {boolean} preattack + */ + 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); + }, + + /** + * @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.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; + } + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Attacks/Necromancer.js b/d2bs/kolbot/libs/core/Attacks/Necromancer.js new file mode 100644 index 000000000..483cf3792 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Necromancer.js @@ -0,0 +1,576 @@ +/** +* @filename Necromancer.js +* @author kolton, theBGuy +* @desc Necromancer attack sequence +* +*/ + +(function (module) { + module.exports = { + novaTick: 0, + maxSkeletons: 0, + maxMages: 0, + maxRevives: 0, + + setArmySize: function () { + ClassAttack[me.classid].maxSkeletons = Config.Skeletons === "max" + ? Skill.getMaxSummonCount(sdk.skills.RaiseSkeleton) + : Config.Skeletons; + ClassAttack[me.classid].maxMages = Config.SkeletonMages === "max" + ? Skill.getMaxSummonCount(sdk.skills.RaiseSkeletalMage) + : Config.SkeletonMages; + ClassAttack[me.classid].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))); + } + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Attacks/Paladin.js b/d2bs/kolbot/libs/core/Attacks/Paladin.js new file mode 100644 index 000000000..5df622631 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Paladin.js @@ -0,0 +1,413 @@ +/** +* @filename Paladin.js +* @author kolton, theBGuy +* @desc Paladin attack sequence +* +*/ + +(function (module) { + module.exports = { + 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); + // } + }, + + /** + * @param {Monster} unit + * @param {number} attackSkill + * @param {number} aura + */ + 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; + }, + + /** + * @param {Monster} unit + * @returns {boolean} + */ + 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; + }, + + /** + * @param {number} x + * @param {number} y + */ + 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; + } + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Attacks/Sorceress.js b/d2bs/kolbot/libs/core/Attacks/Sorceress.js new file mode 100644 index 000000000..50888050a --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Sorceress.js @@ -0,0 +1,272 @@ +/** +* @filename Sorceress.js +* @author kolton, theBGuy +* @desc Sorceress attack sequence +* +*/ + +(function (module) { + module.exports = { + /** @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; + } + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Attacks/Wereform.js b/d2bs/kolbot/libs/core/Attacks/Wereform.js new file mode 100644 index 000000000..f37ab1148 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Wereform.js @@ -0,0 +1,190 @@ +/** +* @filename Wereform.js +* @author kolton, theBGuy +* @desc Wereform attack sequence +* +*/ + +// todo - handle a Bear necro summonmancer +(function (module) { + module.exports = (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, + + /** + * @param {Monster | Player} unit + * @param {boolean} preattack + */ + 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); + }, + + /** + * @param {Monster | Player} unit + * @param {number} skill + * @returns {AttackResult} + */ + 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; + } + }; + })(); +})(module); 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/ClassAttack.js b/d2bs/kolbot/libs/core/ClassAttack.js new file mode 100644 index 000000000..af33ff423 --- /dev/null +++ b/d2bs/kolbot/libs/core/ClassAttack.js @@ -0,0 +1,27 @@ +/** +* @filename ClassAttack.js +* @author theBGuy +* @desc Class specific attack sequences +* +*/ + +// each ClassAttack functionality is loaded into this object when it's needed +// for the actual function files @see core/Attacks/ +const ClassAttack = (function () { + const LazyLoader = require("../modules/LazyLoader"); + + /** + * @type {Map} + */ + const modulePathMap = new Map([ + [sdk.player.class.Amazon.toString(), "./Attacks/Amazon"], + [sdk.player.class.Assassin.toString(), "./Attacks/Assassin"], + [sdk.player.class.Barbarian.toString(), "./Attacks/Barbarian"], + [sdk.player.class.Druid.toString(), "./Attacks/Druid"], + [sdk.player.class.Necromancer.toString(), "./Attacks/Necromancer"], + [sdk.player.class.Paladin.toString(), "./Attacks/Paladin"], + [sdk.player.class.Sorceress.toString(), "./Attacks/Sorceress"], + ]); + + return LazyLoader(modulePathMap); +})(); diff --git a/d2bs/kolbot/libs/core/CollMap.js b/d2bs/kolbot/libs/core/CollMap.js new file mode 100644 index 000000000..e5778bddf --- /dev/null +++ b/d2bs/kolbot/libs/core/CollMap.js @@ -0,0 +1,295 @@ +/** +* @filename CollMap.js +* @author kolton +* @desc manipulate map collision data +* +*/ + +const CollMap = new function () { + this.rooms = []; + this.maps = []; + /** @type {{ room: Room, lines: 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 {Hook} hook */ + const clearHook = function (hook) { + hook && hook.remove(); + }; + + /** + * @this {CollMap} + * @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(clearHook); + this.hooks.splice(index, 1); + } + }; + + /** @this {CollMap} */ + this.removeHooks = function () { + this.hooks.forEach(hook => hook.lines.forEach(clearHook)); + 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..f828d084f --- /dev/null +++ b/d2bs/kolbot/libs/core/Common.js @@ -0,0 +1,28 @@ +/** +* @filename Common.js +* @author theBGuy +* @desc collection of functions shared between muliple scripts +* +*/ + +// each common functionality is loaded into this object when it's needed +// for the actual function files @see core/Common/ +const Common = (function () { + const LazyLoader = require("../modules/LazyLoader"); + + /** + * @type {Map} + */ + const modulePathMap = new Map([ + ["Ancients", "./Common/Ancients"], + ["Baal", "./Common/Baal"], + ["Cain", "./Common/Cain"], + ["Cows", "./Common/Cows"], + ["Diablo", "./Common/Diablo"], + ["Leecher", "./Common/Leecher"], + ["Smith", "./Common/Smith"], + ["Toolsthread", "./Common/Tools"], + ]); + + return LazyLoader(modulePathMap); +})(); diff --git a/d2bs/kolbot/libs/core/Common/Ancients.js b/d2bs/kolbot/libs/core/Common/Ancients.js new file mode 100644 index 000000000..f99a56f2c --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Ancients.js @@ -0,0 +1,151 @@ +/** +* @filename Ancients.js +* @author theBGuy +* @desc Handle Ancients quest +* +*/ + +(function (module) { + module.exports = 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); + }; + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Common/Baal.js b/d2bs/kolbot/libs/core/Common/Baal.js new file mode 100644 index 000000000..23f0f2247 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Baal.js @@ -0,0 +1,342 @@ +/** +* @filename Baal.js +* @author theBGuy +* @desc Handle Baal functions +* +*/ + +(function (module) { + module.exports = new function () { + 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); + } + } + + + const nodes = [ + new PathNode(15097, 5054), + new PathNode(15079, 5014), + new PathNode(15085, 5053), + new PathNode(15085, 5040), + new PathNode(15098, 5040), + new PathNode(15099, 5022), + new PathNode(15086, 5024), + new PathNode(15079, 5014) + ].sort(Sort.units); + + while (nodes.length > 0) { + const node = nodes.shift(); + + // no mobs at that next, skip it + if (node.distance < 35 && node.mobCount({ range: 30 }) === 0) { + continue; + } + + Pather.move(node); + Attack.clear(30); + + nodes.sort(Sort.units); + } + return true; + }; + + 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[me.classid].checkTraps({ x: 15094, y: 5028 }); + + if (check) { + return ClassAttack[me.classid].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 (hurtPercent = 0) { + if (me.inArea(sdk.areas.ThroneofDestruction)) { + if (Config.PublicMode && Loader.scriptName() === "Baal" && !Config.Baal.Silent) { + 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); + hurtPercent > 0 + ? Attack.hurt(sdk.monsters.Baal, hurtPercent) + : Attack.kill(sdk.monsters.Baal); + Pickit.pickItems(); + + return true; + } + + return false; + }; + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Common/Cain.js b/d2bs/kolbot/libs/core/Common/Cain.js new file mode 100644 index 000000000..8ba7315f5 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Cain.js @@ -0,0 +1,139 @@ +/** +* @filename Cain.js +* @author theBGuy +* @desc Complete cain quest +* +*/ + +(function (module) { + module.exports = { + /** + * @param {ObjectUnit} stone + * @returns {boolean} + */ + 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, { range: 40, duration: 3000, skipBlocked: 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; + } + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Common/Cows.js b/d2bs/kolbot/libs/core/Common/Cows.js new file mode 100644 index 000000000..953d21ac7 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Cows.js @@ -0,0 +1,194 @@ +/** +* @filename Cows.js +* @author theBGuy +* @desc clear Moo Moo Farm +* +*/ + +(function (module) { + module.exports = new function Cows () { + /** @type {Room[]} */ + this._badRooms = []; + /** @type {{ id: number, area: number, x: number, y: number } | null} */ + this._kingCoords = null; + + this.buildCowRooms = function () { + /** @type {[number, number, Room][]} */ + const finalRooms = []; + /** @type {Set} */ + const indexes = new Set(); + + const kingPreset = Game.getPresetMonster(sdk.areas.MooMooFarm, sdk.monsters.preset.TheCowKing); + this._kingCoords = kingPreset.realCoords(); + /** @type {Room[]} */ + const badRooms = getRoom(this._kingCoords.x, this._kingCoords.y).getNearby(); + /** + * @param {Room} room + * @returns {string} + */ + const roomKey = function (room) { + return room.x + ":" + room.y; + }; + + for (let room of badRooms) { + this._badRooms.push(room); + if (Config.DebugMode.Path) { + CollMap.drawRoom(room, "red"); + } + const badRooms2 = room.getNearby(); + + for (let badRoom of badRooms2) { + if (!indexes.has(roomKey(badRoom))) { + if (Config.DebugMode.Path) { + CollMap.drawRoom(badRoom, "white"); + } + indexes.add(roomKey(badRoom)); + } + } + } + + let room = getRoom(); + + do { + if (!indexes.has(roomKey(room))) { + finalRooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2, copyObj(room)]); + } + } while (room.getNext()); + + return finalRooms; + }; + + // add soloplays kingTracker? + this.clearCowLevel = function () { + /** + * @param {Room} a + * @param {Room} b + * @returns {number} + */ + function roomSort(a, b) { + const aSeen = seen.has(JSON.stringify(a)); + const bSeen = seen.has(JSON.stringify(b)); + + if (aSeen && !bSeen) { + return 1; // a is seen, b is not seen, so b should come first + } else if (!aSeen && bSeen) { + return -1; // b is seen, a is not seen, so a should come first + } + + 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 count = 0; + /** @type {[number, number]} */ + let myRoom; + /** @type {Partial} */ + let room; + /** @type {Set} */ + const seen = new Set(); + const rooms = this.buildCowRooms(); + + /** @type {Text[]} */ + const hooks = []; + + /** @param {Text} hook */ + const clearHook = function (hook) { + hook && hook.remove(); + }; + + // Check if the starting room is in badRooms + if (!Pather.useTeleport()) { + let startRoom = getRoom(me.x, me.y); + let startRoomCoords = [startRoom.x * 5 + startRoom.xsize / 2, startRoom.y * 5 + startRoom.ysize / 2]; + let startRoomInBadRooms = this._badRooms.some(function (badRoom) { + return CollMap.coordsInRoom(startRoomCoords[0], startRoomCoords[1], badRoom); + }); + + if (startRoomInBadRooms) { + console.warn("Starting room is in badRooms, finding the closest safe room..."); + rooms.sort(function (a, b) { + // eslint-disable-next-line max-len + return getDistance(startRoomCoords[0], startRoomCoords[1], a[0], a[1]) - getDistance(startRoomCoords[0], startRoomCoords[1], b[0], b[1]); + }); + + for (let room of rooms) { + let safeRoom = !this._badRooms.some(function (badRoom) { + return CollMap.coordsInRoom(room[0], room[1], badRoom); + }); + + if (safeRoom) { + let result = Pather.getNearestWalkable(room[0], room[1], 10, 2); + if (result) { + Pather.moveTo(result[0], result[1], 3); + myRoom = [result[0], result[1]]; + break; + } + } + } + } + } + + RoomLoop: + 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); + room = rooms.shift(); + seen.add(JSON.stringify(room)); + const result = Pather.getNearestWalkable(room[0], room[1], 10, 2); + + if (result) { + if (!Pather.useTeleport()) { + // lets avoid running through the badrooms + let path = getPath( + me.area, + result[0], result[1], + me.x, me.y, + 0, + Pather.walkDistance + ); + if (path) { + for (let node of path) { + let pathInBadRoom = this._badRooms.some(function (badRoom) { + return CollMap.coordsInRoom(node.x, node.y, badRoom); + }); + if (pathInBadRoom) { + if (!seen.has(JSON.stringify(room))) { + seen.add(JSON.stringify(room)); + rooms.push(room); + } else { + console.warn("Seen this room before and the path still takes us through the badrooms, skip it"); + } + continue RoomLoop; + } + } + } + } + if (Config.DebugMode.Path) { + CollMap.drawRoom(room[2], "green"); + hooks.push(new Text((++count).toString(), room[0], room[1], 2, 1, null, true)); + } + Pather.moveTo(result[0], result[1], 3); + if (!Attack.clear(30)) return false; + } + } + + CollMap.removeHooks(); + hooks.forEach(clearHook); + + return true; + }; + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Common/Diablo.js b/d2bs/kolbot/libs/core/Common/Diablo.js new file mode 100644 index 000000000..5eca918b4 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Diablo.js @@ -0,0 +1,875 @@ +/** +* @filename Diablo.js +* @author theBGuy +* @desc Handle Diablo related functions +* +*/ + +(function (module) { + const Events = new (require("../../modules/Events")); + + const _Diablo = { + on: Events.on, + off: Events.off, + once: Events.once, + emit: Events.emit, + + diabloSpawned: false, + diaWaitTime: Time.seconds(30), + clearRadius: 30, + clearType: sdk.monsters.spectype.All, + done: false, + waitForGlow: false, + sealOrder: [], + openedSeals: [], + vizLayout: -1, + seisLayout: -1, + infLayout: -1, + entranceCoords: new PathNode(7790, 5544), + starCoords: new PathNode(7791, 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: [], + + /** @param {number[]} bytes */ + diabloLightsEvent: function (bytes = []) { + if (!bytes || bytes.length !== 2) { + return; + } + if (me.act !== 4) { + return; + } + if (bytes[0] === 0x89 && bytes[1] === 0x0C) { + console.debug("Diablo lights event detected"); + _Diablo.diabloSpawned = true; + Misc._diabloSpawned = true; + _Diablo.emit("diablospawned"); + } + }, + + addLightsEventListener: function () { + addEventListener("gamepacket", _Diablo.diabloLightsEvent); + }, + + removeLightsEventListener: function () { + removeEventListener("gamepacket", _Diablo.diabloLightsEvent); + }, + + diaSpawnWatcher: ( + /** @param {null | (() => void)} onSpawn */ + function (onSpawn = null) { + let diaTick = 0; + + return function () { + if (Common.Diablo.done) return false; + // don't throw if we are doing chores + if (Town.choresActive) return true; + // check every 1/4 second + if (getTickCount() - diaTick < 250) return true; + diaTick = getTickCount(); + + if (Common.Diablo.diabloSpawned) { + if (typeof onSpawn === "function") { + onSpawn(); + } + throw new ScriptError("Diablo spawned"); + } + return true; + }; + } + ), + + /** + * @param {Monster} a + * @param {Monster} b + * @returns {number} + */ + 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); + }, + + /** + * @param {number} seal + * @param {number} value + * @returns {number} + */ + getLayout: function (seal, value) { + let sealPreset = Game.getPresetObject(sdk.areas.ChaosSanctuary, seal); + if (!sealPreset) throw new Error("Seal preset not found. Can't continue."); + let _seal = sealPreset.realCoords(); + + if (_seal.y === value || _seal.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" + _Diablo.vizLayout = this.getLayout(sdk.objects.DiabloSealVizier, 5275); + // 1 = "2", 2 = "5" + _Diablo.seisLayout = this.getLayout(sdk.objects.DiabloSealSeis, 7773); + // 1 = "I", 2 = "J" + _Diablo.infLayout = this.getLayout(sdk.objects.DiabloSealInfector, 7893); + }, + + /** + * @param {number} classid + * @returns {number} + */ + getSealDistance: function (classid) { + let sealPreset = Game.getPresetObject(sdk.areas.ChaosSanctuary, classid); + if (!sealPreset) throw new Error("Seal preset not found. Can't continue."); + let seal = sealPreset.realCoords(); + return seal.distance; + }, + + /** + * Follow static path + * @param {number[][]} path + * @returns {void} + */ + followPath: function (path) { + if (Config.Diablo.Fast) { + let last = path.last(); + let lastNode = new PathNode(last[0], last[1]); + Pather.moveToUnit(lastNode); + return; + } + + const canTele = Pather.canTeleport(); + + 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; + } + + let node = new PathNode(path[i][0], path[i][1]); + Pather.moveTo(node.x, node.y, 3, node.distance > 50); + Attack.clear(this.clearRadius, this.clearType, false, _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 - only think we should do this with tele chars + if (canTele && i === 5 && path.length > 8) { + path = path.slice(3); + i = 0; + } + } + }, + + clearStrays: function () { + const startPos = new PathNode(me.x, me.y); + let monster = Game.getMonster(); + + if (monster) { + do { + if (!monster || !monster.attackable) continue; + for (let i = 0; i < this.cleared.length; i += 1) { + let node = new PathNode(this.cleared[i][0], this.cleared[i][1]); + if (node.distanceTo(monster) < 30 + && Attack.validSpot(monster.x, monster.y) + ) { + Pather.moveToUnit(monster); + Attack.clear(15, this.clearType, false, _Diablo.sort); + + break; + } + } + } while (monster.getNext()); + } + + startPos.distance > 5 && Pather.move(startPos); + + return true; + }, + + /** + * @param {number[] | string[]} sealOrder + * @param {boolean} openSeals + */ + runSeals: function (sealOrder, openSeals = true) { + console.log("seal order: " + sealOrder); + _Diablo.sealOrder = sealOrder; + const 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(function (seal) { + if (_Diablo.diabloSpawned) return; + try { + seals[seal](); + } catch (e) { + if (e instanceof ScriptError) { + throw e; + } + + if (e instanceof Error && e.message.includes("Failed to kill")) { + if (Loader.scriptName() === "DiabloHelper") { + console.warn(e); + } else { + throw e; + } + } + } + }); + }, + + /** + * Attempt casting telekinesis on seal to activate it + * @param {Unit} seal + * @returns {boolean} + */ + tkSeal: function (seal) { + if (!Skill.useTK(seal)) return false; + + const checkSeal = function () { + return seal.mode; + }; + + for (let i = 0; i < 5; i++) { + seal.distance > 20 && Attack.getIntoPosition(seal, 18, sdk.collision.WallOrRanged); + + if (Packet.telekinesis(seal) && Misc.poll(checkSeal, 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) { + console.debug("Seal " + classid + " is already open."); + 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 && me.classid === sdk.player.class.Assassin) { + let check = ClassAttack[me.classid].checkTraps({ x: 7899, y: 5293 }); + check && ClassAttack[me.classid].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) { + const vizier = getLocaleString(sdk.locale.monsters.GrandVizierofChaos); + if (Attack.haveKilled(vizier)) { + return true; + } + console.log("Viz layout " + _Diablo.vizLayout); + const path = (_Diablo.vizLayout === 1 ? this.starToVizA : this.starToVizB); + const distCheck = path.last(); + + if (Config.Diablo.SealLeader || Config.Diablo.Fast) { + _Diablo.vizLayout === 1 ? Pather.moveTo(7708, 5269) : Pather.moveTo(7647, 5267); + Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, { range: 35, duration: 3000, skipBlocked: true }); + Config.Diablo.SealLeader && Pather.makePortal() && say("in"); + } + + if (distCheck.distance > 30) { + this.followPath(path); + } + + const seals = [sdk.objects.DiabloSealVizier2, sdk.objects.DiabloSealVizier]; + + const tryOpenSeals = function () { + if (!openSeal) return true; + for (let s of seals) { + if (!_Diablo.openSeal(s)) { + return false; + } + } + return true; + }; + + if (!tryOpenSeals()) { + throw new Error("Failed to open Vizier seals."); + } + + delay(1 + me.ping); + const cb = function () { + let viz = Game.getMonster(vizier); + 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 + */ + _Diablo.vizLayout === 1 + ? Pather.moveToEx(7691, 5292, { callback: cb }) + : Pather.moveToEx(7695, 5316, { callback: cb }); + + + try { + if (!_Diablo.getBoss(vizier)) { + throw new Error("Failed to kill Vizier"); + } + } catch (e) { + if (e instanceof ScriptError) { + throw e; + } + // sometimes we fail just because we aren't in range, move back towards star while checking + Pather.moveToEx(this.starCoords.x, this.starCoords.y, { minDist: 15, callback: cb }); + if (!_Diablo.getBoss(vizier)) { + throw new Error("Failed to kill Vizier"); + } + } + + Config.Diablo.SealLeader && say("out"); + + return true; + }, + + /** + * @param {boolean} openSeal + * @returns {boolean} + */ + seisSeal: function (openSeal = true) { + const deSeis = getLocaleString(sdk.locale.monsters.LordDeSeis); + if (Attack.haveKilled(deSeis)) { + return true; + } + console.log("Seis layout " + _Diablo.seisLayout); + const path = (_Diablo.seisLayout === 1 ? this.starToSeisA : this.starToSeisB); + const distCheck = path.last(); + + if (Config.Diablo.SealLeader || Config.Diablo.Fast) { + _Diablo.seisLayout === 1 ? Pather.moveTo(7767, 5147) : Pather.moveTo(7820, 5147); + Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, { range: 35, duration: 3000, skipBlocked: true }); + Config.Diablo.SealLeader && Pather.makePortal() && say("in"); + } + + if (distCheck.distance > 30) { + this.followPath(path); + } + + if (openSeal && !_Diablo.openSeal(sdk.objects.DiabloSealSeis)) { + throw new Error("Failed to open de Seis seal."); + } + + const cb = function () { + let seis = Game.getMonster(deSeis); + return seis && (seis.distance < Skill.getRange(Config.AttackSkill[1]) || seis.dead); + }; + + _Diablo.seisLayout === 1 + ? Pather.moveToEx(7798, 5194, { callback: cb }) + : Pather.moveToEx(7796, 5155, { callback: cb }); + + try { + if (!_Diablo.getBoss(deSeis)) { + throw new Error("Failed to kill de Seis"); + } + } catch (e) { + if (e instanceof ScriptError) { + throw 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(deSeis); + return seis && (seis.distance < 30 || seis.dead); + } }); + if (!_Diablo.getBoss(deSeis)) { + throw new Error("Failed to kill de Seis"); + } + } + + Config.Diablo.SealLeader && say("out"); + + return true; + }, + + /** + * @param {boolean} openSeal + * @returns {boolean} + */ + infectorSeal: function (openSeal = true) { + const infector = getLocaleString(sdk.locale.monsters.InfectorofSouls); + if (Attack.haveKilled(infector)) { + return true; + } + Precast.doPrecast(true); + console.log("Inf layout " + _Diablo.infLayout); + const path = (_Diablo.infLayout === 1 ? this.starToInfA : this.starToInfB); + const distCheck = path.last(); + const seals = [sdk.objects.DiabloSealInfector2, sdk.objects.DiabloSealInfector]; + + if (Config.Diablo.SealLeader || Config.Diablo.Fast) { + _Diablo.infLayout === 1 ? Pather.moveTo(7860, 5314) : Pather.moveTo(7909, 5317); + Config.Diablo.SealLeader && Attack.securePosition(me.x, me.y, { range: 35, duration: 3000, skipBlocked: true }); + Config.Diablo.SealLeader && Pather.makePortal() && say("in"); + } + + if (distCheck.distance > 70) { + this.followPath(path); + } + + const cb = function () { + let inf = Game.getMonster(infector); + return inf && (inf.distance < Skill.getRange(Config.AttackSkill[1]) || inf.dead); + }; + + const moveToLoc = function () { + if (_Diablo.infLayout === 1) { + if (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 }); + } + }; + + const tryOpenSeals = function () { + if (!openSeal) return true; + for (let s of seals) { + if (!_Diablo.openSeal(s)) { + return false; + } + } + return true; + }; + + try { + if (Config.Diablo.Fast) { + if (!tryOpenSeals()) { + throw new Error("Failed to open Infector seals."); + } + moveToLoc(); + + if (!_Diablo.getBoss(infector)) { + throw new Error("Failed to kill Infector"); + } + } else { + if (openSeal && !_Diablo.openSeal(sdk.objects.DiabloSealInfector)) { + throw new Error("Failed to open Infector seals."); + } + moveToLoc(); + + if (!_Diablo.getBoss(infector)) { + throw new Error("Failed to kill Infector"); + } + if (openSeal && !_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(_Diablo.sealOrder.last()) && Misc.poll(() => { + if (_Diablo.diabloSpawned) return true; + + let lastSeal = Game.getObject(sdk.objects.DiabloSealInfector2); + if (lastSeal && lastSeal.mode) { + return true; + } + return false; + }, Time.minutes(3), 1000); + } + } catch (e) { + if (e instanceof ScriptError) { + throw e; + } + if (e instanceof Error && e.message === "Diablo not found") { + throw e; + } + if (e instanceof Error && e.message === "Failed to kill Infector") { + // sometimes we fail just because we aren't in range, + Pather.moveToEx(this.starCoords.x, this.starCoords.y, { minDist: 15, callback: () => { + let inf = Game.getMonster(infector); + return inf && (inf.distance < 30 || inf.dead); + } }); + if (!_Diablo.getBoss(infector)) { + throw new Error("Failed to kill Infector"); + } + } + } + + Config.Diablo.SealLeader && say("out"); + + return true; + }, + + /** + * @param {string | number} name + * @param {number} [amount=5] + * @returns {boolean} + */ + hammerdinPreAttack: function (name, amount = 5) { + if (!me.paladin || !me.getSkill(sdk.skills.BlessedHammer)) return false; + + 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 pos of positions) { + const [offX, offY] = pos; + // check if we can move there + if (Attack.validSpot(target.x + offX, target.y + offY)) { + Pather.moveTo(target.x + offX, target.y + offY); + Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right); + + for (let n = 0; n < amount; n += 1) { + Skill.cast(sdk.skills.BlessedHammer, sdk.skills.hand.Left); + target = Game.getMonster(name); + if (!target || !target.attackable) return true; + } + + return true; + } + } + + return false; + }, + + /** + * @param {string} id + * @returns {boolean} + */ + preattack: function (id) { + const coords = (function () { + switch (id) { + case getLocaleString(sdk.locale.monsters.GrandVizierofChaos): + return _Diablo.vizLayout === 1 ? [7676, 5295] : [7684, 5318]; + case getLocaleString(sdk.locale.monsters.LordDeSeis): + return _Diablo.seisLayout === 1 ? [7778, 5216] : [7775, 5208]; + case getLocaleString(sdk.locale.monsters.InfectorofSouls): + return _Diablo.infLayout === 1 ? [7913, 5292] : [7915, 5280]; + default: + return []; + } + })(); + if (!coords.length) return false; + + let boss = Game.getMonster(id); + if (boss && boss.dead) return true; + + 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[me.classid].checkTraps({ x: coords[0], y: coords[1] }); + + if (trapCheck) { + ClassAttack[me.classid].placeTraps({ x: coords[0], y: coords[1] }, 5); + + return true; + } + } + + break; + } + + return false; + }, + + /** + * @param {string | number} name + * @returns {boolean} + */ + getBoss: function (name) { + // reasonable timeout to find boss before assuming something went wrong + const timeout = getTickCount() + Time.minutes(3); + + 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; + } + + if (getTickCount() > timeout) { + throw new Error(name + " not found"); + } + } + } + + for (let i = 0; i < 16; i += 1) { + let boss = Game.getMonster(name); + + if (boss) { + _Diablo.hammerdinPreAttack(name, 8); + if (!Config.Diablo.Fast && boss.distance > 40) { + // + } + 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 (!me.inArea(sdk.areas.ChaosSanctuary)) { + if (!me.inArea(sdk.areas.PandemoniumFortress)) { + Town.goToTown(4); + } + + Town.move("portalspot"); + + let tookPortalToChaos = getUnits(sdk.unittype.Object, "portal").filter(function (portal) { + return portal.objtype === sdk.areas.ChaosSanctuary; + }).sort(function (a, b) { + let aParent = a.getParent ? a.getParent() : null; + let bParent = b.getParent ? b.getParent() : null; + if (aParent === me.name) return -1; + if (bParent === me.name) return 1; + return getDistance(me, a) - getDistance(me, b); + }).some(function (portal) { + return Pather.usePortal(null, null, portal); + }); + + if (!tookPortalToChaos) { + throw new Error("Failed to go to Chaos Sanctuary"); + } + } + + 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[me.classid].checkTraps({ x: 7793, y: 5293 }); + if (trapCheck) { + ClassAttack[me.classid].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"); + }, + }; + + module.exports = _Diablo; +})(module); diff --git a/d2bs/kolbot/libs/core/Common/Leecher.js b/d2bs/kolbot/libs/core/Common/Leecher.js new file mode 100644 index 000000000..4174d71c1 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Leecher.js @@ -0,0 +1,49 @@ +/** +* @filename Leecher.js +* @author theBGuy +* @desc Leecher tools +* +*/ + +(function (module) { + const 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 (Leecher.killLeaderTracker) return false; + // check every 3 seconds + if (getTickCount() - Leecher.leadTick < 3000) return true; + 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(Leecher.leader); + + if (party) { + // Player has moved on to another script + if (Leecher.nextScriptAreas.includes(party.area)) { + if (Loader.scriptName() === Leecher.currentScript) { + Leecher.killLeaderTracker = true; + throw new Error("Party leader is running a new script"); + } else { + // kill process + return false; + } + } + } + + return true; + } + }; + + module.exports = Leecher; +})(module); diff --git a/d2bs/kolbot/libs/core/Common/Smith.js b/d2bs/kolbot/libs/core/Common/Smith.js new file mode 100644 index 000000000..4ecb156ba --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Smith.js @@ -0,0 +1,25 @@ +/** +* @filename Smith.js +* @author theBGuy +* @desc Complete smith quest +* +*/ + +(function (module) { + module.exports = 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); + }; +})(module); diff --git a/d2bs/kolbot/libs/core/Common/Tools.js b/d2bs/kolbot/libs/core/Common/Tools.js new file mode 100644 index 000000000..ebd1c48cc --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Tools.js @@ -0,0 +1,363 @@ +/** +* @filename Tools.js +* @author theBGuy +* @desc Tools for Toolsthread and its variations (MapToolsThread, ect) +* +*/ + +(function (module) { + const Toolsthread = { + pots: { + Health: 0, + Mana: 1, + Rejuv: 2, + MercHealth: 3, + MercRejuv: 4 + }, + pingTimer: [], + pauseScripts: [], + stopScripts: [], + timerLastDrink: [], + cloneWalked: false, + + /** + * @param {boolean} print + * @returns {boolean} + */ + 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; + }, + + 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); + }, + + /** + * @param {string} mainScript + * @returns {boolean} + */ + togglePause: function (mainScript = "default.dbj") { + for (let curr of this.pauseScripts) { + let script = getScript(curr); + + if (script) { + if (script.running) { + curr === mainScript && console.log("ÿc1Pausing."); + script.pause(); + } else { + if (curr === mainScript) { + console.log("ÿc2Resuming."); + } + script.resume(); + } + } + } + + return true; + }, + + 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; + }, + + 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} + */ + 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 + */ + 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; + } + + const 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; + } + })(); + + const 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(3); + } + } + + 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; + /** @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} + */ + 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; + } + }; + + module.exports = Toolsthread; +})(module); diff --git a/d2bs/kolbot/libs/core/Config.js b/d2bs/kolbot/libs/core/Config.js new file mode 100644 index 000000000..31141d592 --- /dev/null +++ b/d2bs/kolbot/libs/core/Config.js @@ -0,0 +1,833 @@ +/** +* @filename Config.js +* @author kolton, theBGuy +* @desc config loading and default config values storage +* +*/ + +/** @type {Record} */ +const Scripts = {}; + +/** @type {IConfig} */ +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."); + } + } + + if (getScript("D2BotAutoRush.dbj")) { + const { RushConfig } = require("../systems/autorush/RushConfig"); + const { rushConfigInit } = require("../systems/autorush/RushConstants"); + rushConfigInit(RushConfig[me.profile]); + } + + // 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, + Shrines: false, + }, + + // Experimental + FastParty: false, + AutoEquip: false, + UseExperimentalAvoid: false, + UseExperimentalClearLevel: 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, + UnpartyForMinGameTimeWait: 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 {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + */ + AdvancedSkipCheck: [], + /** @type {DamageType[]} */ + ImmunityException: [], + /** @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, + /** @type {CubingRecipe[]} */ + Recipes: [], + MakeRunewords: false, + /** + * @type {[runeword, string | number, ?boolean, number | undefined][]} + * @example [Runeword.Enigma, 'Archon Plate', Roll.NonEth, 100] + */ + Runewords: [], + KeepRunewords: [], + LadderOverride: 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, + + // 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], preAttack: 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, + UseVigorOnLowStam: 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!", + Silent: false + }, + 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: [], + HurtBaal: 0, + }, + BaalHelper: { + Wait: 120, + KillNihlathak: false, + FastChaos: false, + DollQuit: false, + SoulQuit: false, + KillBaal: false, + SkipTP: false, + HurtBaal: 0, + }, + 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, + ClearType: sdk.monsters.spectype.All, + /** @type {import("sdk/types/Config").DiabloSeal[]} */ + SealOrder: ["vizier", "seis", "infector"] + }, + DiabloHelper: { + Wait: 120, + Entrance: false, + SkipIfBaal: false, + SkipTP: false, + OpenSeals: false, + SafePrecast: true, + ClearRadius: 30, + ClearType: sdk.monsters.spectype.All, + /** @type {import("sdk/types/Config").DiabloSeal[]} */ + SealOrder: ["vizier", "seis", "infector"], + RecheckSeals: false, + HurtDiablo: 0, + }, + /** @type {IConfig["AutoChaos"]} */ + AutoChaos: { + Leader: "", + Diablo: 0, + Taxi: false, + FindShrine: false, + UseShrine: false, + Glitcher: false, + BO: false, + Leech: false, + Ranged: false, + RequireClass: { + Amazon: false, + Sorceress: false, + Necromancer: false, + Paladin: false, + Barbarian: false, + Druid: false, + Assassin: false, + }, + SealPrecast: false, + PreAttack: [0, 0, 0], + SealOrder: [1, 2, 3], + SealDelay: 0 + }, + MFHelper: { + BreakClearLevel: false, + BreakOnDiaBaal: true, + }, + 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: { + WelcomePlayers: true, + 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, + MinGameLength: 3, + NGVoting: false, + NGVoteCooldown: 3, + }, + 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: [] }, + } + }, + OrgTorchHelper: { + Taxi: false, + Helper: false, + SkipTp: false, + GetFade: false, + UseWalkPath: false, + }, + 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..b0f2f5c18 --- /dev/null +++ b/d2bs/kolbot/libs/core/Cubing.js @@ -0,0 +1,1513 @@ +/** +* @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, + + // TODO: refactor to one method that returns the baseRecipeObj + /** + * 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[]} + */ + ingredients: 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: + case sdk.items.runes.Thul: + 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 []; + }, + + /** + * @param {number} index + */ + itemLevel: function (index) { + switch (index) { + case Recipe.HitPower.Helm: + return 84; + case Recipe.HitPower.Boots: + return 71; + case Recipe.HitPower.Gloves: + return 79; + case Recipe.HitPower.Belt: + return 71; + case Recipe.HitPower.Shield: + return 82; + case Recipe.HitPower.Body: + return 85; + case Recipe.HitPower.Amulet: + return 90; + case Recipe.HitPower.Ring: + return 77; + case Recipe.HitPower.Weapon: + return 85; + case Recipe.Blood.Helm: + return 84; + case Recipe.Blood.Boots: + return 71; + case Recipe.Blood.Gloves: + return 79; + case Recipe.Blood.Belt: + return 71; + case Recipe.Blood.Shield: + return 82; + case Recipe.Blood.Body: + return 85; + case Recipe.Blood.Amulet: + return 90; + case Recipe.Blood.Ring: + return 77; + case Recipe.Blood.Weapon: + return 85; + case Recipe.Caster.Helm: + return 84; + case Recipe.Caster.Boots: + return 71; + case Recipe.Caster.Gloves: + return 79; + case Recipe.Caster.Belt: + return 71; + case Recipe.Caster.Shield: + return 82; + case Recipe.Caster.Body: + return 85; + case Recipe.Caster.Amulet: + return 90; + case Recipe.Caster.Ring: + return 77; + case Recipe.Caster.Weapon: + return 85; + case Recipe.Safety.Helm: + return 84; + case Recipe.Safety.Boots: + return 71; + case Recipe.Safety.Gloves: + return 79; + case Recipe.Safety.Belt: + return 71; + case Recipe.Safety.Shield: + return 82; + case Recipe.Safety.Body: + return 85; + case Recipe.Safety.Amulet: + return 90; + case Recipe.Safety.Ring: + return 77; + case Recipe.Safety.Weapon: + return 85; + // Upgrading Recipes------------------------------------------------------------------------// + case Recipe.Socket.Magic.LowWeapon: + case Recipe.Socket.Magic.HighWeapon: + // ilvl < 30 + // ilvl >= 30 + return 30; + 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) { + return 94; + } else if (index === Recipe.Reroll.Charm.Large) { + return 76; + } else if (index === Recipe.Reroll.Charm.LowGrand) { + return 50; + } else if (index === Recipe.Reroll.Charm.Grand) { + return 77; + } + return 91; + } + return undefined; + }, +}; + +/** + * @memberof Recipe + * @function ingredients + */ +Object.defineProperty(Recipe, "ingredients", { + enumerable: false, +}); + +/** + * @memberof Recipe + * @function itemLevel + */ +Object.defineProperty(Recipe, "itemLevel", { + 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} KeyItem + * @property {number} [Level] + * @property {number} [Ethereal] + * @property {boolean} [Enabled] + * @property {boolean} [AlwaysEnabled] + * @property {number} [MainRecipe] + * @property {number} [MaxQuantity] + * @property {() => boolean} [condition] + * @property {string} [pickLine] + * + * + * @todo + * - Allow passing in ilvl + */ + buildRecipes: function () { + Cubing.recipes = []; + + for (let i = 0; i < Config.Recipes.length; i += 1) { + if ( + !isType(Config.Recipes[i], "array") + || Config.Recipes[i].length < 1 + || ( + Config.Recipes[i].length > 2 + && typeof Config.Recipes[i][2] !== "number" + && typeof Config.Recipes[i][2] !== "object" + ) + ) { + throw new Error("Cubing.buildRecipes: Invalid recipe format."); + } + + /** @type {number[]} */ + const [index, keyItem, opts] = Config.Recipes[i]; + const ingredients = Recipe.ingredients(index, keyItem); + const itemLevel = Recipe.itemLevel(index); + /** @type {Partial} */ + const extendedOpts = { + Ethereal: undefined, + MaxQuantity: undefined, + condition: undefined + }; + + if (isType(opts, "number")) { + extendedOpts.Ethereal = opts; + } else if (isType(opts, "object")) { + Object.assign(extendedOpts, opts); + } + + /** @type {recipeObj} */ + const baseRecipeObj = { + Index: index, + KeyItem: keyItem, + Ingredients: ingredients + }; + + if (itemLevel) { + baseRecipeObj.Level = itemLevel; + } + const recipeObj = Object.assign(baseRecipeObj, extendedOpts); + + switch (index) { + case Recipe.Token: + case Recipe.Gem: + if (index === Recipe.Token) { + recipeObj.KeyItem = sdk.items.quest.TokenofAbsolution; + } + recipeObj.AlwaysEnabled = true; + + break; + case Recipe.Unique.Weapon.ToElite: // Ladder only + case Recipe.Unique.Armor.ToElite: // Ladder only + // not ladder and online - we can do these recipes on single player + if (!me.ladder && me.realm) continue; + break; + case Recipe.Reroll.HighRare: + recipeObj.Enabled = false; + + break; + case Recipe.Rune: + if (keyItem >= sdk.items.runes.Eld && keyItem <= sdk.items.runes.Ort) { + recipeObj.AlwaysEnabled = true; + } else { + // not ladder and online - we can do these recipes on single player + if (!me.ladder && me.realm) continue; + } + + break; + } + this.recipes.push(recipeObj); + } + }, + + /** @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) { + const recipe = this.recipes[i]; + + if (recipe.hasOwnProperty("condition") && typeof recipe.condition === "function") { + if (!recipe.condition()) { + console.debug("Skipping recipe " + recipe.Index + " due to condition cb"); + continue; + } + } + + if (recipe.hasOwnProperty("MaxQuantity") && typeof recipe.MaxQuantity === "number") { + let itemClassid = recipe.KeyItem; + let itemCount = me.getItemsEx(itemClassid).filter(function (item) { + return item.isInStorage; + }).length; + + if (itemCount >= recipe.MaxQuantity) { + console.debug( + "Skipping recipe " + recipe.Index + " due to item count exceeding MaxQuantity." + + " Have: " + itemCount + + ", Wanted: " + recipe.MaxQuantity + ); + continue; + } + } + + // 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) { + const item = items[k]; + const { classid, gid } = item; + if (( + (currIngred === "pgem" && this.gemList.includes(classid)) + || (currIngred === "fgem" && this.gems.flawless.includes(classid)) + || (currIngred === "gem" && this.gems.normal.includes(classid)) + || (currIngred === "cgem" && this.gems.chipped.includes(classid)) + || (currIngred === "hpot" && this.pots.healing.includes(classid)) + || (currIngred === "mpot" && this.pots.mana.includes(classid)) + || classid === currIngred) && this.validItem(item, 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: classid, gid: 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 {ItemUnit[] | boolean} + */ + checkRecipe: function (recipe) { + /** @type {number[]} */ + let usedGids = []; + /** @type {ItemUnit[]} */ + 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.) + const tempArray = this.recipes.slice().shuffle(); + + for (let i = 0; i < tempArray.length; i += 1) { + const recipe = tempArray[i]; + + let string = "Transmuting: "; + let items = this.checkRecipe(recipe); + 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..e4658274a --- /dev/null +++ b/d2bs/kolbot/libs/core/Experience.js @@ -0,0 +1,141 @@ +/* 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); + } + } else { + D2Bot.printToConsole("[Game: " + me.gamename + (me.gamepassword ? "//" + me.gamepassword : "") + getGameTime + "]"); + } + } +}; diff --git a/d2bs/kolbot/libs/core/GameData/AreaData.js b/d2bs/kolbot/libs/core/GameData/AreaData.js new file mode 100644 index 000000000..20305132e --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/AreaData.js @@ -0,0 +1,345 @@ +/** +* @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 {Object} AreaDataObj + * @property {number} Super - Number of super uniques present in this area + * @property {number} Index - Area ID + * @property {number} Act - Act this area is in [0-4] + * @property {number} MonsterDensity - Value used to determine monster population density + * @property {Object} ChampionPacks - Champion packs information + * @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 {Object} Size - Size of the area + * @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 {string} LocaleString - Locale string index for getLocaleString + * @property {string} InternalName - Internal name + * @property {function(number): boolean} hasMonsterType - Check if this area has a monster of a certain type + * @property {function(function(any, number): void): void} forEachMonster - Iterate through each monster in this area and apply a callback function + * @property {function(function(any, number, any): void): void} forEachMonsterAndMinion - Iterate through each monster and minion in this area and apply a callback function + * @property {function(): AreaDataObj} townArea - Check if area is a town area + * @property {function(): boolean} haveWaypoint - Check if the area has a waypoint + * @property {function(): (AreaDataObj|undefined)} nearestWaypointArea - Find nearest waypoint in area + * @property {function(): (PresetUnit|undefined)} waypointPreset - Get the waypoint preset unit + */ + + /** + * @typedef {Object} Dungeons + * @property {number[]} DenOfEvil + * @property {number[]} Hole + * @property {number[]} Pit + * @property {number[]} Cave + * @property {number[]} UndergroundPassage + * @property {number[]} Cellar + * @property {number[]} A2Sewers + * @property {number[]} StonyTomb + * @property {number[]} HallsOfDead + * @property {number[]} ClawViperTemple + * @property {number[]} MaggotLair + * @property {number[]} Tombs + * @property {number[]} Swamp + * @property {number[]} FlayerDungeon + * @property {number[]} A3Sewers + * @property {number[]} HighLevelForgottenTemples + * @property {number[]} LowLevelForgottenTemples + * @property {number[]} RedPortalPits + */ + + /** @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 me.haveWaypoint((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; + }, + }); + } + + /** + * @function + * @static + * @name AreaData.findByName + * @param {string} whatToFind + * @param {function(AreaDataObj): boolean} [filter] - Optional filter function to restrict which areas are considered + * @returns {AreaDataObj} + */ + AreaData.findByName = function (whatToFind, filter) { + if (!whatToFind || typeof whatToFind !== "string") { + return AreaData[1]; + } + + const searchTerm = whatToFind.toLowerCase().trim(); + + const areaPool = typeof filter === "function" + ? AreaData.filter(area => area && area.LocaleString && filter(area)) + : AreaData.filter(area => area && area.LocaleString); + + if (areaPool.length === 0) { + return AreaData[1]; + } + + // First attempt: Look for exact matches or contains + const exactMatches = areaPool.filter(function (area) { + if (!area || !area.LocaleString) return false; + + const localeLower = area.LocaleString.toLowerCase(); + const internalLower = area.InternalName ? area.InternalName.toLowerCase() : ""; + + // Exact match gets highest priority + if (localeLower === searchTerm || internalLower === searchTerm) { + return true; + } + + // Contains match gets second priority (handles partial names like "worldstone") + if (localeLower.includes(searchTerm) || internalLower.includes(searchTerm)) { + return true; + } + + return false; + }); + + // If we found exact or contains matches, sort them by length (shorter names first) + // This helps prioritize more specific matches when partial names are given + if (exactMatches.length > 0) { + exactMatches.sort(function (a, b) { + const aLocale = a.LocaleString.toLowerCase(); + const bLocale = b.LocaleString.toLowerCase(); + + // If one contains the term exactly at the start, prioritize it + const aStartsWith = aLocale.startsWith(searchTerm); + const bStartsWith = bLocale.startsWith(searchTerm); + + if (aStartsWith && !bStartsWith) return -1; + if (!aStartsWith && bStartsWith) return 1; + + // Otherwise sort by shortest containing name + return aLocale.length - bLocale.length; + }); + + return exactMatches[0]; + } + + // Fallback: Use the diffCount approach for fuzzy matching + let matches = areaPool + .map(area => { + const localeDiff = area.LocaleString ? searchTerm.diffCount(area.LocaleString.toLowerCase()) : Infinity; + const internalDiff = area.InternalName ? searchTerm.diffCount(area.InternalName.toLowerCase()) : Infinity; + return [Math.min(localeDiff, internalDiff), area]; + }) + .sort((a, b) => a[0] - b[0]); + + return matches[0][1]; + }; + + /** + * @type {Dungeons} + */ + 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..c296b0818 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/LocaleStringID.js @@ -0,0 +1,7819 @@ +/** +* @filename LocaleStringID.js +* @author Nishimura-Katsuo +* @desc locale string indexes from NameStr ids +*/ +(function (module) { + const 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": 2276, + "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 + }; + + const LocaleStringName = {}; + + for (let k in LocaleStringID) { + LocaleStringName[LocaleStringID[k]] = k; + } + + const ClassIdToLocaleString = {}; + + for (let k in LocaleStringID) { + if (k.length <= 3 && Object.prototype.hasOwnProperty.call(NTIPAliasClassID, k)) { + ClassIdToLocaleString[NTIPAliasClassID[k]] = getLocaleString(LocaleStringID[k]); + } + } + + module.exports = { + LocaleStringName: LocaleStringName, + LocaleStringID: LocaleStringID, + /** + * Maps class ID to locale string. Only includes items right now + * @type {Record} + */ + ClassIdToLocaleString: ClassIdToLocaleString + }; +})(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..66bd0b979 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/NTItemAlias.js @@ -0,0 +1,2183 @@ +/* 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; + +const NTIPAliasCodes = { + hax: 0, + axe: 1, + "2ax": 2, + mpi: 3, + wax: 4, + lax: 5, + bax: 6, + btx: 7, + gax: 8, + gix: 9, + wnd: 10, + ywn: 11, + bwn: 12, + gwn: 13, + clb: 14, + scp: 15, + gsc: 16, + wsp: 17, + spc: 18, + mac: 19, + mst: 20, + fla: 21, + whm: 22, + mau: 23, + gma: 24, + ssd: 25, + scm: 26, + sbr: 27, + flc: 28, + crs: 29, + bsd: 30, + lsd: 31, + wsd: 32, + "2hs": 33, + clm: 34, + gis: 35, + bsw: 36, + flb: 37, + gsd: 38, + dgr: 39, + dir: 40, + kri: 41, + bld: 42, + tkf: 43, + tax: 44, + bkf: 45, + bal: 46, + jav: 47, + pil: 48, + ssp: 49, + glv: 50, + tsp: 51, + spr: 52, + tri: 53, + brn: 54, + spt: 55, + pik: 56, + bar: 57, + vou: 58, + scy: 59, + pax: 60, + hal: 61, + wsc: 62, + sst: 63, + lst: 64, + cst: 65, + bst: 66, + wst: 67, + sbw: 68, + hbw: 69, + lbw: 70, + cbw: 71, + sbb: 72, + lbb: 73, + swb: 74, + lwb: 75, + lxb: 76, + mxb: 77, + hxb: 78, + rxb: 79, + gps: 80, + ops: 81, + gpm: 82, + opm: 83, + gpl: 84, + opl: 85, + d33: 86, + g33: 87, + leg: 88, + hdm: 89, + hfh: 90, + hst: 91, + msf: 92, + "9ha": 93, + "9ax": 94, + "92a": 95, + "9mp": 96, + "9wa": 97, + "9la": 98, + "9ba": 99, + "9bt": 100, + "9ga": 101, + "9gi": 102, + "9wn": 103, + "9yw": 104, + "9bw": 105, + "9gw": 106, + "9cl": 107, + "9sc": 108, + "9qs": 109, + "9ws": 110, + "9sp": 111, + "9ma": 112, + "9mt": 113, + "9fl": 114, + "9wh": 115, + "9m9": 116, + "9gm": 117, + "9ss": 118, + "9sm": 119, + "9sb": 120, + "9fc": 121, + "9cr": 122, + "9bs": 123, + "9ls": 124, + "9wd": 125, + "92h": 126, + "9cm": 127, + "9gs": 128, + "9b9": 129, + "9fb": 130, + "9gd": 131, + "9dg": 132, + "9di": 133, + "9kr": 134, + "9bl": 135, + "9tk": 136, + "9ta": 137, + "9bk": 138, + "9b8": 139, + "9ja": 140, + "9pi": 141, + "9s9": 142, + "9gl": 143, + "9ts": 144, + "9sr": 145, + "9tr": 146, + "9br": 147, + "9st": 148, + "9p9": 149, + "9b7": 150, + "9vo": 151, + "9s8": 152, + "9pa": 153, + "9h9": 154, + "9wc": 155, + "8ss": 156, + "8ls": 157, + "8cs": 158, + "8bs": 159, + "8ws": 160, + "8sb": 161, + "8hb": 162, + "8lb": 163, + "8cb": 164, + "8s8": 165, + "8l8": 166, + "8sw": 167, + "8lw": 168, + "8lx": 169, + "8mx": 170, + "8hx": 171, + "8rx": 172, + qf1: 173, + qf2: 174, + ktr: 175, + wrb: 176, + axf: 177, + ces: 178, + clw: 179, + btl: 180, + skr: 181, + "9ar": 182, + "9wb": 183, + "9xf": 184, + "9cs": 185, + "9lw": 186, + "9tw": 187, + "9qr": 188, + "7ar": 189, + "7wb": 190, + "7xf": 191, + "7cs": 192, + "7lw": 193, + "7tw": 194, + "7qr": 195, + "7ha": 196, + "7ax": 197, + "72a": 198, + "7mp": 199, + "7wa": 200, + "7la": 201, + "7ba": 202, + "7bt": 203, + "7ga": 204, + "7gi": 205, + "7wn": 206, + "7yw": 207, + "7bw": 208, + "7gw": 209, + "7cl": 210, + "7sc": 211, + "7qs": 212, + "7ws": 213, + "7sp": 214, + "7ma": 215, + "7mt": 216, + "7fl": 217, + "7wh": 218, + "7m7": 219, + "7gm": 220, + "7ss": 221, + "7sm": 222, + "7sb": 223, + "7fc": 224, + "7cr": 225, + "7bs": 226, + "7ls": 227, + "7wd": 228, + "72h": 229, + "7cm": 230, + "7gs": 231, + "7b7": 232, + "7fb": 233, + "7gd": 234, + "7dg": 235, + "7di": 236, + "7kr": 237, + "7bl": 238, + "7tk": 239, + "7ta": 240, + "7bk": 241, + "7b8": 242, + "7ja": 243, + "7pi": 244, + "7s7": 245, + "7gl": 246, + "7ts": 247, + "7sr": 248, + "7tr": 249, + "7br": 250, + "7st": 251, + "7p7": 252, + "7o7": 253, + "7vo": 254, + "7s8": 255, + "7pa": 256, + "7h7": 257, + "7wc": 258, + "6ss": 259, + "6ls": 260, + "6cs": 261, + "6bs": 262, + "6ws": 263, + "6sb": 264, + "6hb": 265, + "6lb": 266, + "6cb": 267, + "6s7": 268, + "6l7": 269, + "6sw": 270, + "6lw": 271, + "6lx": 272, + "6mx": 273, + "6hx": 274, + "6rx": 275, + ob1: 276, + ob2: 277, + ob3: 278, + ob4: 279, + ob5: 280, + am1: 281, + am2: 282, + am3: 283, + am4: 284, + am5: 285, + ob6: 286, + ob7: 287, + ob8: 288, + ob9: 289, + oba: 290, + am6: 291, + am7: 292, + am8: 293, + am9: 294, + ama: 295, + obb: 296, + obc: 297, + obd: 298, + obe: 299, + obf: 300, + amb: 301, + amc: 302, + amd: 303, + ame: 304, + amf: 305, + cap: 306, + skp: 307, + hlm: 308, + fhl: 309, + ghm: 310, + crn: 311, + msk: 312, + qui: 313, + lea: 314, + hla: 315, + stu: 316, + rng: 317, + scl: 318, + chn: 319, + brs: 320, + spl: 321, + plt: 322, + fld: 323, + gth: 324, + ful: 325, + aar: 326, + ltp: 327, + buc: 328, + sml: 329, + lrg: 330, + kit: 331, + tow: 332, + gts: 333, + lgl: 334, + vgl: 335, + mgl: 336, + tgl: 337, + hgl: 338, + lbt: 339, + vbt: 340, + mbt: 341, + tbt: 342, + hbt: 343, + lbl: 344, + vbl: 345, + mbl: 346, + tbl: 347, + hbl: 348, + bhm: 349, + bsh: 350, + spk: 351, + xap: 352, + xkp: 353, + xlm: 354, + xhl: 355, + xhm: 356, + xrn: 357, + xsk: 358, + xui: 359, + xea: 360, + xla: 361, + xtu: 362, + xng: 363, + xcl: 364, + xhn: 365, + xrs: 366, + xpl: 367, + xlt: 368, + xld: 369, + xth: 370, + xul: 371, + xar: 372, + xtp: 373, + xuc: 374, + xml: 375, + xrg: 376, + xit: 377, + xow: 378, + xts: 379, + xlg: 380, + xvg: 381, + xmg: 382, + xtg: 383, + xhg: 384, + xlb: 385, + xvb: 386, + xmb: 387, + xtb: 388, + xhb: 389, + zlb: 390, + zvb: 391, + zmb: 392, + ztb: 393, + zhb: 394, + xh9: 395, + xsh: 396, + xpk: 397, + dr1: 398, + dr2: 399, + dr3: 400, + dr4: 401, + dr5: 402, + ba1: 403, + ba2: 404, + ba3: 405, + ba4: 406, + ba5: 407, + pa1: 408, + pa2: 409, + pa3: 410, + pa4: 411, + pa5: 412, + ne1: 413, + ne2: 414, + ne3: 415, + ne4: 416, + ne5: 417, + ci0: 418, + ci1: 419, + ci2: 420, + ci3: 421, + uap: 422, + ukp: 423, + ulm: 424, + uhl: 425, + uhm: 426, + urn: 427, + usk: 428, + uui: 429, + uea: 430, + ula: 431, + utu: 432, + ung: 433, + ucl: 434, + uhn: 435, + urs: 436, + upl: 437, + ult: 438, + uld: 439, + uth: 440, + uul: 441, + uar: 442, + utp: 443, + uuc: 444, + uml: 445, + urg: 446, + uit: 447, + uow: 448, + uts: 449, + ulg: 450, + uvg: 451, + umg: 452, + utg: 453, + uhg: 454, + ulb: 455, + uvb: 456, + umb: 457, + utb: 458, + uhb: 459, + ulc: 460, + uvc: 461, + umc: 462, + utc: 463, + uhc: 464, + uh9: 465, + ush: 466, + upk: 467, + dr6: 468, + dr7: 469, + dr8: 470, + dr9: 471, + dra: 472, + ba6: 473, + ba7: 474, + ba8: 475, + ba9: 476, + baa: 477, + pa6: 478, + pa7: 479, + pa8: 480, + pa9: 481, + paa: 482, + ne6: 483, + ne7: 484, + ne8: 485, + ne9: 486, + nea: 487, + drb: 488, + drc: 489, + drd: 490, + dre: 491, + drf: 492, + bab: 493, + bac: 494, + bad: 495, + bae: 496, + baf: 497, + pab: 498, + pac: 499, + pad: 500, + pae: 501, + paf: 502, + neb: 503, + neg: 504, + ned: 505, + nee: 506, + nef: 507, + elx: 508, + hpo: 509, + mpo: 510, + hpf: 511, + mpf: 512, + vps: 513, + yps: 514, + rvs: 515, + rvl: 516, + wms: 517, + tbk: 518, + ibk: 519, + amu: 520, + vip: 521, + rin: 522, + gld: 523, + bks: 524, + bkd: 525, + aqv: 526, + tch: 527, + cqv: 528, + tsc: 529, + isc: 530, + hrt: 531, + brz: 532, + jaw: 533, + eyz: 534, + hrn: 535, + tal: 536, + flg: 537, + fng: 538, + qll: 539, + sol: 540, + scz: 541, + spe: 542, + key: 543, + luv: 544, + xyz: 545, + j34: 546, + g34: 547, + bbb: 548, + box: 549, + tr1: 550, + mss: 551, + ass: 552, + qey: 553, + qhr: 554, + qbr: 555, + ear: 556, + gcv: 557, + gfv: 558, + gsv: 559, + gzv: 560, + gpv: 561, + gcy: 562, + gfy: 563, + gsy: 564, + gly: 565, + gpy: 566, + gcb: 567, + gfb: 568, + gsb: 569, + glb: 570, + gpb: 571, + gcg: 572, + gfg: 573, + gsg: 574, + glg: 575, + gpg: 576, + gcr: 577, + gfr: 578, + gsr: 579, + glr: 580, + gpr: 581, + gcw: 582, + gfw: 583, + gsw: 584, + glw: 585, + gpw: 586, + hp1: 587, + hp2: 588, + hp3: 589, + hp4: 590, + hp5: 591, + mp1: 592, + mp2: 593, + mp3: 594, + mp4: 595, + mp5: 596, + skc: 597, + skf: 598, + sku: 599, + skl: 600, + skz: 601, + hrb: 602, + cm1: 603, + cm2: 604, + cm3: 605, + rps: 606, + rpl: 607, + bps: 608, + bpl: 609, + r01: 610, + r02: 611, + r03: 612, + r04: 613, + r05: 614, + r06: 615, + r07: 616, + r08: 617, + r09: 618, + r10: 619, + r11: 620, + r12: 621, + r13: 622, + r14: 623, + r15: 624, + r16: 625, + r17: 626, + r18: 627, + r19: 628, + r20: 629, + r21: 630, + r22: 631, + r23: 632, + r24: 633, + r25: 634, + r26: 635, + r27: 636, + r28: 637, + r29: 638, + r30: 639, + r31: 640, + r32: 641, + r33: 642, + jew: 643, + ice: 644, + "0sc": 645, + tr2: 646, + pk1: 647, + pk2: 648, + pk3: 649, + dhn: 650, + bey: 651, + mbr: 652, + toa: 653, + tes: 654, + ceh: 655, + bet: 656, + fed: 657, + std: 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..203a097b3 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/QuestData.js @@ -0,0 +1,182 @@ +/** +* @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 () { + const CACHE_TIME = Time.minutes(5); + 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(function (questId) { + _specials.add(questId); + }); + + const refresh = function () { + if (getTickCount() - _lastRefresh > CACHE_TIME) { + 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(function (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..ef3de3a65 --- /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.LadderOverride) 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..4e3d6cb25 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/ShrineData.js @@ -0,0 +1,78 @@ +/** +* @filename ShrineData.js +* @author theBGuy +* @desc shrine data library, handles shrine types, states, durations, and regen times +* +*/ + +(function (module) { + const ShrineData = (function () { + /** + * @constructor + * @param {string} name + * @param {number} state + * @param {number} duration + * @param {number} regen + */ + function Shrine (name, state, duration, regen) { + this.name = name || "Unknown Shrine"; + this.state = state || 0; + this.duration = duration || 0; + this.regenTime = Time.minutes(regen) || Infinity; + } + const _shrines = new Map([ + [sdk.shrines.Refilling, new Shrine("Refilling", sdk.shrines.None, 0, 2)], + [sdk.shrines.Health, new Shrine("Health", sdk.shrines.None, 0, 5)], + [sdk.shrines.Mana, new Shrine("Mana", sdk.shrines.None, 0, 5)], + [sdk.shrines.HealthExchange, new Shrine()], + [sdk.shrines.ManaExchange, new Shrine()], + [sdk.shrines.Armor, new Shrine("Armor", sdk.states.ShrineArmor, 2400, 5)], + [sdk.shrines.Combat, new Shrine("Combat", sdk.states.ShrineCombat, 2400, 5)], + [sdk.shrines.ResistFire, new Shrine("Resist Fire", sdk.states.ShrineResFire, 3600, 5)], + [sdk.shrines.ResistCold, new Shrine("Resist Cold", sdk.states.ShrineResCold, 3600, 5)], + [sdk.shrines.ResistLightning, new Shrine("Resist Lightning", sdk.states.ShrineResLighting, 3600, 5)], + [sdk.shrines.ResistPoison, new Shrine("Resist Poison", sdk.states.ShrineResPoison, 3600, 5)], + [sdk.shrines.Skill, new Shrine("Skill", sdk.states.ShrineSkill, 2400, 5)], + [sdk.shrines.ManaRecharge, new Shrine("Mana Recharge", sdk.states.ShrineManaRegen, 2400, 5)], + [sdk.shrines.Stamina, new Shrine("Stamina", sdk.states.ShrineStamina, 4800, 5)], + [sdk.shrines.Experience, new Shrine("Experience", sdk.states.ShrineExperience, 3600)], + [sdk.shrines.Enirhs, new Shrine()], + [sdk.shrines.Portal, new Shrine("Portal")], + [sdk.shrines.Gem, new Shrine("Gem")], + [sdk.shrines.Fire, new Shrine("Fire")], + [sdk.shrines.Monster, new Shrine("Monster")], + [sdk.shrines.Exploding, new Shrine("Exploding")], + [sdk.shrines.Poison, new Shrine("Poison")], + ]); + + 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..9b5f81f4c --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/SkillData.js @@ -0,0 +1,1577 @@ +/** +* @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, + condition: function () { + return !Config.NoTele; + } + }); + 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) { + try { + 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 invTrans = (getBaseStat("items", unit.classid, "InvTrans") || 0); + 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, + invTrans: invTrans, + header: "", + sockets: this.getItemSockets(unit) + }; + + D2Bot.printToItemLog(itemObj); + + return true; + } catch (e) { + if (e instanceof ScriptError) { + throw e; + } + console.error("Error logging item: ", e); + return false; + } + }, + + /** + * 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..2c06c8a6b --- /dev/null +++ b/d2bs/kolbot/libs/core/Loader.js @@ -0,0 +1,467 @@ +/** +* @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); + } + } + + // run town chores on last script - prevents muling due to leftover items in inventory + if (Loader.scriptIndex === Loader.scriptList.length - 1) { + Town.doChores(); + } + } + } + } 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..4ad0c89d6 --- /dev/null +++ b/d2bs/kolbot/libs/core/Misc.js @@ -0,0 +1,1231 @@ +/** +* @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) { + if ((e instanceof ScriptError)) { + throw 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; + }, + + /** + * Get players in game and in my party + * @returns {Party[]} + */ + getPartyMembers: function () { + /** @type {Party[]} */ + const members = []; + let party = getParty(); + + if (party) { + let myPartyId = party.partyid; + + do { + if (party.partyid !== sdk.party.NoParty + && party.partyid === myPartyId + && party.name !== me.name) { + members.push(copyObj(party)); + } + } while (party.getNext()); + } + + return members; + }, + + /** + * 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 + * @param {number} [x=me.x] + * @param {number} [y=me.y] + * @returns {boolean} + */ + openChests: function (range = 15, x = me.x, y = me.y) { + 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(x, 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(x, 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(), + + /** + * @param {number[]} ignore + * @param {number} range + * @returns {boolean} + */ + shriner: function (ignore = [], range = 50) { + 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; + } + const walkDistance = Pather.getWalkDistance(shrine.x, shrine.y); + + 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() && walkDistance > 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() && walkDistance > 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() && walkDistance > 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: + if (Config.DebugMode.Shrines) { + console.debug( + "Skill shrine. Dist: " + walkDistance + + " Last shrine state: " + Misc.lastShrine.state + + " isMyCurrentState: " + Misc.lastShrine.isMyCurrentState() + ); + } + return !me.getState(sdk.states.ShrineExperience); + case sdk.shrines.ManaRecharge: + if (Config.DebugMode.Shrines) { + console.debug( + "Mana recharge shrine. Dist: " + walkDistance + + " Mana: " + me.mpPercent + "%" + + " Last shrine state: " + Misc.lastShrine.state + + " isMyCurrentState: " + Misc.lastShrine.isMyCurrentState() + ); + } + // we only want if its close to us if we can't teleport + if (!Pather.useTeleport() && walkDistance > 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.Stamina: + if (Config.DebugMode.Shrines) { + console.debug( + "Staima shrine. Dist: " + walkDistance + + " Stamina: " + me.staminaPercent + + "% Max: " + me.staminamax + + " Last shrine state: " + Misc.lastShrine.state + + " isMyCurrentState: " + Misc.lastShrine.isMyCurrentState() + ); + } + // we only want if its close to us if we can't teleport + if ( + !Pather.useTeleport() + && walkDistance > (me.staminamax < 200 || me.staminaPercent < 30) ? 30 : 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() && walkDistance > 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: + if (Config.DebugMode.Shrines) { + console.debug( + "Armor/Combat. Last shrine state: " + Misc.lastShrine.state + + " isMyCurrentState: " + Misc.lastShrine.isMyCurrentState() + + " Distance: " + walkDistance + ); + } + + // we only want if its close to us if we can't teleport + if (!Pather.useTeleport() && walkDistance > 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() && walkDistance > 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 + console.debug("shriner: gem shrine. try my best."); + return Town.prepareForGemShrine(); + case sdk.shrines.Poison: + case sdk.shrines.Exploding: + // if it's close are we are low on gold then why not? + if (!Pather.useTeleport() && walkDistance > 10) { + return false; + } + // ideally we should mock the potion to see if we will actually pick it up + // but for now we just check if we are low on gold + return me.gold < Config.LowGold || me.gold < 500000; + } + 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) + && !ignore.includes(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 (shrine.distance > range) { + continue; // too far away + } + + 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); + Pickit.pickItems(); + } + } + } + + return true; + }, + + /** + * @param {number} range + * @param {number[]} ignore + * @returns {boolean} + */ + scanShrines: function (range, ignore) { + !Array.isArray(ignore) && (ignore = [ignore]); + if (Config.AutoShriner) { + return this.shriner(ignore); + } + if (!Config.ScanShrines.length) return false; + + !range && (range = Pather.useTeleport() ? 25 : 15); + + /** @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))) { + // Gem shrine - prepare and pick anyways + if (Config.ScanShrines[i] === sdk.shrines.Gem) { + console.debug("scanshrines: gem shine. try my best."); + Town.prepareForGemShrine(); + } + 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(); + + Pather.moveToEx(coords[0], coords[1], { + minDist: Skill.haveTK ? 20 : 5, + callback: function () { + 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; + }, + + /** + * 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] + * @param {boolean} [useNativeDelay=false] - use native delay function instead of alloying background processing during the wait time + * @returns {T | false} + */ + poll: function (check, timeout = 6000, sleep = 40, useNativeDelay = false) { + let ret, start = getTickCount(); + let delayFunc = useNativeDelay ? nativeDelay : delay; + + while (getTickCount() - start <= timeout) { + if ((ret = check())) { + return ret; + } + + delayFunc(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..72ebcfa72 --- /dev/null +++ b/d2bs/kolbot/libs/core/NTItemParser.js @@ -0,0 +1,808 @@ +/* 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 ((e instanceof ScriptError)) { + throw e; + } + 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) { + console.log("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; + } + } + } + } + + //console.log("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) { + if ((e instanceof ScriptError)) { + throw 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 + * @returns {number|{line: string, result: number}} + */ +NTIP.CheckItem = function (item, entryList, verbose) { + let i, num; + /** @type {{ line: string, result: number }} */ + 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)) continue; + + 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) { + if ((e instanceof ScriptError)) { + throw e; + } + 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 {ItemUnit} item + * @param {[(item) => Boolean, (item) => Boolean, (item) => Boolean]} entryList + * @returns {{line: string, result: number}[]} + */ +NTIP.DebugCheckItem = function (item, entryList, verbose) { + let i, num; + /** @type {{ line: string, result: number }[]} */ + let results = []; + 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]; + const rval = {}; + + if (typeof type === "function") { + if (!type(item)) continue; + + 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); + } + } + + rval.result = result; + if (result === 1) { + rval.line = stringArray[i].file + " #" + stringArray[i].line; + } + + if (result !== 0) { + results.push(rval); + } + } catch (pickError) { + if ((e instanceof ScriptError)) { + throw e; + } + 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; + } + } + + return results; +}; + +/** @param {string} ch */ +NTIP.IsSyntaxInt = function (ch) { + return ( + ch === "!" + || ch === "%" + || ch === "&" + || (ch >= "(" && ch <= "+") + || ch === "-" + || ch === "/" + || (ch >= ":" && ch <= "?") + || ch === "|" + ); +}; + +/** + * @desc Parses [alias]in() and [alias]notin() syntax in NIP and converts them to standard syntax. + * For example, [type]in(armor, weapon) will be converted to ([type] == armor || [type] == weapon) + * and [name]notin(ring, amulet) will be converted to ([name] != ring && [name] != amulet) + */ +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) { + if ((e instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw 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..cd8060984 --- /dev/null +++ b/d2bs/kolbot/libs/core/Packet.js @@ -0,0 +1,621 @@ +/** +* @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) { + if ((e instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw 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..a310fb423 --- /dev/null +++ b/d2bs/kolbot/libs/core/Pather.js @@ -0,0 +1,2427 @@ +/** +* @filename Pather.js +* @author kolton, theBGuy +* @desc handle player movement +* +*/ + +includeIfNotIncluded("manualplay/hooks/ShrineHooks.js"); + +/** + * @constructor + * @param {number} x + * @param {number} y + */ +function PathNode (x, y) { + this.x = x; + this.y = y; +} + +/** + * Distance from unit to node + * @param {Unit} unit + * @returns {number} + */ +PathNode.prototype.distanceTo = function (unit) { + return !me.gameReady ? NaN : (getDistance.apply(null, [unit, this])); +}; + +PathNode.prototype.getWalkDistance = function () { + return (getPath(me.area, me.x, me.y, this.x, this.y, 0, Pather.walkDistance) || []) + .map(function (e, i, s) { + return i && getDistance(s[i - 1], e) || 0; + }) + .reduce(function (acc, cur) { + return acc + cur; + }, 0) || Infinity; +}; + +/** + * @param {{ x?: number, y?: number }} node + */ +PathNode.prototype.update = function (node) { + if (typeof node.x === "number") this.x = node.x; + if (typeof node.y === "number") this.y = node.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); + } + }, + + /** + * Pick items while pathing + * @param {Pick} arg + */ + pickItems: function (arg = {}) { + const pickSettings = Object.assign({}, { + allowPicking: true, + }, arg); + if (!pickSettings.allowPicking) return; + Pickit.pickItems(Config.PickRange / 2); + }, + + /** + * 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(this.shrinesToIgnore); + } else if (Config.ScanShrines.length > 0) { + Misc.scanShrines(null, this.shrinesToIgnore); + } + } +}; + +const PathDebug = { + enableHooks: false, + /** @type {Map wp < sdk.areas.Harrogath)); + + scriptBroadcast("get-cached-waypoints"); + delay(500); + + if ((!Config.WaypointMenu && !Pather.initialized) || force) { + !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(function (node) { + return ( + 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 })); + }, + + /** + * @param {PathNode} node + * @param {number} distance - desired distance from node + * @param {number} [maxAttempts=16] - number of angles to try + * @returns {PathNode | false} + */ + findSpotAtDistance: function (node, distance, maxAttempts = 16) { + if (!node) return false; + + const angleStep = 360 / maxAttempts; + + for (let i = 0; i < maxAttempts; i++) { + let angle = (i * angleStep) * Math.PI / 180; + let x = Math.round(node.x + Math.cos(angle) * distance); + let y = Math.round(node.y + Math.sin(angle) * distance); + + if (Pather.checkSpot(x, y, sdk.collision.BlockWalk)) { + return new PathNode(x, y); + } + } + + // If no exact distance spot found, try to find nearest walkable spot around the target distance + for (let radius = distance - 2; radius <= distance + 2; radius++) { + for (let i = 0; i < maxAttempts; i++) { + let angle = (i * angleStep) * Math.PI / 180; + let x = Math.round(node.x + Math.cos(angle) * radius); + let y = Math.round(node.y + Math.sin(angle) * radius); + + if (Pather.checkSpot(x, y, sdk.collision.BlockWalk)) { + return new PathNode(x, y); + } + } + } + + return false; + }, + + /** + * @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 {boolean} [clearSettings.allowPicking] + * @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, + allowPicking: settings.allowPicking, + 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 + const PATH_DEBUG_ID = Date.now() + Math.floor(Math.random() * 1000); + + for (let i = 0; i < this.cancelFlags.length; i += 1) { + getUIFlag(this.cancelFlags[i]) && me.cancel(); + } + + if (me.itemoncursor) { + console.warn("Pather.move: Item on cursor, dropping it to prevent pathing issues."); + let item = Game.getCursorUnit(); + if (item) { + item.drop(); + } + } + + 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."); + + // Failed to generate path, maybe coords are invalid? Lets try to find a walkable node + // if (!path.length && getDistance(me, node.x, node.y) > 5) { + // console.debug(path, "move: Failed to generate path, trying to find a walkable node. Current distance: " + getDistance(me, node.x, node.y), " node: ", node); + // let adjustedNode = Pather.getNearestWalkable(node.x, node.y, 5, 1, sdk.collision.BlockWalk); + // if (!adjustedNode) { + // throw new Error("move: Failed to generate path."); + // } + // let [tmpX, tmpY] = adjustedNode; + // path = getPath( + // me.area, + // tmpX, tmpY, + // me.x, me.y, + // useTeleport ? 1 : 0, + // useTeleport ? (annoyingArea ? 30 : this.teleDistance) : this.walkDistance + // ); + + // if (!path || !path.length) { + // throw new Error("move: Failed to generate path."); + // } + // node.x = tmpX; + // node.y = tmpY; + // } + + if (settings.retry <= 3 && target.distance > (useTeleport ? 120 : 60)) { + settings.retry = 10; + } + + path.reverse(); + settings.pop && path.pop(); + PathDebug.drawPath(PATH_DEBUG_ID, 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(); + } + + Config.DebugMode.Shrines && ShrineHooks.check(); + + 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(PATH_DEBUG_ID); + 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_DEBUG_ID, 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(PATH_DEBUG_ID); + Config.DebugMode.Shrines && ShrineHooks.flush(); + + 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) { + const node = new PathNode(x, y); + + 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 (node.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(function () { return 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) { + if (e instanceof ScriptError) { + throw 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(function () { + return 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(function () { + return 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(function () { + return 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(function () { + return 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(function () { + return 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 + ? (function () { + 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(function () { + return 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(function () { + return 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(function (a) { + return 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(function () { + return 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(function (a) { + return 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(function () { + return 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(function () { + return 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(function () { + return 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) : "") + ); + } + + // There might be multiple stairs with the same class id nearby. + // Given we've walked to the stairs, pick the closest one. + if (type === sdk.unittype.Stairs) { + unit = getUnits(type, id).sort(Sort.units).first() || unit; + } + + 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 (Pather.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); + let checkA1Success = function () { + return me.gameReady && me.inArea(sdk.areas.RogueEncampment); + }; + + if (!!npc && npc.distance < 50) { + if (npc && npc.openMenu()) { + Misc.useMenu(sdk.menu.GoWest); + + if (!Misc.poll(checkA1Success, 2000, 100, true)) { + 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(function (area) { + return getWaypoint(Pather.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(Pather.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, 3); + 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) { + nativeDelay(1500); + + break MainLoop; + } + + nativeDelay(20); + } + + while (!me.gameReady) { + nativeDelay(1000); + } + + // In case lag causes the wp menu to stay open + Misc.poll(function () { + return 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 }; + Pather.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 (Pather.wpAreas.includes(currArea) + && !getWaypoint(Pather.wpAreas.indexOf(currArea))) { + this.getWP(currArea); + } + } + + if (me.inTown && this.nextAreas[currArea] !== targetArea + && Pather.wpAreas.includes(targetArea) && getWaypoint(Pather.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..656516a21 --- /dev/null +++ b/d2bs/kolbot/libs/core/Pickit.js @@ -0,0 +1,832 @@ +/** +* @filename Pickit.js +* @author kolton, theBGuy +* @desc handle item pickup +* +*/ + +/** + * @namespace Pickit + */ +const Pickit = { + enabled: true, + 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 + ], + /** @type {{ reason: string, gid: number }[]} */ + systemKeep: [], + + /** + * @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))) { + let existingGem = Pickit.systemKeep.find((el) => el.reason === "GemHunter"); + if (existingGem && existingGem.gid === unit.gid) { + return resultObj(Pickit.Result.WANTED, "GemHunter"); + } + if (!existingGem || !me.getItem(-1, -1, existingGem.gid)) { + existingGem && Pickit.systemKeep.findAndRemove((el) => el.reason === "GemHunter"); + // 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) { + Pickit.systemKeep.push({ reason: "GemHunter", gid: unit.gid }); + 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.x = unit.x; + this.y = unit.y; + this.area = unit.area; + this._useTk = (Skill.haveTK && Pickit.tkable.includes(this.type)); + this.picked = false; + } + + Object.defineProperty(ItemStats.prototype, "useTk", { + get: function () { + if (!this._useTk) return false; + let dist = this.distance; + let coll = CollMap.checkColl(me, this, sdk.collision.WallOrRanged); + return dist > 5 && dist < 20 && !coll; + }, + /** + * @this {ItemStats} + * @param {boolean} value + */ + set: function (value) { + this._useTk = value; + } + }); + + 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)) { + nativeDelay(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 || !Pickit.enabled) 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) { + /** @param {ItemUnit} check */ + let sameItem = function (check) { + return (check.gid === item.gid); + }; + 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(sameItem)) 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))); + } + + if (!_item || _item.gid === undefined) { + console.warn("Item disappeared or became invalid while trying to pick " + itemName); + Pickit.pickList.shift(); + continue; + } + + // 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 + + " ÿc0(" + Storage.Inventory.UsedSpacePercent() + "% full)" + ); + 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) { + if (me.dead || !Pickit.enabled) return false; + const _removeList = []; + const itemList = []; + const range = Config.FastPickRange || Config.PickRange; + + for (let gid of this.gidList) { + _removeList.push(gid); + let 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 + let 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..08074b0bd --- /dev/null +++ b/d2bs/kolbot/libs/core/Precast.js @@ -0,0 +1,622 @@ +/** +* @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) { + if ((e instanceof ScriptError)) { + throw 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()); + + // not sure why this happens but sometimes clicks on other characters while casting + if (getUIFlag(sdk.uiflags.TradePrompt)) { + me.cancel(); + } + } + } + } while (unit.getNext()); + } + + // not sure why this happens but sometimes clicks on other characters while casting + if (getUIFlag(sdk.uiflags.TradePrompt)) { + me.cancel(); + } + + // 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[me.classid].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()) { + if (Pather.useWaypoint("random")) { + let wp = Game.getObject("waypoint"); + let spot = Pather.findSpotAtDistance(wp, 8); + + if (spot) { + Pather.move(spot, { allowNodeActions: false }); + } + Precast.doPrecast(force); + } + } else { + Precast.doPrecast(force); + } + Pather.useWaypoint(returnTo); + } catch (e) { + if ((e instanceof ScriptError)) { + throw 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..b763c7f91 --- /dev/null +++ b/d2bs/kolbot/libs/core/Prototypes.js @@ -0,0 +1,2633 @@ +/** +* @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 + const _test = original(-1); + + const [first, second] = args; + const ret = original.apply(this, args); + + // deal with bug + if ( + (first === sdk.unittype.Monster && 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: { + /** @this {Monster | NPCUnit} */ + get: function () { + const mercClassids = [sdk.mercs.Rogue, sdk.mercs.Guard, sdk.mercs.IronWolf, sdk.mercs.A5Barb]; + return ( + this.type === sdk.unittype.Monster + && !mercClassids.includes(this.classid) + && 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); + }, + }, + isSoul: { + get: function () { + return [ + sdk.monsters.BurningSoul1, + sdk.monsters.BurningSoul2, + sdk.monsters.SoulKiller1, + sdk.monsters.SoulKiller2, + sdk.monsters.SoulKiller3, + sdk.monsters.SoulKiller4, + sdk.monsters.SoulKiller5, + ].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; + }, + }, + isDruidVine: { + /** @this {Unit} */ + get: function () { + return [ + sdk.monsters.PoisonCreeper, sdk.monsters.CarrionVine, sdk.monsters.SolarCreeper, + ].includes(this.classid); + } + }, + isEnchantable: { + /** @this {Monster} */ + get: function () { + if (this.type > sdk.unittype.Monster) { + throw new Error("Unit.isEnchantable: Must be used with monster units."); + } + return (this.isMonster && !this.isNPC && !this.isDruidVine && this.classid !== sdk.monsters.Raven); + }, + }, + isWalking: { + /** @this {Monster} */ + get: function () { + return (this.mode === sdk.monsters.mode.Walking && (this.targetx !== this.x || this.targety !== this.y)); + } + }, + isRunning: { + /** @this {Monster} */ + get: function () { + return (this.mode === sdk.monsters.mode.Running && (this.targetx !== this.x || this.targety !== this.y)); + } + }, + isMoving: { + /** @this {Monster} */ + 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.Monster) { + throw new Error("Unit.idle: Must be used with Player or Monster units."); + } + switch (this.type) { + case sdk.unittype.Player: + // Dead is pretty idle too + return (this.mode === sdk.player.mode.StandingOutsideTown + || this.mode === sdk.player.mode.StandingInTown || this.mode === sdk.player.mode.Idle); + case sdk.unittype.Monster: + return (this.mode === sdk.monsters.mode.Standing); + default: + return false; + } + } + }, + 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); + } + }, + size: { + /** @this {Monster | Player} */ + get: function () { + if (this.type > sdk.unittype.Monster) { + throw new Error("Unit.size: Must be used with monster or player units."); + } + const baseId = getBaseStat("monstats", this.classid, "baseid"); + const size = getBaseStat("monstats2", baseId, "sizex"); + + // in case base stat returns something outrageous + return (typeof size !== "number" || size < 1 || size > 3) ? 3 : size; + } + } +}); + +/** + * @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 unitName = this.name; + const menuId = (function (mode) { + switch (true) { + case mode === "gamble": + return sdk.menu.Gamble; + case mode === "repair": + return sdk.menu.TradeRepair; + case mode === "shop" && String.isEqual(NPC.Charsi, unitName): + case mode === "shop" && String.isEqual(NPC.Fara, unitName): + case mode === "shop" && String.isEqual(NPC.Hratli, unitName): + case mode === "shop" && String.isEqual(NPC.Halbu, unitName): + case mode === "shop" && String.isEqual(NPC.Larzuk, unitName): + return sdk.menu.TradeRepair; + case mode === "shop": + return sdk.menu.Trade; + default: + throw new Error("Unit.startTrade: Invalid mode " + mode); + } + })(mode.toLowerCase()); + + 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) { + if ((e instanceof ScriptError)) { + throw 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 {{ have: boolean, item: 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; + } else { + throw new Error("No valid charged skills found on this item"); + } + } + + 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..a28711e10 --- /dev/null +++ b/d2bs/kolbot/libs/core/Runewords.js @@ -0,0 +1,375 @@ +/** +* @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()) { + continue; + } + + if (isNaN(base)) { + let cleanName = base.replace(/\s+/g, "").toLowerCase(); + + if (NTIPAliasClassID.hasOwnProperty(cleanName)) { + Config.Runewords[i][1] = NTIPAliasClassID[cleanName]; + } 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); + + Config.Runewords.sort(function (a, b) { + const aPriority = a[3] || 0; + const bPriority = b[3] || 0; + return bPriority - aPriority; + }); + + 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..54a8f9bee --- /dev/null +++ b/d2bs/kolbot/libs/core/Skill.js @@ -0,0 +1,778 @@ +/** +* @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) { + if ((e instanceof ScriptError)) { + throw e; + } + return false; + } + }, + + /** + * Cast a skill on self, Unit or coords + * @param {number} skillId + * @param {number} hand + * @param {number | Unit | PathNode} x + * @param {number} [y] + * @param {ItemUnit} [item] + * @param {number} [weaponSlot] + * @returns {boolean} + * + * @todo Track skills cast so we can determine most used, skills, mana expenditure, etc. + */ + 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) { + if ((e instanceof ScriptError)) { + throw e; + } + console.error(e); + // maybe rebuild list? + // Skill.getCharges(); + return false; + } finally { + if (weaponSwitch !== me.weaponswitch) { + me.switchWeapons(weaponSwitch); + } + if (item) { + charge.update(item); + } + } + }, + + get haveTK () { + 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..af9d527c5 --- /dev/null +++ b/d2bs/kolbot/libs/core/Storage.js @@ -0,0 +1,749 @@ +/* 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) { + if (e2 instanceof ScriptError) { + throw 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) { + if (e instanceof ScriptError) { + throw 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) { + if (e instanceof ScriptError) { + throw 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..8a31a6f71 --- /dev/null +++ b/d2bs/kolbot/libs/core/Town.js @@ -0,0 +1,2433 @@ +/** +* @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, + /** @type {Set} */ + dontStashGids: new Set(), + choresActive: false, + + 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 { + Town.choresActive = true; + 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(); + Pickit.pickItems(); + 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(); + + if (Config.SortSettings.SortInventory) { + Storage.Inventory.SortItems(); + } + + 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); + } + + Town.choresActive = false; + 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; + let _needScrolls = ( + me.checkScrolls(sdk.items.TomeofTownPortal) < 13 + || Config.FieldID.Enabled && me.checkScrolls(sdk.items.TomeofIdentify) < 13 + ); + 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 && !_needScrolls) { + choices = new Set([npcs.Key, npcs.Repair, npcs.Gamble, npcs.Shop]); + } + + if (_needScrolls) { + choices.add(npcs.Shop); + choices.delete(npcs.Repair); + choices.delete(npcs.Gamble); + choices.delete(npcs.Key); + } + + 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) { + if ((e instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw e; + } + 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) { + if ((e2 instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw 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(function (pot) { + return 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) { + if ((e instanceof ScriptError)) { + throw 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.dontStashGids.has(item.gid)) { + return false; + } + if (Town.ignoreType(item.itemType)) { + return false; + } + if ([sdk.items.quest.HoradricStaff, sdk.items.quest.KhalimsWill].includes(item.classid)) { + return false; + } + /** + * @todo add sorting here first if we can't fit the item + */ + return Storage.Stash.CanFit(item); + }, + + /** + * @param {boolean} [stashGold=true] + * @returns {boolean} + */ + stash: function (stashGold = true) { + if (!me.needStash()) return true; + + if (!getUIFlag(sdk.uiflags.Stash)) { + 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) { + if ((e instanceof ScriptError)) { + throw 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) { + let npc; + // 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? + 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) { + if ((e instanceof ScriptError)) { + throw e; + } + console.error(e); + } + sold && delay(250); // would a rand delay be better? + }); + // quick check before closing shopui + if (Town.choresActive && getUIFlag(sdk.uiflags.Shop) && npc && typeof npc === "object") { + let _needTpScrolls = me.checkScrolls(sdk.items.TomeofTownPortal) < 13; + let _needIdScrolls = Config.FieldID.Enabled && me.checkScrolls(sdk.items.TomeofIdentify) < 13; + if (_needTpScrolls && npc.getItem(sdk.items.ScrollofTownPortal)) { + console.info(null, "Buying some tp scrolls before we leave"); + Town.fillTome(sdk.items.TomeofTownPortal); + } + if (_needIdScrolls && npc.getItem(sdk.items.ScrollofIdentify)) { + console.info(null, "Buying some id scrolls before we leave"); + Town.fillTome(sdk.items.TomeofIdentify); + } + let _needPots = me.needPotions(); + /** @param {ItemUnit} item */ + let isPot = function (item) { + return [ + sdk.items.type.HealingPotion, + sdk.items.type.ManaPotion, + sdk.items.type.RejuvPotion + ].includes(item.itemType); + }; + if (_needPots && npc.getItems().some(isPot)) { + console.info(null, "Buying some pots before we leave"); + Town.buyPotions(); + } + let _needRepair = me.needRepair(); + if (_needRepair && String.isEqual(Town.tasks.get(me.act).Repair, npc.name)) { + console.info(null, "Repairing before we leave"); + Town.repair(true); + } + } + } + // 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) { + if ((e instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw 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 (e) { + if ((e instanceof ScriptError)) { + throw e; + } + 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) { + if ((e instanceof ScriptError)) { + throw 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) { + if ((e instanceof ScriptError)) { + throw 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; + }, + + /** + * @description get a gem worthy upgrading to inventory. go town if needed + * @returns {boolean} If a gem to upgrade is in inventory + */ + prepareForGemShrine: function () { + /** + * @param {ItemUnit} unit + * @returns {boolean} + */ + function isUpgradePossible(unit) { + return ( + unit.itemType >= sdk.items.type.Amethyst + && unit.itemType <= sdk.items.type.Skull + && !Object.values(sdk.items.gems.Perfect).includes(unit.classid) + ); + } + + const { ClassIdToLocaleString } = require("./GameData/LocaleStringID"); + + /** + * @class + * @param {ItemUnit} gem + * @implements {Partial} + */ + function GemUnit(gem) { + this.itemType = gem.itemType; + this.classid = gem.classid; + this.type = gem.type; + this.mode = gem.mode; + this.gid = gem.gid; + this.quality = gem.quality; + this.ilvl = gem.ilvl; + } + // eslint-disable-next-line no-unused-vars + GemUnit.prototype.getFlag = function (flag) { + return true; + }; + + GemUnit.prototype.getStat = Unit.prototype.getStat; + GemUnit.prototype.getStatEx = Unit.prototype.getStatEx; + + Object.defineProperties(GemUnit.prototype, { + name: { + /** @this {GemUnit} */ + get: function () { + return ClassIdToLocaleString[this.classid] || "Unknown Gem"; + }, + }, + fname: { + /** @this {GemUnit} */ + get: function () { + return ClassIdToLocaleString[this.classid] || "Unknown Gem"; + }, + }, + }); + + /** + * @param {ItemUnit} unit + * @returns {boolean} + */ + function pickitWantsUpgrade(unit) { + // Stub Object to let Pickit Check + let gem = new GemUnit(unit); + // lets upgrade the gem + gem.classid += 1; + return [ + Pickit.Result.WANTED, Pickit.Result.CUBING, + Pickit.Result.RUNEWORD, Pickit.Result.CRAFTING + ].includes(Pickit.checkItem(gem).result); + } + + // do i have any gem worth upgrading + const haveUpGems = me.getItemsEx() + .some(function (p) { + return isUpgradePossible(p) && pickitWantsUpgrade(p); + }); + + if (!haveUpGems) { + console.debug("I dont have any gems worth upgrading"); + return false; + } + // we have gems that must not be upgraded in inventory + const isInventoryClean = !me.getItemsEx() + .some(function (p) { + return p.isInInventory && isUpgradePossible(p) && !pickitWantsUpgrade(p); + }); + + // we have gems that can be upgraded in inventory + const validGem = me.getItemsEx() + .find(function (p) { + return p.isInInventory && isUpgradePossible(p) && pickitWantsUpgrade(p); + }); + + // we already got good gems in inv and no bad games -> take shrine + if (isInventoryClean && validGem) { + console.debug("Inventory looks good. lets take the shrine."); + Town.dontStashGids.add(validGem.gid); + return true; + } + + // go to town + const preArea = me.area; + const preAct = me.act; + if (!me.inTown) { + // not an essential function -> handle thrown errors + try { + Town.goToTown(); + } catch (e) { + if ((e instanceof ScriptError)) { + throw e; + } + return false; + } + } + + // stash the bad gems first + if (!isInventoryClean) { + Town.identify(); + Town.clearInventory(); + Town.stash(); + } + + // get any good gem. flawless first (by lvlreq) + const goodGem = me.getItemsEx() + .filter(function (p) { + return isUpgradePossible(p) && pickitWantsUpgrade(p); + }) + .sort (function(a, b) { + return b.lvlreq - a.lvlreq; + }) + .first(); + + let sucess = false; + + if (goodGem) { + console.debug("get Gem: " + goodGem.fname); + Town.stash(false); + if (Storage.Inventory.MoveTo(goodGem)) { + sucess = true; + Town.dontStashGids.add(goodGem.gid); + Item.logger("Inventoried", goodGem); + } + } + + if (me.area !== preArea) { + // leave town + me.act !== preAct && Town.goToTown(preAct); + Town.move("portalspot"); + + if (!Pather.usePortal(null, me.name)) { + try { + Pather.usePortal(preArea, me.name); + } catch (e) { + if ((e instanceof ScriptError)) { + throw e; + } + throw new Error("Town.visitTown: Failed to go back from town"); + } + } + + Config.PublicMode && Pather.makePortal(); + } + return sucess; + } +}; diff --git a/d2bs/kolbot/libs/core/Util.js b/d2bs/kolbot/libs/core/Util.js new file mode 100644 index 000000000..2253f54d5 --- /dev/null +++ b/d2bs/kolbot/libs/core/Util.js @@ -0,0 +1,673 @@ +/** + * @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 + * @param {number} [initialByte] - Optional initial byte to add to the packet + * @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(sdk.packets.send.RunToLocation).word(x).word(y).send(); + */ + function PacketBuilder (initialByte) { + // globals DataView ArrayBuffer + if (this.__proto__.constructor !== PacketBuilder) { + throw new Error("PacketBuilder must be called with 'new' operator!"); + } + + let queue = []; + let count = 0; + + // If initial byte was provided, add it + if (typeof initialByte === "number") { + queue.push({ type: "Uint8", size: 1, data: initialByte }); + count += 1; + } + + /** + * Internal function to add data to the packet queue + * @param {string} type - The data type + * @param {number} size - The byte size + * @returns {function} - A function that accepts data and adds it to the queue + */ + function enqueue(type, size) { + return function() { + for (let i = 0; i < arguments.length; i++) { + let arg = arguments[i]; + + 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"); + + /** + * Builds a DataView from the packet data + * @returns {DataView} - The built packet as a DataView + */ + this.buildDataView = function () { + let dv = new DataView(new ArrayBuffer(count)), i = 0; + queue.forEach(function (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; + }; + + /** + * Sends the packet to the server + * @returns {PacketBuilder} - Returns this for method chaining + */ + this.send = function() { + sendPacket(this.buildDataView().buffer); + return this; + }; + + /** + * Spoofs the packet to the client + * @returns {PacketBuilder} - Returns this for method chaining + */ + this.spoof = function() { + getPacket(this.buildDataView().buffer); + return 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) { + if ((err instanceof ScriptError)) { + throw err; + } + 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) { + if ((e instanceof ScriptError)) { + throw 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..4e631bc9a --- /dev/null +++ b/d2bs/kolbot/libs/globals.js @@ -0,0 +1,375 @@ +/** +* @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; + }, + }); +} + +if (!global.hasOwnProperty("hardDelay")) { + /** + * @description blocks the thread for specified milliseconds - use sparingly this does not yield to the other threads so it + * can potentially cause the heartbeat thread to crash + * @param {number} ms + */ + Object.defineProperty(global, "hardDelay", { + value: function (ms) { + let start = getTickCount(); + while (getTickCount() - start < ms) { + // + } + }, + }); +} + +if (!global.hasOwnProperty("nativeDelay")) { + /** + * @description Use sparingly, this calls the original delay function bypassing the background worker checks + * @param {number} ms + */ + Object.defineProperty(global, "nativeDelay", { + value: function (ms) { + if (global.hasOwnProperty("_delay")) { + return global._delay(ms); + } + return delay(ms); + }, + }); +} + +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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; +}; + +/** + * Deep merge objects, handling nested properties properly + * @param {Object} target - Target object to merge into + * @param {Object} source - Source object to merge from + * @returns {Object} Merged object + */ +function deepMerge(target, source) { + if (!source || typeof source !== "object") { + return target; + } + + // Immutable merge + // let result = Object.assign({}, target); + + // for (let key in source) { + // if (source.hasOwnProperty(key)) { + // if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) { + // // Recursively merge nested objects + // result[key] = deepMerge(result[key] || {}, source[key]); + // } else { + // // Direct assignment for primitives and arrays + // result[key] = source[key]; + // } + // } + // } + + // return result; + + // Mutable merge - this modifies the target object + for (let key in source) { + if (source.hasOwnProperty(key)) { + if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) { + if (!target[key] || typeof target[key] !== "object" || Array.isArray(target[key])) { + target[key] = {}; + } + deepMerge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + } + + return target; +} 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..fd5df145c 100644 --- a/d2bs/kolbot/libs/manualplay/config/Amazon.js +++ b/d2bs/kolbot/libs/manualplay/config/Amazon.js @@ -18,194 +18,209 @@ 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + /* 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..6f9e5c52a 100644 --- a/d2bs/kolbot/libs/manualplay/config/Assassin.js +++ b/d2bs/kolbot/libs/manualplay/config/Assassin.js @@ -18,203 +18,218 @@ 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + /* 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..b88f4b161 100644 --- a/d2bs/kolbot/libs/manualplay/config/Barbarian.js +++ b/d2bs/kolbot/libs/manualplay/config/Barbarian.js @@ -18,192 +18,207 @@ 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + /* 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..ea20146cb 100644 --- a/d2bs/kolbot/libs/manualplay/config/Druid.js +++ b/d2bs/kolbot/libs/manualplay/config/Druid.js @@ -18,196 +18,211 @@ 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + /* 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..5655035ca 100644 --- a/d2bs/kolbot/libs/manualplay/config/Necromancer.js +++ b/d2bs/kolbot/libs/manualplay/config/Necromancer.js @@ -18,217 +18,232 @@ 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + /* 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..58aa71365 100644 --- a/d2bs/kolbot/libs/manualplay/config/Paladin.js +++ b/d2bs/kolbot/libs/manualplay/config/Paladin.js @@ -18,196 +18,211 @@ 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + /* 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..bdb2a143d 100644 --- a/d2bs/kolbot/libs/manualplay/config/Sorceress.js +++ b/d2bs/kolbot/libs/manualplay/config/Sorceress.js @@ -18,195 +18,213 @@ 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 = []; + // Skip specific monsters by classid. For a list of monster names and ids, see -> \kolbot\libs\modules\sdk.js or usee sdk.monsters.MonsterID enums. + // Example: Config.SkipId = [sdk.monsters.FireTower, 310]; + Config.SkipId = []; + // 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 + + /** + * Advanced Skip config. Allows for more granular control over which monsters to skip. + * @type {({ classid?: number, name?: string, spectype?: number, enchant?: number[], aura?: number[], immunity?: DamageType[] }|((unit: Monster) => boolean))[]} + * Multiple entries are separated by commas + */ + Config.AdvancedSkipCheck = [ + // { + // name: getLocaleString(sdk.locale.monsters.Pindleskin), + // immunity: ["lightning"] + // } + ]; + + /* 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..2774cb89d 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js @@ -1,780 +1,1068 @@ /** -* @filename ActionHooks.js -* @author theBGuy -* @desc Action hooks for MapThread -* -*/ - -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; - } -}; - -ActionHooks.areaInfo[sdk.areas.MatronsDen] = { - 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}, -}; + * @filename ActionHooks.js + * @author theBGuy + * @desc Action hooks for MapThread + * + */ + +const ActionHooks = (function () { + /** + * @typedef {Object} ActionHookEntry + * @property {string} name - The name of the hook + * @property {string} type - The type of the hook (unit, area, portal) + * @property {string} action - The action to perform (openChest, openPortal, etc.) + * @property {Object} dest - The destination coordinates (x, y) or area ID + * @property {Hook} hook + */ + + /** @param {ActionHookEntry[]} hooks */ + const clearHooks = function (hooks) { + while (hooks.length) { + hooks.pop().hook.remove(); + } + }; + + /** + * @param {string} name - Hook name + * @param {string} type - Hook type (area, unit, portal, wp) + * @param {number|PathNode} dest - Destination area ID or coordinates + * @param {string} [text] - Custom text (defaults to standard by hook type) + * @param {{ do: string, id: number }} [action] - Optional action object + * @returns {ActionHookEntry} + */ + const createActionHook = function (name, type, dest, text, action = false) { + if (!text) { + text = (function () { + switch (name) { + case "Next Area": + return "Num 0: " + getAreaName(dest).replace("Level", "Lvl"); + case "Previous Area": + return "ÿc1Num 1: " + getAreaName(dest).replace("Level", "Lvl"); + case "Waypoint": + return "ÿc9Num 2: WP"; + case "POI": + return "ÿc} */ + const hookObj = { + name: name, + type: type, + dest: dest + }; + + if (action) { + hookObj.action = action; + } + + hookObj.hook = new Text(text, Hooks.dashBoard.x + 5, ActionHooks.yHookLoc()); + + return hookObj; + }; + + /** + * @type {Map} + */ + const specialAreaConfigs = new Map([ + [sdk.areas.BloodMoor, [ + { name: "Side Area", type: "area", dest: sdk.areas.DenofEvil }, + ]], + [sdk.areas.ColdPlains, [ + { name: "Side Area", type: "area", dest: sdk.areas.BurialGrounds }, + ]], + [sdk.areas.BurialGrounds, [ + { name: "Side Area", type: "area", dest: sdk.areas.Mausoleum }, + ]], + [sdk.areas.Tristram, [ + { + name: "POI2", + type: "unit", + action: { do: "openChest", id: sdk.quest.chest.Wirt }, + dest: { x: 25048, y: 5177 }, + text: "ÿc} + */ + const areaConfigs = new Map([ + [sdk.areas.Tristram, [ + { name: "Previous Area", type: "portal", dest: sdk.areas.StonyField }, + ]], + + [sdk.areas.MooMooFarm, [ + { name: "Previous Area", type: "portal", dest: sdk.areas.RogueEncampment }, + ]], + + [sdk.areas.PalaceCellarLvl3, [ + { name: "Previous Area", type: "area", dest: sdk.areas.PalaceCellarLvl2 }, + { name: "Next Area", type: "portal", dest: sdk.areas.ArcaneSanctuary }, + ]], + + [sdk.areas.ArcaneSanctuary, [ + { name: "Previous Area", type: "area", dest: sdk.areas.PalaceCellarLvl3 }, + { name: "Next Area", type: "area", dest: sdk.areas.CanyonofMagic }, + ]], + + [sdk.areas.CanyonofMagic, [ + { name: "Previous Area", type: "portal", dest: sdk.areas.ArcaneSanctuary }, + ]], + + [sdk.areas.DuranceofHateLvl3, [ + { name: "Previous Area", type: "area", dest: sdk.areas.DuranceofHateLvl2 }, + { name: "Next Area", type: "area", dest: sdk.areas.PandemoniumFortress }, + ]], + + [sdk.areas.NihlathaksTemple, [ + { + name: "Previous Area", + type: "unit", + dest: { x: 10071, y: 13305 }, + action: { do: "openPortal", id: sdk.areas.Harrogath } + }, + ]], + + [sdk.areas.Abaddon, [ + { name: "Previous Area", type: "portal", dest: sdk.areas.FrigidHighlands }, + ]], + + [sdk.areas.PitofAcheron, [ + { name: "Previous Area", type: "portal", dest: sdk.areas.ArreatPlateau }, + ]], + + [sdk.areas.InfernalPit, [ + { name: "Previous Area", type: "portal", dest: sdk.areas.FrozenTundra }, + ]], + + [sdk.areas.ThroneofDestruction, [ + { name: "Previous Area", type: "area", dest: sdk.areas.WorldstoneLvl3 }, + { name: "Next Area", type: "area", dest: sdk.areas.WorldstoneChamber }, + ]], + ]); + + const areaInfo = new Map([ + [sdk.areas.MatronsDen, { + 11: { x: 20023, y: 7643 }, + 20: { x: 20303, y: 7803 }, + 21: { x: 20263, y: 7683 }, + }], + + [sdk.areas.FurnaceofPain, { + 14: { x: 20138, y: 14873 }, + 15: { x: 20138, y: 14563 }, + }], + ]); + + const nextAreas = new Map([ + [sdk.areas.TamoeHighland, sdk.areas.MonasteryGate], + [sdk.areas.SpiderForest, sdk.areas.FlayerJungle], + [sdk.areas.GreatMarsh, sdk.areas.FlayerJungle], + [sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail], + [sdk.areas.GlacialTrail, sdk.areas.FrozenTundra], + [sdk.areas.AncientsWay, sdk.areas.ArreatSummit], + ]); + + const 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, + ]; + + const addTombs = function () { + if (!me.inArea(sdk.areas.CanyonofMagic)) { + return; + } + + const correctTomb = getRoom().correcttomb; + const currExits = getArea() + .exits.filter((ex) => 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(); + ActionHooks.hooks.push(createActionHook( + "POI" + (i - 3), + "area", + curr.target + )); + } + + curr = currExits.shift(); + ActionHooks.hooks.push(createActionHook( + "Side Area", + "area", + curr.target + )); + + curr = currExits.shift(); + ActionHooks.hooks.push(createActionHook( + "POI", + "area", + curr.target + )); + }; + + const addUberPortals = function () { + // Only check for portals if we're in Harrogath in Hell difficulty + if (!me.inArea(sdk.areas.Harrogath) || !me.hell) { + return; + } + + const uberPortalIds = [ + sdk.areas.MatronsDen, + sdk.areas.ForgottenSands, + sdk.areas.FurnaceofPain, + sdk.areas.UberTristram, + ]; + + const uberPortals = getUnits(sdk.unittype.Object, sdk.objects.RedPortal); + + if (!uberPortals || !uberPortals.some(portal => uberPortalIds.includes(portal.objtype))) { + return; + } + + TextHooks.displaySettings = false; + ActionHooks.frame.push({ + name: "portalbox", + hook: new Box(Hooks.portalBoard.x - 8, Hooks.portalBoard.y + Hooks.resfix.y - 17, 190, 70, 0x0, 1, 0), + }); + + ActionHooks.frame.push({ + name: "portalframe", + hook: new Frame(Hooks.portalBoard.x - 8, Hooks.portalBoard.y + Hooks.resfix.y - 17, 190, 70, 0), + }); + + const portalConfig = [ + { area: sdk.areas.MatronsDen, name: "Matron's Den", key: 5, yOffset: 0 }, + { area: sdk.areas.ForgottenSands, name: "Forgotten Sands", key: 6, yOffset: 15 }, + { area: sdk.areas.FurnaceofPain, name: "Furnace of Pain", key: 7, yOffset: 30 }, + { area: sdk.areas.UberTristram, name: "Uber Tristam", key: 8, yOffset: 45 } + ]; + + portalConfig.forEach(function (config) { + if (Pather.getPortal(config.area)) { + ActionHooks.portals.push({ + name: config.name, + type: "portal", + dest: config.area, + hook: new Text( + "ÿc1Num " + config.key + ": " + config.name, + Hooks.portalBoard.x, + Hooks.portalBoard.y + Hooks.resfix.y + config.yOffset + ), + }); + } + }); + }; + + const addChaosSeals = function () { + if (!me.inArea(sdk.areas.ChaosSanctuary)) { + return; + } + const sealIds = [sdk.objects.DiabloSealInfector, sdk.objects.DiabloSealSeis, sdk.objects.DiabloSealVizier]; + const seals = Game.getPresetObjects(sdk.areas.ChaosSanctuary).filter(function (seal) { + return sealIds.includes(seal.id); + }).map(function (seal) { + return seal.realCoords(); + }); + + const infSeal = seals.find(function (seal) { + return seal.id === sdk.objects.DiabloSealInfector; + }); + const seisSeal = seals.find(function (seal) { + return seal.id === sdk.objects.DiabloSealSeis; + }); + const vizSeal = seals.find(function (seal) { + return seal.id === sdk.objects.DiabloSealVizier; + }); + + if (infSeal) { + ActionHooks.hooks.push(createActionHook( + "POI4", + "unit", + { x: infSeal.x, y: infSeal.y }, + "ÿc k === keycode)) { + return; + } + + ActionHooks.action = keycode; + }, + + checkAction: function () { + if (!ActionHooks.action) { + return; + } + + const obj = { type: false, dest: false, action: false }; + + 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)) { + processToggleKeys(); + + return; + } + + const qolObj = processActionKey(); + if (qolObj && qolObj.action) { + Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(qolObj)); + + return; + } + + const ctrlObj = processCtrlKey(); + if (ctrlObj && ctrlObj.action) { + Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(ctrlObj)); + + return; + } + + let hook = processPOIKey(); + if (hook) { + Object.assign(obj, hook); + Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(obj)); + + return; + } + + hook = ((action) => { + switch (action) { + case sdk.keys.Numpad0: + return ActionHooks.getHook("Next Area"); + case sdk.keys.Numpad1: + return ActionHooks.getHook("Previous Area"); + case sdk.keys.Numpad2: + return ActionHooks.getHook("Waypoint"); + case sdk.keys.Numpad3: + return ActionHooks.getHook("POI"); + case sdk.keys.Numpad4: + return ActionHooks.getHook("Side Area"); + case 188: // shift < + return TextHooks.getHook("Previous Act", TextHooks.qolHooks); + case 190: // shift > + return TextHooks.getHook("Next Act", TextHooks.qolHooks); + default: + return null; + } + })(this.action); + + if (hook) { + Object.assign(obj, hook); + Messaging.sendToScript(MapMode.mapHelperFilePath, JSON.stringify(obj)); + } + } catch (e) { + console.error(e); + } finally { + ActionHooks.action = null; + } + }, + + check: function () { + if (!this.enabled) return; + + ActionHooks.checkAction(); + + if (me.area !== this.currArea) { + this.flush(); + + while (!me.area || !me.gameReady) { + delay(150); + } + + ActionHooks.add(me.area); + TextHooks.events.emit("areachange", me.area); + ActionHooks.currArea = me.area; + } + }, + + yHookLoc: function () { + return 545 - this.hooks.length * 10 + Hooks.resfix.y; + }, + + /** + * Creates new action hook based on our current area + * @param {number} area + */ + add: function (area) { + let bossX; + + // Specific area override + if (me.inArea(sdk.areas.CanyonofMagic) && !nextAreas.has(sdk.areas.CanyonofMagic)) { + nextAreas.set(sdk.areas.CanyonofMagic, getRoom().correcttomb); + } + + if (specialAreaConfigs.has(area)) { + for (let config of specialAreaConfigs.get(area)) { + this.hooks.push(createActionHook( + config.name, + config.type, + config.dest, + Object.hasOwn(config, "text") ? config.text : "", + Object.hasOwn(config, "action") ? config.action : false + )); + } + } + + addTombs(); + addChaosSeals(); + addCowPortal(); + + const poi = VectorHooks.getPOI(); + + if (poi) { + this.hooks.push(createActionHook( + "POI", + "unit", + { x: poi.x, y: poi.y }, + "ÿc 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); - }; -}; - -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; -}; 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..6b28f37ac 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js @@ -6,110 +6,114 @@ */ 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, + /** @type {{ shrine: ObjectUnit, hook: Text }[]} */ + hooks: [], + shrines: new Map([ + [sdk.shrines.Refilling, "Refilling"], + [sdk.shrines.Health, "Health"], + [sdk.shrines.Mana, "Mana"], + [sdk.shrines.HealthExchange, "Health Exchange"], + [sdk.shrines.ManaExchange, "Mana Exchange"], + [sdk.shrines.Armor, "Armor"], + [sdk.shrines.Combat, "Combat"], + [sdk.shrines.ResistFire, "Resist Fire"], + [sdk.shrines.ResistCold, "Resist Cold"], + [sdk.shrines.ResistLightning, "Resist Lightning"], + [sdk.shrines.ResistPoison, "Resist Poison"], + [sdk.shrines.Skill, "Skill"], + [sdk.shrines.ManaRecharge, "Mana Recharge"], + [sdk.shrines.Stamina, "Stamina"], + [sdk.shrines.Experience, "Experience"], + [sdk.shrines.Enirhs, "Enirhs"], + [sdk.shrines.Portal, "Portal"], + [sdk.shrines.Gem, "Gem"], + [sdk.shrines.Fire, "Fire"], + [sdk.shrines.Monster, "Monster"], + [sdk.shrines.Exploding, "Exploding"], + [sdk.shrines.Poison, "Poison"] + ]), + + 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.remove(); + this.hooks.splice(i, 1); + + i -= 1; + } + } + + let shrine = Game.getObject(); + + if (shrine) { + do { + if (!ShrineHooks.shrines.has(shrine.objtype)) { + continue; + } + if (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()); + } + }, + + /** @param {ObjectUnit} shrine */ + newHook: function (shrine) { + let typeName = ShrineHooks.shrines.get(shrine.objtype); + return typeName ? new Text(typeName, shrine.x, shrine.y, 4, 6, 2, true) : null; + }, + + /** @param {ObjectUnit} shrine */ + add: function (shrine) { + if (!shrine.objtype) return; + + this.hooks.push({ + shrine: copyUnit(shrine), + hook: this.newHook(shrine) + }); + }, + + /** @param {ObjectUnit} shrine */ + getHook: function (shrine) { + for (let entry of ShrineHooks.hooks) { + if (entry.shrine.gid === shrine.gid) { + return entry.hook; + } + } + + return false; + }, + + /** @param {ObjectUnit} shrine */ + remove: function (shrine) { + for (let i = 0; i < this.hooks.length; i++) { + if (this.hooks[i].shrine.gid === shrine.gid) { + this.hooks[i].hook.remove(); + this.hooks.splice(i, 1); + + return true; + } + } + + return false; + }, + + flush: function () { + while (this.hooks.length) { + this.hooks.pop().hook.remove(); + } + } }; - -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"; diff --git a/d2bs/kolbot/libs/manualplay/hooks/TextHooks.d.ts b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.d.ts new file mode 100644 index 000000000..99d37ebea --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.d.ts @@ -0,0 +1,66 @@ +/** + * An entry in the hook arrays + */ +export interface HookEntry { + name: string; + hook: Hook; + dest?: number; + type?: string; +} + +declare global { + /** + * The TextHooks module + */ + const TextHooks: { + events: Events; + enabled: boolean; + displayTitle: boolean; + displaySettings: boolean; + frameworkDisplayed: boolean; + frameYSizeScale: number; + frameYLocScale: number; + settingsModifer: number; + dashBoardWidthScale: number; + statusFrameYSize: number; + qolFrameYSize: number; + statusHooks: HookEntry[]; + dashBoard: HookEntry[]; + qolHooks: HookEntry[]; + hooks: HookEntry[]; + + /** + * Check and update the hooks + */ + check(): void; + + /** + * Update a hook's text or add it if it doesn't exist + * @param name - The hook identifier + * @param hookArr - The array containing the hooks + * @param text - The text to update + */ + updateHook(name: string, hookArr: HookEntry[], text: string): void; + + /** + * Add a hook to the specified array + * @param name - The hook identifier + * @param hookArr - The array to add the hook to + * @returns Whether the hook was added + */ + add(name: string, hookArr: HookEntry[]): boolean; + + /** + * Find a hook in the specified array + * @param name - The hook identifier + * @param hookArr - The array to search in + * @returns The found hook entry or false if not found + */ + getHook(name: string, hookArr: HookEntry[]): HookEntry | boolean; + + /** + * Remove all hooks + */ + flush(): void; + }; +} diff --git a/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js index d52a4a213..b9ec728a2 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js @@ -1,313 +1,432 @@ /** -* @filename TextHooks.js -* @author theBGuy -* @desc Text hooks for MapThread -* -*/ - -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(); - } - }, -}; + * @filename TextHooks.js + * @author theBGuy + * @desc Text hooks for MapThread + * + */ + +const TextHooks = (function () { + const Events = new (require("../../modules/AsyncEvents")); + const HookFactory = require("../modules/HookFactory"); + + const Y_POS_MODIFIER = 16 + * (Number(!!me.diff) + + Number(!!me.gamepassword) + + Number(!!me.gametype) + + Number(!!me.gamename) + + Number(!!me.gameserverip && !me.realm)); + const IP = (me.gameserverip.length > 0 ? me.gameserverip.split(".")[3] : ""); + const Y_LOC_MAP_SCALE = { 1: 40, 2: 30, 3: 20, 4: 10, 6: -10, 9: -40 }; + + /** @typedef {import("./TextHooks").HookEntry} HookEntry */ + /** @typedef {import("../libs/Hooks")} */ + + /** + * @param {number} click + * @param {number} x + * @param {number} y + * @returns {boolean} + */ + const onClick = function (click, x, y) { + /** + * @param {HookEntry} h1 + * @param {HookEntry} h2 + */ + const sortHooks = function (h1, h2) { + return Math.abs(h1.hook.y - y) - Math.abs(h2.hook.y - y); + }; + + if (click === 0) { + const actionHooks = TextHooks.statusHooks.filter(function ({ hook }) { + return typeof hook.click === "function"; + }).sort(sortHooks); + + if (!actionHooks.length) { + console.log("No action hooks found.", actionHooks); + return false; + } + + if (actionHooks[0].name === "hideSettings" || actionHooks[0].name === "showSettings") { + TextHooks.displaySettings = !TextHooks.displaySettings; + + return true; + } + } + + return false; + }; + + const getScale = function (hkLen) { + if (!!Y_LOC_MAP_SCALE[hkLen]) { + TextHooks.frameYSizeScale = -1 * Y_LOC_MAP_SCALE[hkLen]; + TextHooks.frameYLocScale = Y_LOC_MAP_SCALE[hkLen]; + } else { + TextHooks.frameYSizeScale = 0; + TextHooks.frameYLocScale = 0; + } + + TextHooks.settingsModifer = Math.max(0, hkLen - 3); + }; + + /** @param {HookEntry[]} hooks */ + const clearHooks = (hooks) => { + while (hooks.length) { + hooks.pop().hook.remove(); + } + }; + + const timer = function () { + return " (" + new Date(getTickCount() - me.gamestarttime).toISOString().slice(11, -5) + ")"; + }; + + const textHooks = { + credits: function () { + return HookFactory.createHooks.text({ + name: "credits", + text: "MM by theBGuy", + x: 0, + y: 600 + Hooks.resfix.y, + }); + }, + title: function () { + return HookFactory.createHooks.text({ + name: "title", + text: ":: Running Map-Mode, enter .help in chat to see more commands ::", + x: 0, + y: 13, + }); + }, + ping: function () { + return HookFactory.createHooks.text({ + name: "ping", + text: "Ping: " + me.ping, + x: 785 + Hooks.resfix.x, + y: 56 + Y_POS_MODIFIER, + color: 4, + font: 1, + align: 1, + }); + }, + time: function () { + return HookFactory.createHooks.text({ + name: "time", + text: timer(), + x: 785 + Hooks.resfix.x, + y: 72 + Y_POS_MODIFIER, + color: 4, + font: 1, + align: 1, + }); + }, + ip: function () { + const hook = HookFactory.createHooks.text({ + name: "ip", + text: "IP: " + IP, + x: 785 + Hooks.resfix.x, + y: 88 + Y_POS_MODIFIER, + color: 4, + font: 1, + align: 1, + }); + hook.hook.zorder = 0; + return hook; + }, + key5: function () { + return HookFactory.createHooks.text({ + name: "key5", + text: "Key 5: " + (me.inTown ? "Heal" : "Make Portal"), + x: Hooks.qolBoard.x + 5 + Hooks.resfix.x, + y: 545 - TextHooks.qolHooks.length * 10 + Hooks.resfix.y, + }); + }, + key6: function () { + return HookFactory.createHooks.text({ + name: "key6", + text: "Key 6: " + (me.inTown ? "Open Stash" : "Go To Town"), + x: Hooks.qolBoard.x + 5 + Hooks.resfix.x, + y: 545 - TextHooks.qolHooks.length * 10 + Hooks.resfix.y, + }); + }, + nextAct: function () { + if (me.inTown && me.accessToAct(me.act + 1)) { + return Object.assign({ + dest: me.act + 1, + type: "actChange", + }, HookFactory.createHooks.text({ + name: "Next Act", + text: "Shift > : Next Act", + x: Hooks.qolBoard.x + 5 + Hooks.resfix.x, + y: 545 - TextHooks.qolHooks.length * 10 + Hooks.resfix.y, + })); + } + return null; + }, + previousAct: function () { + if (me.inTown && me.act > 1) { + return Object.assign({ + dest: me.act - 1, + type: "actChange", + }, HookFactory.createHooks.text({ + name: "Previous Act", + text: "Shift < : Previous Act", + x: Hooks.qolBoard.x + 5 + Hooks.resfix.x, + y: 545 - TextHooks.qolHooks.length * 10 + Hooks.resfix.y, + })); + } + return null; + }, + pickitStatus: function () { + return HookFactory.createHooks.text({ + name: "pickitStatus", + text: "ÿc4N-Pad - ÿc0: " + (ItemHooks.pickitEnabled ? "ÿc orginalLen; + }, + + /** + * @param {string} name + * @param {HookEntry[]} hookArr + * @returns {HookEntry|boolean} + */ + getHook: function (name, hookArr = []) { + for (let i = 0; i < hookArr.length; i++) { + if (hookArr[i].name === name) { + return hookArr[i]; + } + } + + return false; + }, + + flush: function () { + if (!Hooks.enabled) { + clearHooks(this.hooks); + TextHooks.frameworkDisplayed = false; + } + + clearHooks(this.statusHooks); + clearHooks(this.dashBoard); + clearHooks(this.qolHooks); + }, + }; +})(); diff --git a/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js index 18a745450..9ea3a2cae 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js @@ -5,397 +5,564 @@ * */ -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; - } -}; +const VectorHooks = (function () { + const nextAreas = new Map([ + [sdk.areas.TamoeHighland, sdk.areas.MonasteryGate], + [sdk.areas.SpiderForest, sdk.areas.FlayerJungle], + [sdk.areas.GreatMarsh, sdk.areas.FlayerJungle], + [sdk.areas.CrystalizedPassage, sdk.areas.GlacialTrail], + [sdk.areas.GlacialTrail, sdk.areas.FrozenTundra], + [sdk.areas.AncientsWay, sdk.areas.ArreatSummit], + [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber], + ]); + + /** + * Areas that contain super chests + */ + const superChestAreas = new Set([ + sdk.areas.CaveLvl2, + sdk.areas.HoleLvl2, + sdk.areas.PitLvl2, + sdk.areas.UndergroundPassageLvl2, + sdk.areas.Crypt, + sdk.areas.Mausoleum, + sdk.areas.StonyTombLvl2, + sdk.areas.AncientTunnels, + sdk.areas.GreatMarsh, + sdk.areas.SpiderCave, + sdk.areas.SwampyPitLvl3, + sdk.areas.DisusedFane, + sdk.areas.ForgottenReliquary, + sdk.areas.ForgottenTemple, + sdk.areas.DisusedReliquary, + sdk.areas.DrifterCavern, + sdk.areas.IcyCellar, + sdk.areas.Abaddon, + sdk.areas.PitofAcheron, + sdk.areas.InfernalPit + ]); + + /** + * Areas that contain large sparkly chests + */ + const largeSparklyChestAreas = new Set([ + sdk.areas.GlacialTrail, + sdk.areas.HallsofAnguish, + sdk.areas.HallsofPain + ]); + + const hellEntranceAreas = new Set([ + sdk.areas.FrigidHighlands, + sdk.areas.ArreatPlateau, + sdk.areas.FrozenTundra + ]); + + /** + * Helper function to create a POI from a preset object + * @param {number} objectId - The ID of the preset object to find + * @param {string} name - Name to display + * @param {Object} [action] - Optional action + * @return {Object|false} POI information or false if not found + */ + function createPOIFromPreset(objectId, name, action = null) { + const unit = Game.getPresetObject(me.area, objectId); + if (!unit) return false; + + const coords = unit.realCoords(); + const poi = { + name: name, + x: coords.x, + y: coords.y + }; + + if (action) { + poi.action = action; + } + + return poi; + } + + /** + * Helper to create POI from preset monster + * @param {number} monsterId - The ID of the preset monster to find + * @param {string} name - Name to display + * @param {Object} [action] - Optional action + * @return {Object|false} POI information or false if not found + */ + function createPOIFromMonster(monsterId, name, action = null) { + const unit = Game.getPresetMonster(me.area, monsterId); + if (!unit) return false; + + const poi = { + name: name, + x: unit.x, + y: unit.y + }; + + if (action) { + poi.action = action; + } + + return poi; + } + + /** + * Helper to create POI from preset stairs + * @param {number} stairId - The ID of the preset stair to find + * @param {string} name - Name to display + * @param {Object} [action] - Optional action + * @return {Object|false} POI information or false if not found + */ + function createPOIFromStair(stairId, name, action = null) { + const unit = Game.getPresetStair(me.area, stairId); + if (!unit) return false; + + const poi = { + name: name, + x: unit.x, + y: unit.y + }; + + if (action) { + poi.action = action; + } + + return poi; + } + + /** + * Helper to create a POI from fixed coordinates + * @param {number} x - X coordinate + * @param {number} y - Y coordinate + * @param {string} name - Name to display + * @param {Object} [action] - Optional action + * @return {Object} POI information + */ + function createFixedPOI(x, y, name, action = null) { + const poi = { + name: name, + x: x, + y: y + }; + + if (action) { + poi.action = action; + } + + return poi; + } + + /** + * Helper to create a POI from a live monster + * @param {number} monsterId - Monster ID to look for + * @param {number} x - Default X coordinate if monster not found + * @param {number} y - Default Y coordinate if monster not found + * @param {string} name - Name to display + * @param {Object} [action] - Optional action + * @return {Object} POI information + */ + function createMonsterPOI(monsterId, x, y, name, action = null) { + const monster = Game.getMonster(monsterId); + const poi = { + name: name, + x: monster ? monster.x : x, + y: monster ? monster.y : y + }; + + if (action) { + poi.action = action; + } + + return poi; + } + + /** + * Handlers for specific locations + */ + const poiHandlers = { + superChest: function() { + return createPOIFromPreset( + sdk.objects.SmallSparklyChest, + "SuperChest", + { do: "openChest", id: sdk.objects.SmallSparklyChest } + ); + }, + + largeSparklyChest: function() { + return createPOIFromPreset( + sdk.objects.LargeSparklyChest, + "SuperChest", + { do: "openChest", id: sdk.objects.LargeSparklyChest } + ); + }, + + talRashaTomb: function() { + let poi = createPOIFromPreset(sdk.quest.chest.HoradricStaffHolder, "Orifice"); + if (poi) return poi; + + return createPOIFromPreset(sdk.objects.SmallSparklyChest, "SuperChest"); + }, + + getHellEntrance: function () { + return createPOIFromPreset( + sdk.objects.RedPortal, + "Hell Entrance", + { do: "usePortal" } + ); + }, + }; + + poiHandlers[sdk.areas.ColdPlains] = function() { + return createPOIFromStair(sdk.exits.preset.AreaEntrance, "Cave Level 1"); + }; + + poiHandlers[sdk.areas.StonyField] = function() { + return createPOIFromMonster( + sdk.monsters.preset.Rakanishu, + "Cairn Stones", + { do: "usePortal", id: sdk.areas.Tristram } + ); + }; + + poiHandlers[sdk.areas.DarkWood] = function() { + return createPOIFromPreset(sdk.quest.chest.InifussTree, "Tree"); + }; + + poiHandlers[sdk.areas.BlackMarsh] = function() { + return createPOIFromStair(sdk.exits.preset.AreaEntrance, "Hole Level 1"); + }; + + poiHandlers[sdk.areas.DenofEvil] = function() { + return createPOIFromMonster(sdk.monsters.preset.Corpsefire, "Corpsefire"); + }; + + poiHandlers[sdk.areas.BurialGrounds] = function() { + return createPOIFromMonster(sdk.monsters.preset.BloodRaven, "Bloodraven"); + }; + + poiHandlers[sdk.areas.TowerCellarLvl5] = function() { + return createPOIFromPreset(sdk.objects.SuperChest, "Countess"); + }; + + poiHandlers[sdk.areas.Barracks] = function() { + return createPOIFromPreset(sdk.quest.chest.MalusHolder, "Smith"); + }; + + poiHandlers[sdk.areas.Cathedral] = function() { + return createFixedPOI(20047, 4898, "BoneAsh"); + }; + + poiHandlers[sdk.areas.CatacombsLvl4] = function() { + return createFixedPOI(22549, 9520, "Andariel"); + }; + + poiHandlers[sdk.areas.Tristram] = function() { + return createMonsterPOI(sdk.monsters.Griswold, 25163, 5170, "Griswold"); + }; + + poiHandlers[sdk.areas.MooMooFarm] = function() { + const monster = Game.getMonster(sdk.monsters.TheCowKing); + if (monster) { + return createFixedPOI(monster.x, monster.y, "Cow King"); + } + const preset = Game.getPresetMonster(me.area, sdk.monsters.preset.TheCowKing); + if (preset) { + return createFixedPOI(preset.x, preset.y, "Cow King"); + } + return false; + }; + + poiHandlers[sdk.areas.LutGholein] = function() { + return createPOIFromStair(sdk.exits.preset.A2EnterSewersDoor, "Sewer's Level 1"); + }; + + poiHandlers[sdk.areas.A2SewersLvl3] = function() { + return createPOIFromPreset(sdk.quest.chest.HoradricScrollChest, "Radament"); + }; + + poiHandlers[sdk.areas.HallsoftheDeadLvl3] = function() { + return createPOIFromPreset( + sdk.quest.chest.HoradricCubeChest, + "Cube", + { do: "openChest", id: sdk.quest.chest.HoradricCubeChest } + ); + }; + + poiHandlers[sdk.areas.ClawViperTempleLvl2] = function() { + return createPOIFromPreset( + sdk.quest.chest.ViperAmuletChest, + "Amulet", + { do: "openChest", id: sdk.quest.chest.ViperAmuletChest } + ); + }; + + poiHandlers[sdk.areas.MaggotLairLvl3] = function() { + return createPOIFromPreset( + sdk.quest.chest.ShaftoftheHoradricStaffChest, + "Staff", + { do: "openChest", id: sdk.quest.chest.ShaftoftheHoradricStaffChest } + ); + }; + + poiHandlers[sdk.areas.ArcaneSanctuary] = function() { + return createPOIFromPreset(sdk.quest.chest.Journal, "Summoner"); + }; + + poiHandlers[sdk.areas.DurielsLair] = function() { + return createFixedPOI(22577, 15609, "Tyrael"); + }; + + poiHandlers[sdk.areas.FlayerJungle] = function() { + return createPOIFromPreset(sdk.quest.chest.GidbinnAltar, "Gidbinn"); + }; + + poiHandlers[sdk.areas.KurastBazaar] = function() { + return createPOIFromStair(sdk.exits.preset.A3EnterSewers, "Sewer's Level 1"); + }; + + poiHandlers[sdk.areas.SpiderCavern] = function() { + return createPOIFromPreset( + sdk.quest.chest.KhalimsEyeChest, + "Eye", + { do: "openChest", id: sdk.quest.chest.KhalimsEyeChest } + ); + }; + + poiHandlers[sdk.areas.FlayerDungeonLvl3] = function() { + return createPOIFromPreset( + sdk.quest.chest.KhalimsBrainChest, + "Brain", + { do: "openChest", id: sdk.quest.chest.KhalimsBrainChest } + ); + }; + + poiHandlers[sdk.areas.A3SewersLvl2] = function() { + return createPOIFromPreset( + sdk.quest.chest.KhalimsHeartChest, + "Heart", + { do: "openChest", id: sdk.quest.chest.KhalimsHeartChest } + ); + }; + + poiHandlers[sdk.areas.RuinedTemple] = function() { + return createPOIFromPreset( + sdk.quest.chest.LamEsensTomeHolder, + "Lam Esen", + { do: "openChest", id: sdk.quest.chest.LamEsensTomeHolder } + ); + }; + + poiHandlers[sdk.areas.Travincal] = function() { + return createPOIFromPreset(sdk.objects.CompellingOrb, "Orb"); + }; + + poiHandlers[sdk.areas.DuranceofHateLvl3] = function() { + return createFixedPOI(17588, 8069, "Mephisto"); + }; + + poiHandlers[sdk.areas.PlainsofDespair] = function() { + return createPOIFromMonster(sdk.monsters.Izual, "Izual"); + }; + + poiHandlers[sdk.areas.RiverofFlame] = function() { + return createPOIFromPreset(sdk.quest.chest.HellForge, "Hephasto"); + }; + + poiHandlers[sdk.areas.ChaosSanctuary] = function() { + return createPOIFromPreset(sdk.objects.DiabloStar, "Star"); + }; + + poiHandlers[sdk.areas.Harrogath] = function() { + return createFixedPOI( + 5112, 5120, + "Anya Portal", + { do: "usePortal", id: sdk.areas.NihlathaksTemple } + ); + }; + + poiHandlers[sdk.areas.BloodyFoothills] = function() { + return createFixedPOI(3899, 5113, "Shenk"); + }; + + poiHandlers[sdk.areas.FrozenRiver] = function() { + return createPOIFromPreset(sdk.objects.FrozenAnyasPlatform, "Frozen Anya"); + }; + + poiHandlers[sdk.areas.NihlathaksTemple] = function() { + return createFixedPOI(10058, 13234, "Pindle"); + }; + + poiHandlers[sdk.areas.HallsofVaught] = function() { + return createPOIFromPreset(sdk.objects.NihlathaksPlatform, "Nihlathak"); + }; + + poiHandlers[sdk.areas.ThroneofDestruction] = function() { + return createFixedPOI(15118, 5002, "Throne Room"); + }; + + poiHandlers[sdk.areas.WorldstoneChamber] = function() { + const baal = Game.getMonster(sdk.monsters.Baal); + return createFixedPOI( + baal ? baal.x : 15134, + baal ? baal.y : 5923, + "Baal" + ); + }; + + poiHandlers[sdk.areas.MatronsDen] = function() { + return createPOIFromPreset(sdk.objects.SmallSparklyChest, "Lilith"); + }; + + poiHandlers[sdk.areas.ForgottenSands] = function() { + const duriel = Game.getMonster(sdk.monsters.UberDuriel); + if (!duriel) return false; + return createFixedPOI(duriel.x, duriel.y, "Duriel"); + }; + + poiHandlers[sdk.areas.FurnaceofPain] = function() { + return createPOIFromPreset(sdk.objects.SmallSparklyChest, "Izual"); + }; + + /** + * Add a vector line from player to the specified coordinates + * @param {number} x - destination x coordinate + * @param {number} y - destination y coordinate + * @param {number} color - line color + */ + function addVector(x, y, color) { + VectorHooks.hooks.push(new Line(me.x, me.y, x, y, color, true)); + } + + /** + * Add area name text at specified coordinates + * @param {Object} area - area information + */ + function addAreaName(area) { + VectorHooks.names.push(new Text(getAreaName(area.target), area.x, area.y, 0, 6, 2, true)); + } + + return { + enabled: true, + currArea: 0, + lastLoc: { x: 0, y: 0 }, + /** @type {Text[]} */ + names: [], + /** @type {Line[]} */ + hooks: [], + + check: function () { + if (!this.enabled) { + this.flush(); + + return; + } + + if (me.area !== this.currArea) { + this.flush(); + + if (!me.area || !me.gameReady) return; + + try { + /** @type {Area["exits"]} */ + const exits = getArea().exits; + VectorHooks.currArea = me.area; + + if (exits) { + for (let exit of exits) { + if (me.inArea(sdk.areas.CanyonofMagic)) { + addVector(exit.x, exit.y, exit.target === getRoom().correcttomb ? 0x69 : 0x99); + } else if (nextAreas.has(me.area) && exit.target === nextAreas.get(me.area)) { + addVector(exit.x, exit.y, 0x1F); + } else if (exit.target === ActionHooks.prevAreas.indexOf(me.area) && nextAreas.get(me.area)) { + addVector(exit.x, exit.y, 0x99); + } else if (exit.target === ActionHooks.prevAreas.indexOf(me.area)) { + addVector(exit.x, exit.y, 0x1F); + } else if (exit.target === ActionHooks.prevAreas[me.area]) { + addVector(exit.x, exit.y, 0x0A); + } else { + addVector(exit.x, exit.y, 0x99); + } + + addAreaName(exit); + } + } + + let wp = this.getWP(); + wp && addVector(wp.x, wp.y, 0xA8); + let poi = this.getPOI(); + poi && addVector(poi.x, poi.y, 0x7D); + } catch (e) { + console.error(e); + } + } else if (me.x !== this.lastLoc.x || me.y !== this.lastLoc.y) { + VectorHooks.update(); + } + }, + + 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.pop().remove(); + } + + while (this.names.length) { + this.names.pop().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 () { + if (superChestAreas.has(me.area)) { + let result = poiHandlers.superChest(); + if (result) return result; + } + + if (largeSparklyChestAreas.has(me.area)) { + let result = poiHandlers.largeSparklyChest(); + if (result) return result; + } + + if (hellEntranceAreas.has(me.area)) { + let result = poiHandlers.getHellEntrance(); + if (result) return result; + } + + if (me.area >= sdk.areas.TalRashasTomb1 && me.area <= sdk.areas.TalRashasTomb7) { + return poiHandlers.talRashaTomb(); + } + + if (poiHandlers[me.area]) { + try { + return poiHandlers[me.area](); + } catch (e) { + console.error("Error in POI handler for area " + me.area + ": " + e); + } + } + + return false; + } + }; +})(); diff --git a/d2bs/kolbot/libs/manualplay/libs/AttackOverrides.js b/d2bs/kolbot/libs/manualplay/libs/AttackOverrides.js deleted file mode 100644 index d003b3507..000000000 --- a/d2bs/kolbot/libs/manualplay/libs/AttackOverrides.js +++ /dev/null @@ -1,33 +0,0 @@ -/** -* @filename AttackOverrides.js -* @author kolton, theBGuy -* @desc Attack.js fixes to improve functionality for map mode -* -*/ - -includeIfNotIncluded("common/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.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { - notify && print("ÿc1Bad attack config. Don't expect your bot to attack."); - } - - this.getPrimarySlot(); - Skill.init(); - - 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/Hooks.js b/d2bs/kolbot/libs/manualplay/libs/Hooks.js new file mode 100644 index 000000000..b73ffd2c4 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/libs/Hooks.js @@ -0,0 +1,89 @@ +/** + * @typedef {import('./hooks/TextHooks')} + */ + +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; + + const invoFlagCheck = function () { + return [sdk.uiflags.Stash, sdk.uiflags.Cube, sdk.uiflags.TradePrompt].every(function (el) { + return !getUIFlag(el); + }); + }; + + 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 && invoFlagCheck()) { + ItemHooks.flush(); + TextHooks.check(); + } else { + MonsterHooks.flush(); + ShrineHooks.flush(); + TextHooks.flush(); + VectorHooks.flush(); + ActionHooks.flush(); + ItemHooks.flush(); + } + } + + Hooks.flushed = flag; + + return true; + } +}; diff --git a/d2bs/kolbot/libs/manualplay/libs/MiscOverrides.js b/d2bs/kolbot/libs/manualplay/libs/MiscOverrides.js deleted file mode 100644 index 631832360..000000000 --- a/d2bs/kolbot/libs/manualplay/libs/MiscOverrides.js +++ /dev/null @@ -1,163 +0,0 @@ -/** -* @filename MiscOverrides.js -* @author theBGuy -* @desc Misc.js additions to improve functionality for map mode -* -*/ - -includeIfNotIncluded("common/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(); - } -}; - -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); -}; - -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(); - } -}; diff --git a/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js b/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js index 179b72ff7..d879f713e 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 { + console.log("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..e922b8bd6 100644 --- a/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.js @@ -5,31 +5,35 @@ * */ -includeIfNotIncluded("common/Pickit.js"); +includeIfNotIncluded("core/Pickit.js"); -Pickit.basicPickItems = function () { - let itemList = []; - let item = Game.getItem(); +/** + * @param {number} range + * @returns {boolean} + */ +Pickit.basicPickItems = function (range) { + 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 && item.distance <= range) { + 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..59839e8cf --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/main.js @@ -0,0 +1,319 @@ +/** +* @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(); + +/** + * @typedef {import('./hooks/TextHooks')} + */ + +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")); + const HelpMenu = require("./modules/HelpMenu"); + + 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)) { + return; + } + 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(" "); + const qolObj = { type: "qol", dest: false, action: false, params: [] }; + + 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; + + if (msgList.length > 1) { + qolObj.params.push(msgList.at(1)); + } + + 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]; + + if (msgList.length > 2) { + qolObj.params.push(msgList.at(2)); + } + + 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; + } + case "docubing": + case "makerunewords": + qolObj.action = cmd; + + 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); + + try { + while (true) { + while (!me.area || !me.gameReady) { + nativeDelay(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(); + } + } + } catch (e) { + Misc.errorReport(e, "main.js", "main()"); + } +} diff --git a/d2bs/kolbot/libs/manualplay/modules/HelpMenu.js b/d2bs/kolbot/libs/manualplay/modules/HelpMenu.js new file mode 100644 index 000000000..05c185d32 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/modules/HelpMenu.js @@ -0,0 +1,217 @@ +/** + * @filename HelpMenu.js + * @author theBGuy + * @desc UMD module Help Menu for MapThread + * + */ + +(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.HelpMenu = factory(); + } +}([].filter.constructor("return this")(), function() { + const Worker = require("../../modules/Worker"); + const POSITION_MODIFIER = ( + Number(!!me.diff) + + Number(!!me.gamepassword) + + Number(!!me.gametype) + + Number(!!me.gamename) + + Number(!!me.gameserverip && !me.realm) + ); + + const HelpMenu = { + /** @type {Text[]} */ + hooks: [], + /** @type {(Box | Frame)[]} */ + box: [], + cleared: true, + helpBoxX: 715 + (me.screensize ? 0 : -160), + helpBoxY: 78 + 16 * POSITION_MODIFIER, + helpBoxTextX: 647 + (me.screensize ? 0 : -160), + helpBoxTextY: 78 + 16 * POSITION_MODIFIER + 15, + action: [], + actionY: -1, + tick: 0, + chatCommands: { + "me": "Displays Character level, Area, and x/y coordinates", + "pick [n]": "Pick items from the ground to inventory within n range (default: 40)", + "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 [n]": "Buy and stack n antidote potions (default: 10) for 5 minutes of boosted poison resistance", + "thawing [n]": "Buy and stack n thawing potions (default: 10) for 5 minutes of boosted cold resistance", + "stamina [n]": "Buy and stack n stamina potions (default: 10) for 5 minutes of boosted stamina", + }, + // "Num": { + // "9:": "Stops current pathing action", + // }, + }, + + /** + * @param {number} click + * @param {number} x + * @param {number} y + * @returns {boolean} + */ + 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; + }, + + /** + * @param {string} text + */ + addHook: function (text) { + this.hooks.push(new Text("ÿc4." + text, this.helpBoxTextX, this.helpBoxTextY + 13 * this.hooks.length, 0, 0, 0, false, this.hookHandler)); + }, + + showMenu: function () { + this.cleared = false; + + this.hooks.push(new Text("ÿc2Chat Commands:", this.helpBoxTextX, this.helpBoxTextY, 0, 0, 0)); + this.hooks[this.hooks.length - 1].zorder = 2; + + const addCommands = (commands, prefix = "") => { + const cmdKeys = Object.keys(commands); + + for (let i = 0; i < cmdKeys.length; i++) { + const key = cmdKeys[i]; + + if (typeof commands[key] === "object") { + this.addHook(prefix + key); + + const subPrefix = prefix + key + " "; + Object.keys(commands[key]).forEach(subKey => { + this.addHook(" " + subPrefix + subKey); + }); + } else { + this.addHook(prefix + key); + } + } + }; + + addCommands(this.chatCommands); + + 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, 18, 0x0, 4, 2)); + this.box[this.box.length - 1].zorder = 1; + 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.hooks.push(new Text("ÿc1X", this.helpBoxTextX + 125, this.helpBoxTextY, 0, 0, 0, false, this.hookHandler)); + this.hooks[this.hooks.length - 1].zorder = 1; + }, + + hideMenu: function () { + this.cleared = true; + + while (this.hooks.length > 0) { + this.hooks.shift().remove(); + } + + while (this.box.length > 0) { + this.box.shift().remove(); + } + + return; + }, + + sortHooks: function (h1, h2) { + return Math.abs(h1.y - HelpMenu.actionY) - Math.abs(h2.y - HelpMenu.actionY); + } + }; + + Worker.runInBackground.helpAction = function () { + while (HelpMenu.action.length > 0) { + HelpMenu.tick = getTickCount(); + HelpMenu.actionY = HelpMenu.action.shift()[1]; + const actionHooks = HelpMenu.hooks.filter(function (hook) { + return typeof hook.click === "function"; + }).sort(HelpMenu.sortHooks); + + if (actionHooks[0].text === "ÿc1X") { + HelpMenu.hideMenu(); + + return true; + } + + const msgList = actionHooks[0].text.split(" ").filterNull(); + let cmd = msgList[0].split(".")[1]; + + if (!actionHooks[0].text.includes(".")) { + cmd = msgList[1]; + } + + try { + let str = ""; + + if (msgList[0] === "ÿc4." && msgList.length >= 2) { + const parentCmd = msgList[1]; + const subCmd = msgList[2]; + const subWithParam = msgList.slice(2).join(" "); + + if (HelpMenu.chatCommands[parentCmd]) { + if (HelpMenu.chatCommands[parentCmd][subCmd]) { + str = HelpMenu.chatCommands[parentCmd][subCmd]; + } else if (HelpMenu.chatCommands[parentCmd][subWithParam]) { + str = HelpMenu.chatCommands[parentCmd][subWithParam]; + } + } + } else if (typeof HelpMenu.chatCommands[cmd] === "object") { + const subCommands = Object.keys(HelpMenu.chatCommands[cmd]); + str = "Available options: " + subCommands.join(", "); + } else { + str = HelpMenu.chatCommands[cmd]; + } + + !!str && me.overhead(str); + } catch (e) { + console.error(e); + me.overhead(cmd); + } + + delay(150); + } + + return true; + }; + + return HelpMenu; +})); diff --git a/d2bs/kolbot/libs/manualplay/modules/HookFactory.js b/d2bs/kolbot/libs/manualplay/modules/HookFactory.js new file mode 100644 index 000000000..3e176bbca --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/modules/HookFactory.js @@ -0,0 +1,132 @@ +/** + * @filename HookFactory.js + * @author theBGuy + * @desc UMD module HookFactory for MapThread + * + */ + +(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.HookFactory = factory(); + } +}([].filter.constructor("return this")(), function() { + /** + * @typedef {Object} HookEntry + * @property {string} name - The identifier for this hook entry + * @property {Hook} hook - The actual hook object (Text, Box, Frame, etc.) + * @property {number} [dest] - Optional destination (for act change hooks) + * @property {string} [type] - Optional type information (e.g., "actChange") + */ + + const HookFactory = { + createHooks: { + // text: string, x: number, y: number, color: number, font: number, align: number, automap: boolean, ClickHandler?: Function, HoverHandler?: Function + /** + * Creates a text hook + * @param {Partial} options + * @returns {HookEntry} The created hook entry + */ + text: function(options = {}) { + const { name, text, x, y, color, font, align, automap, handler } = Object.assign({ + name: "", + text: "", + x: 0, + y: 0, + color: 4, + font: 0, + align: 0, + automap: false, + handler: null + }, options); + return { + name: name, + hook: new Text(text, x, y, color, font, align, automap, handler), + }; + }, + + // x: number, y: number, xsize: number, ysize: number, color: number, opacity: number, align: number, automap: boolean, ClickHandler?: Function, HoverHandler?: Function + /** + * Creates a box hook + * @param {Partial} options + * @returns {HookEntry} The created hook entry + */ + box: function(options = {}) { + const { name, x, y, width, height, color, opacity, click } = Object.assign({ + name: "", + x: 0, + y: 0, + width: 0, + height: 0, + color: 0x0, + opacity: 1, + click: null + }, options); + return { + name: name, + hook: new Box(x, y, width, height, color, opacity, click) + }; + }, + + // rame(x: number, y: number, xsize: number, ysize: number, color: number, opacity: number, align: number, automap: boolean, ClickHandler?: Function, HoverHandler?: Function) + /** + * Creates a frame hook + * @param {Partial} options + */ + frame: function(options = {}) { + const { name, x, y, width, height, style } = Object.assign({ + name: "", + x: 0, + y: 0, + width: 0, + height: 0, + style: 0 + }, options); + return { + name: name, + hook: new Frame(x, y, width, height, style) + }; + } + }, + + /** + * Creates a container with a box and frame + * @param {string} boxName - Name for the box element + * @param {string} frameName - Name for the frame element + * @param {number} x - X coordinate position + * @param {number} y - Y coordinate position + * @param {number} width - Width of the container + * @param {number} height - Height of the container + * @param {number} opcacity - Opacity of the box + * @returns {Array} Array containing the box and frame hooks + */ + createContainer: function(boxName, frameName, x, y, width, height, opcacity) { + const container = []; + + container.push(this.createHooks.box({ + name: boxName, + x: x, + y: y, + width: width, + height: height, + opacity: opcacity, + })); + container[container.length - 1].hook.zorder = 0; + container.push(this.createHooks.frame({ + name: frameName, + x: x, + y: y, + width: width, + height: height + })); + + return container; + } + }; + + return HookFactory; +})); diff --git a/d2bs/kolbot/libs/manualplay/threads/MapHelper.js b/d2bs/kolbot/libs/manualplay/threads/MapHelper.js index 906f75c1f..6dcc9225c 100644 --- a/d2bs/kolbot/libs/manualplay/threads/MapHelper.js +++ b/d2bs/kolbot/libs/manualplay/threads/MapHelper.js @@ -2,419 +2,649 @@ * @filename MapHelper.js * @author theBGuy * @credits kolton -* @desc MapHelper used in conjuction with MapThread.js +* @desc MapHelper used in conjuction with main.js * +* @typedef {import("../../../sdk/globals")} */ -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); +/** + * @typedef {Object} UnitAction + * @property {string} do - The action to perform ('openChest', 'usePortal') + * @property {number} [id] - The ID associated with the action + */ + +/** + * @typedef {'area' | 'unit' | 'wp' | 'actChange' | 'portal' | 'qol' | 'drop' |'stack'} EventType + */ + +/** + * @typedef {Object} ActionEvent + * @property {EventType} type - The type of action to perform + * @property {number|string|null} [dest] - The destination area ID, unit ID, or act number + * @property {UnitAction|string} [action] - Action details or action string + * @property {string[]} params + */ + +function main () { + // getUnit test + getUnit(-1) === null && console.warn("getUnit bug detected"); + + console.log("ÿc9MapHelper loaded"); + + /** @type {ActionEvent} */ + const obj = { type: null, dest: null, action: null, params: [] }; + 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], + 15: [12638, 9063], + 20: [12708, 9063], + 25: [12948, 9128], + }; + + Config.init(); + Attack.init(false); + Pickit.init(); + Storage.Init(); + Runewords.init(); + Cubing.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; + }; + + /** + * @param {number} portalID + */ + const 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(); + } + }; + + const 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); + }; + + /** + * @param {number} fromLoc + */ + const 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.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(); + } + }; + + 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) && 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: + if (me.inArea(sdk.areas.CanyonofMagic)) { + Pather.moveTo(12692, 5195); + redPortal = Pather.getPortal(obj.dest); + !redPortal && Pather.useWaypoint(obj.dest); + } else if (me.inArea(sdk.areas.PalaceCellarLvl3)) { + Pather.moveTo(10073, 8670); + Pather.usePortal(null); + } + + 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": + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { + showConsole(); + console.warn("No valid attack skill(s) set for clear command. Check your Config."); + } else { + 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": + openRedPortal(sdk.areas.MooMooFarm); + + break; + case "ubertrist": + openRedPortal(sdk.areas.UberTristram); + + break; + case "uberportal": + 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": + { + let range = obj.params.length > 0 && !isNaN(Number(obj.params[0])) + ? Number(obj.params[0]) + : Config.PickRange; + Config.ManualPlayPick ? Pickit.pickItems(range) : Pickit.basicPickItems(range); + } + + 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": + dropItems(sdk.storage.Inventory); + + break; + case "stash": + dropItems(sdk.storage.Stash); + + break; + } + + break; + case "stack": { + let quantity = obj.params.length > 0 && !isNaN(Number(obj.params[0])) + ? Number(obj.params[0]) + : 10; + switch (obj.action) { + case "thawing": + Town.buyPots(quantity, "Thawing", true, true); + + break; + case "antidote": + Town.buyPots(quantity, "Antidote", true, true); + + break; + case "stamina": + Town.buyPots(quantity, "Stamina", true, true); + + break; + } + break; + } + case "makerunewords": + Runewords.makeRunewords(); + + break; + case "docubing": + Cubing.doCubing(); + + 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..d7628dec2 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,291 @@ * */ 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(); -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("libs/manualplay/main.js"); + + 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..98203ae3e 100644 --- a/d2bs/kolbot/libs/manualplay/threads/PickThread.js +++ b/d2bs/kolbot/libs/manualplay/threads/PickThread.js @@ -5,51 +5,51 @@ * */ js_strict(true); +include("critical.js"); -include("json2.js"); -include("NTItemParser.dbl"); -include("OOG.js"); -include("CraftingSystem.js"); -include("common/util.js"); +// globals needed for core gameplay +includeCoreLibs(); -includeCommonLibs(); +// system libs +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/AsyncEvents.js b/d2bs/kolbot/libs/modules/AsyncEvents.js new file mode 100644 index 000000000..5d824f1ff --- /dev/null +++ b/d2bs/kolbot/libs/modules/AsyncEvents.js @@ -0,0 +1,51 @@ +/** + * @author Jaenster + * @description A node like event system + * + */ + +(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 + }; +})(module, require); diff --git a/d2bs/kolbot/libs/modules/Control.js b/d2bs/kolbot/libs/modules/Control.js index 68db3598c..c096bf183 100644 --- a/d2bs/kolbot/libs/modules/Control.js +++ b/d2bs/kolbot/libs/modules/Control.js @@ -3,193 +3,319 @@ * @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); + Control.CharCreateStatusText = new Control(sdk.controls.LabelBox, 268, 320, 264, 120); + } + + // 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..9c44453e3 100644 --- a/d2bs/kolbot/libs/modules/Events.js +++ b/d2bs/kolbot/libs/modules/Events.js @@ -1,51 +1,109 @@ /** * @author Jaenster * @description A node like event system - * */ -(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 - }; -})(module, require); +(function (module) { + /** + * @class Events + * @constructor + */ + function Events() { + const handlers = new WeakMap(); + const onceHandlers = new WeakMap(); + + /** + * Get the event map for an object and map type. + * @param {Object} obj - The object to get the map for. + * @param {WeakMap>} mapType - The WeakMap storing event maps. + * @returns {Map} The event map for the object. + */ + function getMap(obj, mapType) { + if (!mapType.has(obj)) { + mapType.set(obj, new Map()); + } + return mapType.get(obj); + } + + /** + * Register an event handler for the given key. + * @param {string} key - The event name. + * @param {Function} handler - The callback function. + * @param {WeakMap} [handlerType] - Optional handler map (internal use). + * @returns {Events} The instance for chaining. + */ + function on(key, handler, handlerType) { + handlerType = handlerType || handlers; + let map = getMap(this, handlerType); + if (!map.has(key)) { + map.set(key, []); + } + map.get(key).push(handler); + return this; + } + + /** + * Register a one-time event handler for the given key. + * @param {string} key - The event name. + * @param {Function} handler - The callback function. + * @returns {Events} The instance for chaining. + */ + function once(key, handler) { + return on.call(this, key, handler, onceHandlers); + } + + /** + * Remove an event handler for the given key. + * @param {string} key - The event name. + * @param {Function} handler - The callback function to remove. + * @returns {Events} The instance for chaining. + */ + function off(key, handler) { + [handlers, onceHandlers].forEach(function (handlerType) { + let map = getMap(this, handlerType); + if (map.has(key)) { + let arr = map.get(key); + let idx = arr.indexOf(handler); + if (idx > -1) { + arr.splice(idx, 1); + } + } + }, this); + return this; + } + + /** + * Emit an event, calling all handlers for the given key. + * @param {string} key - The event name. + * @param {...*} args - Arguments to pass to the handlers. + * @returns {Events} The instance for chaining. + */ + function emit(key, ...args) { + let callbacks = []; + + let onceMap = getMap(this, onceHandlers); + let restMap = getMap(this, handlers); + + if (onceMap.has(key)) { + callbacks = callbacks.concat(onceMap.get(key).splice(0)); + } + if (restMap.has(key)) { + callbacks = callbacks.concat(restMap.get(key)); + } + + callbacks.forEach(function (cb) { + cb.apply(this, args); + }, this); + + return this; + } + + // Attach methods to the instance + this.on = on; + this.once = once; + this.off = off; + this.emit = emit; + } + + module.exports = Events; +})(module); diff --git a/d2bs/kolbot/libs/modules/Graph.js b/d2bs/kolbot/libs/modules/Graph.js new file mode 100644 index 000000000..b34da8278 --- /dev/null +++ b/d2bs/kolbot/libs/modules/Graph.js @@ -0,0 +1,694 @@ +/** + * @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); + } + }; + + /** + * @typedef {Object} GraphAnalysis + * @property {number} vertexCount - Total number of vertices + * @property {number} avgDegree - Average neighbors per vertex + * @property {number} deadEndRatio - Ratio of dead-ends (degree <= 1) to total vertices + * @property {number} maxDegree - Maximum neighbors any vertex has + * @property {number} componentCount - Number of disconnected components + * @property {"linear" | "maze" | "open" | "hub"} mapType - Inferred map type + * @property {number} sizeWeight - Recommended size weight for scoring + * @property {number} distWeight - Recommended distance weight for scoring + */ + + /** + * Analyze graph structure to determine map characteristics and optimal tuning + * @param {Graph} graph + * @returns {GraphAnalysis} + */ + Graph.analyzeGraph = function(graph) { + let vertices = graph.vertices; + let vertexCount = vertices.length; + + if (vertexCount === 0) { + return { + vertexCount: 0, + avgDegree: 0, + deadEndRatio: 0, + maxDegree: 0, + componentCount: 0, + mapType: "open", + sizeWeight: 0.33, + distWeight: 0.7 + }; + } + + // Calculate degree statistics + let degrees = vertices.map(function(v) { + return graph.nearbyVertices(v).length; + }); + let totalDegree = degrees.reduce(function(sum, d) { return sum + d; }, 0); + let avgDegree = totalDegree / vertexCount; + let maxDegree = Math.max.apply(null, degrees); + let deadEnds = degrees.filter(function(d) { return d <= 1; }).length; + let deadEndRatio = deadEnds / vertexCount; + + // Count connected components + let visited = new Set(); + let componentCount = 0; + for (let vertex of vertices) { + if (visited.has(vertex.id)) continue; + componentCount++; + let queue = [vertex]; + visited.add(vertex.id); + while (queue.length) { + let current = queue.shift(); + let neighbors = graph.nearbyVertices(current); + for (let neighbor of neighbors) { + if (!visited.has(neighbor.id)) { + visited.add(neighbor.id); + queue.push(neighbor); + } + } + } + } + + // Determine map type based on metrics + let mapType = "open"; + let sizeWeight = 0.33; + let distWeight = 0.7; + + if (avgDegree <= 1.5 && deadEndRatio < 0.15) { + // Low connectivity, few dead-ends = linear corridor + mapType = "linear"; + sizeWeight = 0.2; // Less important to clear small branches + distWeight = 0.8; // Just follow the path + } else if (deadEndRatio > 0.3) { + // Many dead-ends = maze-like + mapType = "maze"; + sizeWeight = 0.5; // Very important to clear branches + distWeight = 0.5; // Balance with distance + } else if (avgDegree > 3 && maxDegree > 5) { + // High connectivity = open area + mapType = "open"; + sizeWeight = 0.25; + distWeight = 0.75; // Prefer nearest neighbor + } else if (componentCount > 1 || (maxDegree > 4 && avgDegree < 2.5)) { + // Multiple components or hub pattern + mapType = "hub"; + sizeWeight = 0.4; // Important to clear smaller sections + distWeight = 0.6; + } + + return { + vertexCount: vertexCount, + avgDegree: avgDegree, + deadEndRatio: deadEndRatio, + maxDegree: maxDegree, + componentCount: componentCount, + mapType: mapType, + sizeWeight: sizeWeight, + distWeight: distWeight + }; + }; + + /** + * Adaptive search that analyzes the graph and uses optimal algorithm/tuning + * @param {Graph} graph + * @param {(vertex: Vertex) => any} explore + * @param {"walk" | "teleport"} mode + */ + Graph.adaptiveSearch = function(graph, explore, mode = "walk") { + let analysis = Graph.analyzeGraph(graph); + + console.log("Graph Analysis: " + JSON.stringify({ + vertices: analysis.vertexCount, + avgDegree: analysis.avgDegree.toFixed(2), + deadEndRatio: (analysis.deadEndRatio * 100).toFixed(1) + "%", + components: analysis.componentCount, + mapType: analysis.mapType + })); + + // For linear maps, simple nearest neighbor is often sufficient and faster + if (analysis.mapType === "linear" && analysis.componentCount === 1) { + console.log("Using nearestNeighbourSearch for linear map"); + return Graph.nearestNeighbourSearch(graph, explore, mode); + } + + // For all other types, use cluster-aware with tuned weights + console.log("Using clusterAwareSearch with weights: size=" + + analysis.sizeWeight + ", dist=" + analysis.distWeight); + return Graph.clusterAwareSearch(graph, explore, mode, analysis.sizeWeight, analysis.distWeight); + }; + + /** + * Cluster-aware nearest neighbor search that identifies connected components + * and prioritizes completing smaller/closer clusters before moving on. + * This prevents leaving isolated sections unexplored until the end. + * @param {Graph} graph + * @param {(vertex: Vertex) => any} explore + * @param {"walk" | "teleport"} mode + * @param {number} [sizeWeight=0.33] - Weight for component size in scoring + * @param {number} [distWeight=0.7] - Weight for distance in scoring + */ + Graph.clusterAwareSearch = function(graph, explore, mode = "walk", sizeWeight = 0.33, distWeight = 0.7) { + /** + * Find all connected components in the remaining unvisited graph + * @returns {Vertex[][]} + */ + function findComponents() { + let unvisited = graph.vertices.filter(filterSeen); + let components = []; + let visited = new Set(); + + for (let vertex of unvisited) { + if (visited.has(vertex.id)) continue; + + // BFS to find all vertices in this component + let component = []; + let queue = [vertex]; + visited.add(vertex.id); + + while (queue.length) { + let current = queue.shift(); + component.push(current); + + let neighbors = graph.nearbyVertices(current).filter(function(v) { + return !v.seen && !visited.has(v.id); + }); + + for (let neighbor of neighbors) { + visited.add(neighbor.id); + queue.push(neighbor); + } + } + + if (component.length > 0) { + components.push(component); + } + } + + return components; + } + + /** + * Find the entry point (closest vertex) into a component from current position + * @param {Vertex[]} component + * @returns {Vertex | null} + */ + function findEntryPoint(component) { + return component.reduce(function(best, v) { + v.clearCache(); + let dist = v.pathDistance(mode); + if (dist < best.dist) { + return { vertex: v, dist: dist }; + } + return best; + }, { vertex: null, dist: Infinity }).vertex; + } + + /** + * Score a component for priority (lower = should visit sooner) + * Factors: size (smaller first), distance to entry point + * @param {Vertex[]} component + * @returns {number} + */ + function scoreComponent(component) { + let entry = findEntryPoint(component); + if (!entry) return Infinity; + + let distanceToEntry = entry.pathDistance(mode); + let size = component.length; + + // Normalize size (1 vertex = 0, larger = higher score) + let sizeScore = (size - 1) * 50; + + return (sizeScore * sizeWeight) + (distanceToEntry * distWeight); + } + + /** + * Calculate the "branch size" reachable from a vertex without backtracking through origin + * Smaller branches should be cleared first to avoid backtracking + * @param {Vertex} vertex - The vertex to measure from + * @param {Vertex} origin - The vertex we came from (don't count paths back through here) + * @param {Vertex[]} component - The component we're exploring + * @returns {number} - Number of unvisited vertices reachable + */ + function getBranchSize(vertex, origin, component) { + let visited = new Set([origin.id, vertex.id]); + let queue = [vertex]; + let count = 1; + + while (queue.length) { + let current = queue.shift(); + let neighbors = graph.nearbyVertices(current).filter(function(v) { + return !v.seen && component.includes(v) && !visited.has(v.id); + }); + + for (let neighbor of neighbors) { + visited.add(neighbor.id); + queue.push(neighbor); + count++; + } + } + + return count; + } + + /** + * Explore within a component using nearest-neighbor with dead-end awareness + * @param {Vertex[]} component + */ + function exploreComponent(component) { + let currentVertex = findEntryPoint(component); + + while (currentVertex && !currentVertex.seen) { + CollMap.drawRoom(currentVertex, "green", true); + explore(currentVertex); + currentVertex.markAsSeen(); + CollMap.drawRoom(currentVertex, "purple", true); + + // Handle movement during explore + if (!currentVertex.coordsInRoom(me.x, me.y)) { + let _newVertex = graph.vertexForRoom(getRoom(me.x, me.y)); + if (_newVertex && component.includes(_newVertex)) { + currentVertex = _newVertex; + } + } + + // Get unvisited neighbors within this component + let neighbors = graph.nearbyVertices(currentVertex) + .filter(function(v) { + return !v.seen && component.includes(v); + }); + + if (neighbors.length === 0) { + // No neighbors in component, find nearest unvisited in component + currentVertex = component + .filter(filterSeen) + .reduce(function(best, v) { + v.clearCache(); + let dist = v.pathDistance(mode); + if (dist < best.dist) { + return { vertex: v, dist: dist }; + } + return best; + }, { vertex: null, dist: Infinity }).vertex; + } else { + // Sort neighbors by branch size (smaller branches first to avoid backtracking) + neighbors.sort(function(a, b) { + let aBranchSize = getBranchSize(a, currentVertex, component); + let bBranchSize = getBranchSize(b, currentVertex, component); + + // Prioritize smaller branches (clear them first) + if (aBranchSize !== bBranchSize) { + return aBranchSize - bBranchSize; + } + + // Tie-breaker: use path distance + let distA = currentVertex.pathDistanceTo(a, mode); + let distB = currentVertex.pathDistanceTo(b, mode); + return distA - distB; + }); + + currentVertex = neighbors[0]; + } + + // Clear caches for next iteration + for (let v of graph.vertices) { + v.clearCache(); + } + } + } + + // Main loop + while (graph.vertices.some(function(v) { return !v.seen; })) { + let components = findComponents(); + + if (components.length === 0) break; + + // Sort components by score (lower = higher priority) + components.sort(function(a, b) { + return scoreComponent(a) - scoreComponent(b); + }); + + let targetComponent = components[0]; + + for (let v of targetComponent) { + CollMap.drawRoom(v, "white", true); + } + + exploreComponent(targetComponent); + } + }; + + 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/LazyLoader.js b/d2bs/kolbot/libs/modules/LazyLoader.js new file mode 100644 index 000000000..f3f6dd2ac --- /dev/null +++ b/d2bs/kolbot/libs/modules/LazyLoader.js @@ -0,0 +1,192 @@ +/** + * @filename LazyLoader.js + * @author theBGuy + * @desc Provides a factory function to create a proxy object that lazily loads modules on property access. + * Useful for grouping related modules and optimizing resource usage by loading each module only when needed. + * + */ + +(function (module, require) { + function getCallerRelativeDir() { + const stack = new Error().stack.match(/[^\r\n]+/g); + let loaderDir, callerDir; + + for (let i = 1; i < stack.length; i++) { + // Find LazyLoader.js location + if (/lazyloader\.js/i.test(stack[i])) { + let match = stack[i].match(/@([a-zA-Z]:.+?)\.js:/); + if (match) { + loaderDir = match[1].replace(/\\/g, "/"); + } + } else { + // Find first non-LazyLoader caller + const match = stack[i].match(/@([a-zA-Z]:.+?)\.js:/); + if (match) { + callerDir = match[1].replace(/\\/g, "/"); + break; + } + } + } + if (!loaderDir || !callerDir) return ""; + // Remove filenames, keep directories + loaderDir = loaderDir.substring(0, loaderDir.lastIndexOf("/")); + callerDir = callerDir.substring(0, callerDir.lastIndexOf("/")); + // Compute relative path from loaderDir to callerDir + const loaderParts = loaderDir.split("/"); + const callerParts = callerDir.split("/"); + // Find common root + let i = 0; + while (i < loaderParts.length && i < callerParts.length && loaderParts[i] === callerParts[i]) { + i++; + } + // Go up from loaderDir + let relPath = ""; + for (let j = i; j < loaderParts.length; j++) relPath += "../"; + // Go down to callerDir + relPath += callerParts.slice(i).join("/"); + return relPath; + } + + /** + * Create a lazy-loading proxy for modules. + * @param {Map} modulePathMap - Map of module names to paths + */ + function createLazyModuleProxy(modulePathMap) { + const moduleCache = new Map(); + const callerRelDir = getCallerRelativeDir(); + + function loadModule(moduleName, customHandler) { + const nameStr = String(moduleName); + + if (customHandler) { + moduleCache.set(nameStr, customHandler); + return customHandler; + } + if (moduleCache.has(nameStr)) { + return moduleCache.get(nameStr); + } + let modulePath = modulePathMap.get(nameStr); + if (!modulePath) { + throw new Error("Unknown module: " + nameStr); + } + try { + if (modulePath.startsWith("./")) { + modulePath = callerRelDir + "/" + modulePath.slice(2); + } + console.debug("Loading module: " + nameStr + " from " + modulePath); + let module = require(modulePath); + moduleCache.set(nameStr, module); + return module; + } catch (error) { + throw new Error("Failed to load module " + nameStr + ": " + error.message); + } + } + + return new Proxy({}, { + /** + * @param {Object} target + * @param {string} property + * @returns {any} + */ + get: function (target, property) { + if (property === "load") { + /** @param {string} module */ + return function (module, customHandler) { + return loadModule(module, customHandler); + }; + } + if (modulePathMap.has(property)) { + return loadModule(property); + } + // Backwards compatibility for ClassAttack: proxy to current class instance + if ( + typeof property === "string" && + !(property in target) && + modulePathMap.has(me.classid) + ) { + const instance = loadModule(me.classid); + if (instance && property in instance) { + const value = instance[property]; + return typeof value === "function" ? value.bind(instance) : value; + } + } + if (!target.hasOwnProperty(property)) { + console.warn("Attempted to access unknown property: " + String(property)); + } + return target[property]; + }, + + /** + * @param {Object} target + * @param {string} property + * @param {any} value + * @returns {boolean} + */ + set: function (target, property, value) { + target[property] = value; + return true; + }, + + /** + * @param {Object} target + * @param {string} property + * @returns {boolean} + */ + has: function (target, property) { + return typeof property === "string" && ( + modulePathMap.has(property) || target.hasOwnProperty(property) + ); + }, + + /** + * @param {Object} target + * @returns {string[]} + */ + ownKeys: function (target) { + return Array.from(modulePathMap.keys()).concat(Object.keys(target)); + }, + + /** + * @param {Object} target + * @param {string} property + * @returns {PropertyDescriptor | undefined} + */ + getOwnPropertyDescriptor: function (target, property) { + if (modulePathMap.has(property)) { + return { + enumerable: true, + configurable: true, + get: function () { + return loadModule(property); + } + }; + } + return Object.getOwnPropertyDescriptor(target, property); + }, + + /** + * @param {Object} target + * @param {string} property + * @returns {boolean} + */ + deleteProperty: function (target, property) { + if (modulePathMap.has(property)) { + if (moduleCache.has(property)) { + moduleCache.delete(property); + } + if (target.hasOwnProperty(property)) { + delete target[property]; + } + return true; + } + if (target.hasOwnProperty(property)) { + delete target[property]; + return true; + } + return false; + } + }); + } + + module.exports = createLazyModuleProxy; +})(module, require); diff --git a/d2bs/kolbot/libs/modules/LocalChat.js b/d2bs/kolbot/libs/modules/LocalChat.js new file mode 100644 index 000000000..1ccf9df25 --- /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; + console.log("ÿ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..80bd73e7a 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("./AsyncEvents")); + 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..c24217b9f 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) { + console.log((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..c42457015 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("./AsyncEvents"); + + /** @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..84b8907d5 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("./AsyncEvents")); + 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") { + console.log("ÿ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) { + console.log("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..4feb058b1 100644 --- a/d2bs/kolbot/libs/modules/Worker.js +++ b/d2bs/kolbot/libs/modules/Worker.js @@ -3,104 +3,142 @@ * @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 + + console.log("[ÿ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); + return true; + }, + deleteProperty: function (target, name) { + if (target.processes.hasOwnProperty(name)) { + 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 + global.nativeSleep = delay; + + /** + * 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/external/base64.js b/d2bs/kolbot/libs/modules/external/base64.js new file mode 100644 index 000000000..703bb5cc3 --- /dev/null +++ b/d2bs/kolbot/libs/modules/external/base64.js @@ -0,0 +1,62 @@ +/** + * https://github.com/MaxArt2501/base64-js/blob/master/base64.js + * With modifications for Kolbot environment + */ + +(function (module) { + // base64 character set, plus padding character (=) + const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + // Regular expression to check formal correctness of base64 encoded strings + const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; + + /** + * @param {string} string + */ + const btoa = function(string) { + string = String(string); + let bitmap, a, b, c, + result = "", i = 0, + rest = string.length % 3; // To determine the final padding + + for (; i < string.length;) { + if ((a = string.charCodeAt(i++)) > 255 + || (b = string.charCodeAt(i++)) > 255 + || (c = string.charCodeAt(i++)) > 255) {throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");} + + bitmap = (a << 16) | (b << 8) | c; + result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) + + b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63); + } + + // If there's need of padding, replace the last 'A's with equal signs + return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result; + }; + + /** + * @param {string} string + */ + const atob = function(string) { + // atob can work with strings with whitespaces, even inside the encoded part, + // but only \t, \n, \f, \r and ' ', which can be stripped. + string = String(string).replace(/[\t\n\f\r ]+/g, ""); + if (!b64re.test(string)) {throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");} + + // Adding the padding if missing, for semplicity + string += "==".slice(2 - (string.length & 3)); + let bitmap, result = "", r1, r2, i = 0; + for (; i < string.length;) { + bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 + | (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++))); + + result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) + : r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) + : String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255); + } + return result; + }; + + module.exports = { + btoa: btoa, + atob: atob + }; +})(module); diff --git a/d2bs/kolbot/libs/modules/sdk.js b/d2bs/kolbot/libs/modules/sdk.js index dbda6f715..5e45d4b6e 100644 --- a/d2bs/kolbot/libs/modules/sdk.js +++ b/d2bs/kolbot/libs/modules/sdk.js @@ -3,4880 +3,5557 @@ * @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, + Squelch: 4, + 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, + /** @param {number} classid */ + 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, + }, + Raven: 419, + PoisonCreeper: 425, + CarrionVine: 426, + SolarCreeper: 427, + 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, + TalictheDefender: 540, + MadawctheGuardian: 541, + KorlictheProtector: 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, + Cap: 1930, + SkullCap: 1931, + Helm: 1932, + FullHelm: 1933, + GreatHelm: 1934, + Crown: 1935, + Mask: 1936, + QuiltedArmor: 1937, + LeatherArmor: 1938, + HardLeatherArmor: 1939, + StuddedLeather: 1940, + RingMail: 1941, + ScaleMail: 1942, + ChainMail: 1943, + BreastPlate: 1944, + SplintMail: 1945, + PlateMail: 1946, + FieldPlate: 1947, + GothicPlate: 1948, + FullPlateMail: 1949, + AncientArmor: 1950, + LightPlate: 1951, + Buckler: 1952, + SmallShield: 1953, + LargeShield: 1954, + KiteShield: 1955, + TowerShield: 1956, + GothicShield: 1957, + LeatherGloves: 1958, + HeavyGloves: 1959, + ChainGloves: 1960, + LightGauntlets: 1961, + Gauntlets: 1962, + Boots: 1963, + HeavyBoots: 1964, + ChainBoots: 1965, + LightPlatedBoots: 1966, + Greaves: 1967, + Sash: 1968, + LightBelt: 1969, + Belt: 1970, + HeavyBelt: 1971, + PlatedBelt: 1972, + BoneHelm: 1973, + BoneShield: 1974, + SpikedShield: 1975, + HandAxe: 1976, + Axe: 1977, + DoubleAxe: 1978, + MilitaryPick: 1979, + WarAxe: 1980, + LargeAxe: 1981, + BroadAxe: 1982, + BattleAxe: 1983, + GreatAxe: 1984, + GiantAxe: 1985, + Wand: 1986, + YewWand: 1987, + BoneWand: 1988, + GrimWand: 1989, + Club: 1990, + Scepter: 1991, + GrandScepter: 1992, + WarScepter: 1993, + SpikedClub: 1994, + Mace: 1995, + MorningStar: 1996, + Flail: 1997, + WarHammer: 1998, + Maul: 1999, + GreatMaul: 2000, + ShortSword: 2001, + Scimitar: 2002, + Sabre: 2003, + Falchion: 2004, + CrystalSword: 2005, + BroadSword: 2006, + LongSword: 2007, + WarSword: 2008, + TwoHandedSword: 2009, + Claymore: 2010, + GiantSword: 2011, + BastardSword: 2012, + Flamberge: 2013, + GreatSword: 2014, + Dagger: 2015, + Dirk: 2016, + Kris: 2017, + Blade: 2018, + ThrowingKnife: 2019, + ThrowingAxe: 2020, + BalancedKnife: 2021, + BalancedAxe: 2022, + Javelin: 2023, + Pilum: 2024, + ShortSpear: 2025, + Glaive: 2026, + ThrowingSpear: 2027, + Spear: 2028, + Trident: 2029, + Brandistock: 2030, + Spetum: 2031, + Pike: 2032, + Bardiche: 2033, + Voulge: 2034, + Scythe: 2035, + Poleaxe: 2036, + Halberd: 2037, + WarScythe: 2038, + ShortStaff: 2039, + LongStaff: 2040, + GnarledStaff: 2041, + BattleStaff: 2042, + WarStaff: 2043, + ShortBow: 2044, + HuntersBow: 2045, + LongBow: 2046, + CompositeBow: 2047, + ShortBattleBow: 2048, + LongBattleBow: 2049, + ShortWarBow: 2050, + LongWarBow: 2051, + LightCrossbow: 2052, + Crossbow: 2053, + HeavyCrossbow: 2054, + RepeatingCrossbow: 2055, + BarbedShield: 2056, + GrimShield: 2057, + GrimHelm: 2058, + WarBelt: 2059, + BattleBelt: 2060, + MeshBelt: 2061, + SharkskinBelt: 2062, + DemonhideSash: 2063, + WarBoots: 2064, + BattleBoots: 2065, + MeshBoots: 2066, + SharkskinBoots: 2067, + DemonhideBoots: 2068, + WarGauntlets: 2069, + BattleGauntlets: 2070, + HeavyBracers: 2071, + SharkskinGloves: 2072, + DemonhideGloves: 2073, + AncientShield: 2074, + Pavise: 2075, + DragonShield: 2076, + Scutum: 2077, + RoundShield: 2078, + Defender: 2079, + MagePlate: 2080, + OrnatePlate: 2081, + ChaosArmor: 2082, + EmbossedPlate: 2083, + SharktoothArmor: 2084, + TemplarCoat: 2085, + RussetArmor: 2086, + Cuirass: 2087, + MeshArmor: 2088, + TigulatedMail: 2089, + LinkedMail: 2090, + TrellisedArmor: 2091, + DemonhideArmor: 2092, + SerpentskinArmor: 2093, + GhostArmor: 2094, + DeathMask: 2095, + GrandCrown: 2096, + WingedHelm: 2097, + Basinet: 2098, + Casque: 2099, + Sallet: 2100, + WarHat: 2101, + ChuKoNu: 2102, + Ballista: 2103, + SiegeCrossbow: 2104, + Arbalest: 2105, + GothicBow: 2106, + RuneBow: 2107, + LargeSiegeBow: 2108, + ShortSiegeBow: 2109, + DoubleBow: 2110, + CedarBow: 2111, + RazorBow: 2112, + EdgeBow: 2113, + RuneStaff: 2114, + GothicStaff: 2115, + CedarStaff: 2116, + Quarterstaff: 2117, + JoStaff: 2118, + GrimScythe: 2119, + BecdeCorbin: 2120, + Partizan: 2121, + BattleScythe: 2122, + Bill: 2123, + LochaberAxe: 2124, + Lance: 2125, + Yari: 2126, + WarFork: 2127, + Fuscina: 2128, + WarSpear: 2129, + Harpoon: 2130, + Spiculum: 2131, + Simbilan: 2132, + GreatPilum: 2133, + WarJavelin: 2134, + Hurlbat: 2135, + WarDart: 2136, + Francisca: 2137, + BattleDart: 2138, + Stilleto: 2139, + Cinquedeas: 2140, + Rondel: 2141, + Poignard: 2142, + ExecutionerSword: 2143, + Zweihander: 2144, + TuskSword: 2145, + DacianFalx: 2146, + Espandon: 2147, + AncientSword: 2148, + RuneSword: 2149, + BattleSword: 2150, + DimensionalBlade: 2151, + Tulwar: 2152, + Shamshir: 2153, + Cutlass: 2154, + Gladius: 2155, + MarteldeFer: 2156, + WarClub: 2157, + BattleHammer: 2158, + Knout: 2159, + JaggedStar: 2160, + FlangedMace: 2161, + BarbedClub: 2162, + DivineScepter: 2163, + HolyWaterSprinkler: 2164, + RuneScepter: 2165, + Cudgel: 2166, + GraveWand: 2167, + TombWand: 2168, + PetrifiedWand: 2169, + BurntWand: 2170, + AncientAxe: 2171, + GothicAxe: 2172, + Tabar: 2173, + BeardedAxe: 2174, + MilitaryAxe: 2175, + Naga: 2176, + Crowbill: 2177, + TwinAxe: 2178, + Cleaver: 2179, + Hatchet: 2180, + GothicSword: 2181, + StranglingGasPotion: 2182, + FulminatingPotion: 2183, + ChokingGasPotion: 2184, + ExplodingPotion: 2185, + RancidGasPotion: 2186, + OilPotion: 2187, + Gidbinn: 2188, + TheGidbinn1: 2189, + DecoyGidbinn: 2190, + WirtsLeg: 2191, + HoradricMalus1: 2192, + HoradricMalus2: 2193, + HellForgeHammer: 2194, + HoradricStaff1: 2195, + ShaftoftheHoradricStaff: 2196, + Orifice: 2197, + Elixir: 2198, + TomeofTownPortal: 2199, + ScrollofTownPortal: 2200, + TomeofIdentify: 2201, + ScrollofIdentify: 2202, + RightClicktoUse: 2203, + RightClicktoOpen: 2204, + RightClicktoRead: 2205, + InsertScrolls: 2206, + StaminaPotion: 2207, + AntidotePotion: 2208, + RejuvenationPotion: 2209, + FullRejuvenationPotion: 2210, + ThawingPotion: 2211, + Amulet: 2212, + TopoftheHoradricStaff: 2213, + Ring: 2214, + Gold: 2215, + ScrollofInifuss: 2216, + KeytotheCairnStones: 2217, + Arrows: 2218, + Torch: 2219, + Bolts: 2220, + Key1: 2221, + Key2: 2222, + TheBlackTowerKey: 2223, + RightClicktoPermanentlyAdd20toLifePotionofLife: 2224, + Shrine: 2225, + TeleportPad: 2226, + AJadeFigurine: 2227, + TheGoldenBird: 2228, + LamEsensTome1: 2229, + LamEsensTome2: 2230, + HoradricCube: 2231, + HoradricScroll: 2232, + MephistosSoulstone: 2233, + RightClicktoLearnSkillofYourChoiceBookofSkill: 2234, + Ear: 2235, + ChippedAmethyst: 2236, + FlawedAmethyst: 2237, + Amethyst: 2238, + FlawlessAmethyst: 2239, + PerfectAmethyst: 2240, + ChippedTopaz: 2241, + FlawedTopaz: 2242, + Topaz: 2243, + FlawlessTopaz: 2244, + PerfectTopaz: 2245, + ChippedSapphire: 2246, + FlawedSapphire: 2247, + Sapphire: 2248, + FlawlessSapphire: 2249, + PerfectSapphire: 2250, + ChippedEmerald: 2251, + FlawedEmerald: 2252, + FlawlessEmerald: 2253, + Emerald: 2254, + PerfectEmerald: 2255, + ChippedRuby: 2256, + FlawedRuby: 2257, + Ruby: 2258, + FlawlessRuby: 2259, + PerfectRuby: 2260, + ChippedDiamond: 2261, + FlawedDiamond: 2262, + Diamond: 2263, + FlawlessDiamond: 2264, + PerfectDiamond: 2265, + MinorHealingPotion: 2266, + LightHealingPotion: 2267, + HealingPotion: 2268, + GreaterHealingPotion: 2269, + SuperHealingPotion: 2270, + MinorManaPotion: 2271, + LightManaPotion: 2272, + ManaPotion: 2273, + GreaterManaPotion: 2274, + SuperManaPotion: 2275, + Herb: 2276, + ChippedSkull: 2277, + FlawedSkull: 2278, + Skull: 2279, + FlawlessSkull: 2280, + PerfectSkull: 2281, + 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: { + Ladder: 719, + 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, + RejectedByServer: 5215, + 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..dd371933d --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/Advertise.js @@ -0,0 +1,44 @@ +/** +* @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)); + + let message = Array.isArray(Config.Advertise.Message) + ? Config.Advertise.Message.random() + : Config.Advertise.Message; + + say("!" + 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/AntiIdle.js b/d2bs/kolbot/libs/modules/workers/AntiIdle.js new file mode 100644 index 000000000..bf8677201 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/AntiIdle.js @@ -0,0 +1,35 @@ +/** +* @filename AntiIdle.js +* @author theBGuy +* @desc Worker script for idleing in town to prevent disconnects +* +*/ + +(function (module, require, Worker) { + // Only load this in global scope + if (new RegExp(/[default.dbj|main.js]/gi).test(getScript(true).name)) { + 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; + }; + })(); + + console.log("ÿc2Kolbotÿc0 :: AntiIdle 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..c9af49e03 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/SimpleParty.js @@ -0,0 +1,230 @@ +(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) { + if (e instanceof ScriptError) { + throw 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; + SimpleParty.enabled = true; + + 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)); + } else if (msg === "unparty") { + SimpleParty.enabled = false; + clickParty(getParty(), BUTTON_LEAVE_PARTY); + } + }); + + // 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..bcdaa7e8c --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/TownChicken.js @@ -0,0 +1,396 @@ +/** +* @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) { + if (e instanceof ScriptError) { + throw 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 (e) { + if (e instanceof ScriptError) { + throw e; + } + 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) { + if (e instanceof ScriptError) { + throw 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) { + if (e instanceof ScriptError) { + throw 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) { + if (e instanceof ScriptError) { + throw 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..4104d35f7 --- /dev/null +++ b/d2bs/kolbot/libs/oog/D2Bot.js @@ -0,0 +1,230 @@ +/** +* @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 () { + // Pick up handle updates, in case the instance is taken over by a new manager. + addEventListener("copydata", function (mode, msg) { + if (msg === "Handle" && typeof mode === "number") { + D2Bot.handle = mode; + } + }); + + 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..874993d4e --- /dev/null +++ b/d2bs/kolbot/libs/oog/DataFile.js @@ -0,0 +1,168 @@ +/** +* @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 = { + _path: "data/" + me.profile + ".json", + _default: { + handle: 0, + name: "", + level: 0, + experience: 0, + gold: 0, + deaths: 0, + runs: 0, + lastArea: "", + ingameTick: 0, + gameName: "", + currentGame: "", + nextGame: "" + }, + + init: function () { + if (!FileTools.exists(this._path)) { + this.create(); + + return true; + } + return false; + }, + + create: function () { + FileAction.write(this._path, 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(this._path) && DataFile.create(); + + let obj; + let string = FileAction.read(this._path); + + 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(this._path, 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..10fb70f41 --- /dev/null +++ b/d2bs/kolbot/libs/oog/Locations.js @@ -0,0 +1,479 @@ +/** +* @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(); + // Controls.TcpIpCancel.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(); + } + ], + [ + sdk.game.locations.ServerDown, + function () { + ControlAction.timeoutDelay("Server Down", Time.minutes(5)); + Controls.OkCentered.click(); + } + ], + ]); + 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.LobbyPleaseWait], + function (location) { + let startTick = getTickCount(); + if (!Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location)) { + Controls.OkCentered.click(); + } else { + if (getTickCount() - startTick < Time.seconds(5)) { + ControlAction.timeoutDelay( + "After Game Delay", + Math.max((Time.seconds(5) - (getTickCount() - startTick), 1000)) + ); + } + } + } + ); + addLocations([sdk.game.locations.CharSelectPleaseWait], + function (location) { + if (!Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, location)) { + Controls.OkCentered.click(); + } + } + ); + addLocations([sdk.game.locations.Disconnected, sdk.game.locations.LobbyLostConnection], + function (loc) { + ControlAction.timeoutDelay(loc === sdk.game.locations.Disconnected ? "Disconnected" : "Lost Connection", 3000); + 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"; + } + ); + + /** @param {number} loc */ + const run = function (loc) { + try { + let func = _loc.get(loc); + if (typeof func === "function") { + // console.debug("Handling location: " + loc); + Starter.lastLocation.push(loc); + if (Starter.lastLocation.length > 5) { + Starter.lastLocation.shift(); + } + func(loc); + } else if (loc !== undefined && loc !== null) { + console.log("Unhandled location: " + loc); + } + } catch (e) { + console.error(e); + } + }; + + module.exports = { + locations: _loc, + addLocations: addLocations, + parseControlText: parseControlText, + run: run, + }; +})(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..4ef5146ac 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,146 @@ // 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 + } + + // remove the / from it, in case it was libs/ (rather than lib/) and we now have a leading slash + if (directory.startsWith("/")) { + directory = directory.substr(1); + } + + 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); + } + + // remove the / from it, in case it was libs/ (rather than lib/) and we now have a leading slash + if (fullpath.startsWith("/")) { + fullpath = fullpath.substr(1); + } + + 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 && console.log("ÿc2Kolbotÿc0 :: - loading dependency of " + filename + ": " + moduleNameShort); + !depth && notify && console.log("ÿ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..39df5ebac --- /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[me.classid].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..63d2531f0 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/AutoBaal.js @@ -0,0 +1,297 @@ +/** +* @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[me.classid].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[me.classid].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[me.classid].checkTraps({ x: 15095, y: 5037 })) { + ClassAttack[me.classid].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[me.classid].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/AutoChaos.js b/d2bs/kolbot/libs/scripts/AutoChaos.js new file mode 100644 index 000000000..02b1b2f46 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/AutoChaos.js @@ -0,0 +1,701 @@ +/** +* @filename AutoChaos.js +* @author noah-@github.com, theBGuy +* @desc AutoChaos is an anonymous Team CS script without any explicit in-game communication. +* This script is designed for running Classic Taxi CS. +* @note this has been experimentally updated to work with current kolbot and has not been fully tested. +*/ + + +const AutoChaos = new Runnable( + function AutoChaos() { + let taxi = ""; + let tpID = 0; + let lastCall = 0; + let lastUpdate = 0; + + /** + * @param {string} message + * @param {boolean} overhead + */ + function notify(message, overhead) { + if (overhead) { + me.overhead(overhead); + } + + takeScreenshot(); + return message; + } + + function setTaxi() { + if (Config.AutoChaos.Leader !== "" && getParty(Config.AutoChaos.Leader)) { + taxi = Config.AutoChaos.Leader; + } else if (taxi === "" || !getParty(taxi)) { + taxi = detectTaxi(); + } + } + + function detectTaxi() { + let player = getParty(); + let current = taxi; + let lvl = 0; + + if (!player) { + return current; + } + + do { + if (player.name !== me.name && player.classid === sdk.player.class.Sorceress && lvl < player.level) { + let tick = getTickCount(); + + while (player.area === sdk.areas.None && getTickCount() - tick < 5000) { + delay(100); + } + + if (player.area !== sdk.areas.None && player.area < sdk.areas.PandemoniumFortress) { + continue; + } + + current = player.name; + lvl = player.level; + + if (Pather.getPortal(108, current) && !Pather.getPortal(sdk.areas.ChaosSanctuary, player.name)) { + break; + } + } + } while (player.getNext()); + + return current; + } + + function doNext() { + let portal, gid, tick; + + if (taxi === "" || (lastCall >= 3 && Config.AutoChaos.Diablo === -1)) { + Town.doChores(); + return true; + } + + if (lastCall >= 3 && Config.AutoChaos.UseShrine) { + Town.goToTown(1); + Town.move("portalspot"); + + if ((portal = Pather.getPortal(sdk.areas.StonyField, null)) || + (portal = Pather.getPortal(sdk.areas.ColdPlains, null)) || + (portal = Pather.getPortal(sdk.areas.BloodMoor, null))) { + Pather.usePortal(null, null, portal); + getShrine(sdk.shrines.Experience, 8, 10); + + if (!Pather.usePortal(sdk.areas.RogueEncampment, null)) { + Town.goToTown(); + } + } + + Pather.useWaypoint(sdk.areas.PandemoniumFortress, true); + Town.move("portalspot"); + Config.AutoChaos.UseShrine = false; + } + + portal = Pather.getPortal(null, taxi); + + if (!portal || portal.gid === tpID) { + tick = getTickCount(); + + if (lastCall < 3 && (tick - lastUpdate) > 10000) { + taxi = ""; + lastUpdate = tick; + } + + return true; + } + + gid = portal.gid; + + if (Config.AutoChaos.BO && portal.objtype !== 107) { + return true; + } + + if (Config.AutoChaos.SealDelay) { + delay(Config.AutoChaos.SealDelay * 1000); + } + + if (!Pather.usePortal(null, null, portal)) { + return true; + } + + tpID = gid; + lastUpdate = getTickCount(); + + if (me.inArea(sdk.areas.RiverofFlame)) { + precast(); + } else if (me.inArea(sdk.areas.ChaosSanctuary)) { + if (Config.AutoChaos.SealPrecast) { + Precast.doPrecast(true); + } + + Common.Diablo.initLayout(); + + if (Common.Diablo.getSealDistance(sdk.objects.DiabloSealVizier) < 60) { + vizier(); + } else if (Common.Diablo.getSealDistance(sdk.objects.DiabloSealInfector) < 60) { + infector(); + } else if (Common.Diablo.getSealDistance(sdk.objects.DiabloSealSeis) < 80) { + seis(); + } else if (getDistance(me, 7795, 5293) < 30) { + diablo(); + return false; + } else { + notify("", "I should not be here..."); + } + } + + Town.goToTown(); + return true; + } + + /** + * @param {number} area + */ + function waitForParty(area = 0) { + let time = getTickCount(); + let classes = copyObj(Config.AutoChaos.RequireClass); + + while (Object.values(classes).indexOf(true) >= 0 && (getTickCount() - time < 25000)) { + let party = getParty(); + + if (party) { + do { + if (classes[sdk.player.class.nameOf(party.classid)] && party.level >= 30) { + if (area > 0 && party.area !== me.area) { + time = getTickCount(); + continue; + } + + classes[sdk.player.class.nameOf(party.classid)] = false; + } + } while (party.getNext()); + } + + delay(250); + } + + if (classes.indexOf(true) >= 0) { + throw new Error(notify("Not enough players.")); + } + } + + function precast() { + let sorc = getParty(taxi); + + while ( + sorc + && sorc.area !== sdk.areas.ChaosSanctuary + && (!me.getState(sdk.states.BattleOrders) || Game.getPlayer(taxi)) + ) { + Precast.doPrecast(true); + delay(500); + } + + if (!Pather.usePortal(null, taxi)) { + Pather.useWaypoint(sdk.areas.PandemoniumFortress, true); + } + + if (Town.needHealing() || (Town.checkScrolls("tbk") < 10)) { + Town.doChores(); + } + + Town.move("portalspot"); + } + + /** + * @param {number} type + * @param {number} distance + * @param {number} retry + */ + function getShrine(type, distance, retry) { + let shrine = getUnit(2); + + if (!shrine) { + return; + } + + do { + if (getDistance(me.x, me.y, shrine.x, shrine.y) < distance && shrine.objtype === type) { + while (retry-- > 0) { + if (Misc.getShrine(shrine)) { + break; + } + } + + return; + } + } while (shrine.getNext()); + } + + function sealDelay() { + if (!Config.AutoChaos.SealDelay) { + return; + } + + const time = Config.AutoChaos.SealDelay * 1000 + getTickCount(); + const loc = new PathNode(me.x, me.y); + + while (getTickCount() < time) { + if (me.inTown) { + delay(Config.AutoChaos.SealDelay * 1000); + } else { + doPreattack(loc); + } + } + } + + function clearOut() { + Pickit.pickItems(); + Town.goToTown(); + lastCall += 1; + Town.move("portalspot"); + } + + function vizier() { + slayBoss(sdk.locale.monsters.GrandVizierofChaos, Config.AutoChaos.PreAttack[0], 20); + clearOut(); + } + + function seis() { + slayBoss(sdk.locale.monsters.LordDeSeis, Config.AutoChaos.PreAttack[1], 30); + clearOut(); + } + + function infector() { + slayBoss(sdk.locale.monsters.InfectorofSouls, Config.AutoChaos.PreAttack[2], 20); + clearOut(); + } + + /** + * @param {number} amount + */ + function taxiInit(amount = 0) { + if (amount > 0) { + delay(amount); + } + + const pos = new PathNode(me.x, me.y); + const time = getTickCount(); + let count = time; + + Precast.doPrecast(); + + while (!me.getState(sdk.states.BattleOrders)) { + if (getTickCount() - count > 5000) { + count = getTickCount(); + Pather.moveTo(pos.x + rand(-4, 4), pos.y + rand(-4, 4)); + } + + if (getTickCount() - time > 50000) { + console.log("Game timed out."); + return false; + } + + delay(100); + } + + return true; + } + + /** + * @param {boolean} last + */ + function taxiVizier(last) { + const viz = Common.Diablo.vizLayout === 1 ? new PathNode(7683, 5302) : new PathNode(7687, 5315); + + Pather.moveTo(viz.x, viz.y); + Pather.makePortal(false); + Common.Diablo.openSeal(sdk.objects.DiabloSealVizier); + + if (!last) { + Common.Diablo.openSeal(sdk.objects.DiabloSealVizier2); + } + + Pather.moveTo(viz.x, viz.y); + sealDelay(); + slayBoss(sdk.locale.monsters.GrandVizierofChaos, Config.AutoChaos.PreAttack[0], 25, 0); + + if (last) { + Common.Diablo.openSeal(sdk.objects.DiabloSealVizier2); + Pather.moveTo(viz.x, viz.y); + } + + Pickit.pickItems(); + } + + function taxiSeis() { + const seis = Common.Diablo.seisLayout === 1 ? new PathNode(7782, 5224) : new PathNode(7775, 5193); + + Pather.moveTo(seis.x, seis.y, 10); + Pather.makePortal(false); + Common.Diablo.openSeal(sdk.objects.DiabloSealSeis); + sealDelay(); + Pather.moveTo(seis.x, seis.y, 10); + Pather.teleDistance = 45; + slayBoss(sdk.locale.monsters.LordDeSeis, Config.AutoChaos.PreAttack[1], 30, 0); + Pickit.pickItems(); + } + + /** + * @param {boolean} last + */ + function taxiInfector(last) { + const inf = Common.Diablo.infLayout === 1 ? new PathNode(7926, 5300) : new PathNode(7924, 5282); + + Pather.moveTo(inf.x, inf.y); + Pather.makePortal(false); + Common.Diablo.openSeal(sdk.objects.DiabloSealInfector); + + if (!last) { + Common.Diablo.openSeal(sdk.objects.DiabloSealInfector2); + } + + Pather.moveTo(inf.x, inf.y); + slayBoss(sdk.locale.monsters.InfectorofSouls, Config.AutoChaos.PreAttack[2], 25, 0); + + if (last) { + Common.Diablo.openSeal(sdk.objects.DiabloSealInfector2); + Pather.moveTo(inf.x, inf.y); + } + + Pickit.pickItems(); + } + + function diablo() { + let maxTime = 0; + let preCount = 1; + let postCount = 1; + let party = getParty(); + + if (party) { + do { + if (party.level >= 30) { + preCount += 1; + } + } while (party.getNext()); + } + + if (Config.AutoChaos.Leech) { + Pather.moveTo(7767, 5263); + } else { + if (Config.AutoChaos.Diablo >= 0) { + Pather.moveTo(7798, 5293); + slayBoss(sdk.locale.monsters.Diablo, 0, 30, Config.AutoChaos.Diablo); + + if (Config.AutoChaos.Diablo === 0) { + let pick = getTickCount(); + + while (getTickCount() - pick < 1500) { + Pickit.pickItems(); + delay(me.ping + 100); + } + + return; + } + } + + if (!Pather.usePortal(sdk.areas.PandemoniumFortress, null)) { + Town.goToTown(); + } + + Town.doChores(); + } + + while (maxTime < 180000) { + party = getParty(); + + if (party) { + postCount = 1; + do { + if (party.level >= 30) { + postCount += 1; + } + } while (party.getNext()); + + if (postCount < preCount || (!Config.AutoChaos.Taxi && !getParty(taxi))) { + break; + } + } + + maxTime += 3000; + delay(3000); + } + } + + /** + * @param {PathNode} loc + */ + function doPreattack(loc) { + switch (me.classid) { + case sdk.player.class.Amazon: + case sdk.player.class.Sorceress: + case sdk.player.class.Necromancer: { + if (me.getState(sdk.states.SkillDelay)) { + let player = Game.getPlayer(); + Skill.cast(Config.AttackSkill[2], 0, player.x, player.y); + } else { + Skill.cast(Config.AttackSkill[1], 0, me.x, me.y); + } + + if (Config.Dodge && me.hp * 100 / me.hpmax <= Config.DodgeHP) { + Attack.deploy(loc, Config.DodgeRange, 5, 15); + } + + return true; + } + case sdk.player.class.Paladin: + if (Config.AttackSkill[3] !== sdk.skills.BlessedHammer) { + return false; + } + + if (Config.AttackSkill[4] > 0) { + Skill.setSkill(Config.AttackSkill[4], 0); + } + + for (let i = 0; i < 3; i++) { + Skill.cast(Config.AttackSkill[3], 1); + } + return true; + case sdk.player.class.Barbarian: + Skill.cast(Config.AttackSkill[3], Skill.getHand(Config.AttackSkill[3])); + return true; + case sdk.player.class.Druid: + if (Config.AttackSkill[3] === sdk.skills.Tornado) { + return Skill.cast(Config.AttackSkill[3], 0, me.x, me.y); + } + + break; + case sdk.player.class.Assassin: { + if (Config.UseTraps) { + let check = ClassAttack[me.classid].checkTraps({ x: me.x, y: me.y }); + + if (check) { + return ClassAttack[me.classid].placeTraps({ x: me.x, y: me.y }, 5); + } + } + + break; + } + default: + delay(2000); + } + + return false; + } + + /** + * @param {number} classid + * @param {number} [preattack] + * @param {number} [retry] + * @param {number} [stop] + */ + function slayBoss(classid, preattack, retry, stop = 0) { + /** @type {Monster | null} */ + let boss = null; + let bosshp = 0; + let reposition = 0; + let checkPosition = 0; + let tick = 0; + let time = getTickCount() + (retry * 1000); + let loc = new PathNode(me.x, me.y); + + const name = getLocaleString(classid); + + while (getTickCount() < time) { + if (!boss) { + boss = Game.getMonster(name); + } else if (boss && boss.attackable && preattack > 0) { + preattack -= 1; + } else { + break; + } + + if (me.paladin || classid !== sdk.locale.monsters.Diablo) { + doPreattack(loc); + } + + delay(me.ping + 10); + } + + if (!boss) { + console.log("Unable to find: " + name); + return; + } + + if (!Config.AutoChaos.Ranged) { + for (let i = 0; i < 10; i++) { + if (Pather.moveTo(boss.x + rand(-3, 3), boss.y + rand(0, 3), 0)) { + break; + } else { + doPreattack(loc); + } + } + } + + time = getTickCount() + (retry * 1000); + + do { + if (!boss || !boss.attackable) { + return; + } + + bosshp = parseInt((boss.hp * 100) / 128, 10); + + if (bosshp < stop) { + return; + } + + if (tick - checkPosition >= 3000) { + if (Pather.useTeleport || bosshp >= reposition) { + if (Config.AutoChaos.Ranged) { + Attack.deploy(boss, 15, 5, 9); + } else { + if (Pather.useTeleport) { + Attack.deploy(boss, 3, 5, 9); + } else { + if (time - tick < 10000) { + notify("", name + " loc:" + boss.x + "," + boss.y + " me loc:" + me.x + "," + me.y); + loc.update(boss.x + rand(-5, 5), boss.y + rand(0, 5)); + } else { + loc.update(boss.x + rand(-3, 3), boss.y + rand(0, 3)); + } + + Pather.moveTo(loc.x, loc.y, 0); + Misc.click(0, 0, loc.x, loc.y); + } + } + } + + checkPosition = tick; + reposition = bosshp; + } + + try { + if (!ClassAttack[me.classid].doAttack(boss, false)) { + Skill.cast(Config.AttackSkill[3], Skill.getHand(Config.AttackSkill[3]), boss); + } + } catch (e) { + console.error(e); + } + + tick = getTickCount(); + } while (tick < time); + + throw new Error(notify("Failed to kill: " + name + " in " + retry + " sec", "Failed to kill:" + name + " loc:" + boss.x + "," + boss.y + " me loc:" + me.x + "," + me.y)); + } + + // START ------------------------------------------------------------------------------------------------------------- + Common.Diablo.addLightsEventListener(); + Common.Diablo.on("diablospawned", function () { + lastCall = 3; + }); + + if (me.area !== sdk.areas.PandemoniumFortress) { + Pather.useWaypoint(sdk.areas.PandemoniumFortress, true); + } + + Pather.walkTo(me.x + rand(-5, 5), me.y + rand(-5, 5)); + Town.doChores(); + + if (Config.AutoChaos.Taxi) { + waitForParty(); + + Pather.useWaypoint(sdk.areas.RiverofFlame, true); + Pather.makePortal(false); + Pather.moveTo(me.x, me.y - 5); + + waitForParty(sdk.areas.RiverofFlame); + + if (!taxiInit()) { + return false; + } + + Pather.moveTo(7797, 5606); + Pather.moveTo(7797, 5590); + + for (let i = 0; i < 3; i += 1) { + if (Config.AutoChaos.SealOrder[i] === 1) { + taxiVizier(i === 2); + } else if (Config.AutoChaos.SealOrder[i] === 2) { + taxiSeis(i === 2); + } else if (Config.AutoChaos.SealOrder[i] === 3) { + taxiInfector(i === 2); + } else { + throw new Error(notify("Invalid Config.AutoChaos.SealOrder setting.")); + } + } + + Pather.moveTo(7786, 5285); + Pather.makePortal(false); + diablo(); + } else if (Config.AutoChaos.FindShrine) { + Pather.useWaypoint(sdk.areas.StonyField, true); + + const foundShrine = [sdk.areas.StonyField, sdk.areas.ColdPlains, sdk.areas.BloodMoor].some(function (area) { + return Misc.getShrinesInArea(area, 15, false); + }); + + if (foundShrine) { + console.log("Exp shrine found."); + Town.goToTown(); + } else { + Pather.journeyTo(sdk.areas.RogueEncampment); + } + + Pather.useWaypoint(sdk.areas.PandemoniumFortress, true); + Town.doChores(); + let time = getTickCount(); + + while (me.ingame) { + if (getTickCount() - time > 5000) { + time = getTickCount(); + Pickit.pickItems(); // find stray gold on ground + setTaxi(); + + if (taxi === "" || !getParty(taxi)) { + break; + } + } + + delay(100); + } + } else if (Config.AutoChaos.Glitcher) { + // Glitcher mode + } else { + Town.move("portalspot"); + let time = getTickCount(); + + while (me.ingame) { + delay(100); + setTaxi(); + + if (getTickCount() - time > 25000) { + if (taxi === "" || !getParty(taxi)) { + break; + } + + time = getTickCount(); + } + + if (!doNext()) { + break; + } + } + } + + return true; + }, + { + startArea: sdk.areas.PandemoniumFortress, + preAction: null, + cleanup: function () { + Common.Diablo.off("diablospawned"); + Common.Diablo.removeLightsEventListener(); + } + } +); diff --git a/d2bs/kolbot/libs/scripts/Baal.js b/d2bs/kolbot/libs/scripts/Baal.js new file mode 100644 index 000000000..b52cf2f44 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Baal.js @@ -0,0 +1,105 @@ +/** +* @filename Baal.js +* @author kolton, YGM, theBGuy +* @desc clear Throne of Destruction and kill Baal +* +*/ + +const Baal = new Runnable( + function Baal () { + 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) { + if (!Config.Baal.Silent) { + 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(); + if (!Config.Baal.Silent) { + 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..d05de72be --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BaalAssistant.js @@ -0,0 +1,510 @@ +/** +* @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 () { + 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) + }))) { + console.log("ÿc hotCheck, Time.seconds(Config.BaalAssistant.Wait), 1000); + + if (!hotCheck) { + console.log("ÿ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 || Config.BaalAssistant.HurtBaal > 0) { + delay(1000); + Pather.moveTo(15134, 5923); + Config.BaalAssistant.HurtBaal > 0 + ? Attack.hurt(sdk.monsters.Baal, Config.BaalAssistant.HurtBaal) + : 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..5259fd96b --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BaalHelper.js @@ -0,0 +1,159 @@ +/** +* @filename BaalHelper.js +* @author kolton, theBGuy +* @desc help the leading player in clearing Throne of Destruction and killing Baal +* +*/ + +/** + * @typedef {ScriptContext & { chatEvent: (nick: string, msg: string) => void }} BaalHelperContext + */ + +const BaalHelper = new Runnable( + /** @param {BaalHelperContext} ctx */ + function BaalHelper (ctx) { + let quitFlag; + + 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; + } + } + }; + ctx.chatEvent = chatEvent; + + 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 ScriptError( + "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: function () { + 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"); + + quitFlag = false; + + if (Config.BaalHelper.DollQuit || Config.BaalHelper.SoulQuit) { + addEventListener("chatmsg", chatEvent); + } + + 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 ScriptError("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); + } + } + + if (Config.BaalHelper.DollQuit && Game.getMonster(sdk.monsters.SoulKiller)) { + console.log("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 || Config.BaalHelper.HurtBaal) { + Common.Baal.killBaal(Config.BaalHelper.HurtBaal); + } else { + Town.goToTown(); + // infinite loops are bad, TODO: add break condition, maybe a 5-10 minute timeout? + 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(); + } + + Config.RandomPrecast && Precast.needOutOfTownCast() + ? Precast.doRandomPrecast(true, (Config.BaalHelper.SkipTP ? sdk.areas.WorldstoneLvl2 : sdk.areas.Harrogath)) + : Precast.doPrecast(true); + + if (!Config.BaalHelper.SkipTP) { + Town.goToTown(5); + Town.move("portalspot"); + } + }, + /** @param {BaalHelperContext} ctx */ + cleanup: function (ctx) { + removeEventListener("chatmsg", ctx.chatEvent); + } + } +); + +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..655fa4f6f --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ClassicChaosAssistant.js @@ -0,0 +1,134 @@ +/** +* @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 () { + 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..c1ea012cd --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ControlBot.js @@ -0,0 +1,2248 @@ +/** +* @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 +* +*/ + +/** + * @typedef {ScriptContext & { cleanup: () => void }} ControlBotContext + */ + +const ControlBot = new Runnable( + /** + * @param {ControlBotContext} ctx + */ + function ControlBot (ctx) { + const thankYouMessages = [ + "Ty {name}. Current count: {stats}", + "Got your vote, {name}! The tally is now {stats}", + "Vote recorded, {name}. Current standings: {stats}", + "Thanks {name}, I've counted your vote. Current count: {stats}", + "{name}'s vote has been tallied. Updated count: {stats}", + "Roger that {name}, vote recorded. Current status: {stats}" + ]; + + const voteCompletionMessages = [ + "Thank you {name}, that settles it. Time to tally the votes.", + "And {name} makes it unanimous! Tallying votes now.", + "That's everyone! Thanks {name} for the final vote. Let's count them up.", + "With {name}'s vote, we're all accounted for. Time for the results.", + "Last vote in from {name}! Let's see where we stand.", + "Decision time! {name} has cast the final vote. Counting now." + ]; + + const alreadyCountedMessages = [ + "Your vote has already been counted", + "I've already recorded your vote", + "You've voted already, no need to vote again", + "One vote per person, yours is already counted", + "Vote already registered, thanks!", + "I remember your vote, no need to repeat" + ]; + + const voteRequestMessages = [ + "{players} please cast your vote for ng. Voting ends in {time}s", + "Still waiting on ng votes from {players}. {time} seconds remaining", + "Don't forget to vote for ng {players}! Time remaining: {time}s", + "{players}, we need your vote for ng! {time} seconds left to decide", + "Hey {players}, make your voice heard! {time}s left to vote for ng", + "{time} seconds left and we're still waiting on {players} to vote for ng" + ]; + + const queuePositionMessages = [ + "{command} has been added to the queue. Position: {position}", + "Added {command} to queue. You're #{position} in line", + "Request for {command} queued. Current position: {position}", + "I'll get to your {command} request soon. Queue position: {position}", + "You're #{position} in the queue for {command}", + "{command} added. There are {position} requests ahead of you" + ]; + + const currentlyRunningMessages = [ + "Currently running {command} for {nick}", + "Right now I'm helping {nick} with {command}", + "Busy with {command} for {nick} at the moment", + "Working on {command} with {nick} now", + "{nick}'s {command} request is in progress" + ]; + + const stillRunningMessages = [ + "Still processing your {command} request right now", + // come up with others + ]; + + // 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, + timedOut, + } = 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 cain = function (nick) { + log("starting cain"); + + if (Game.getNPC(NPC.Cain)) { + log("Cain has already been rescued"); + + return true; + } + + Town.doChores(); + Pather.useWaypoint(sdk.areas.StonyField, true); + Precast.doPrecast(true); + if (!Pather.journeyTo(sdk.areas.Tristram)) { + log("Can't get to tristram"); + + return true; + } + + 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, { range: 25, duration: 3000 }); + Pather.makePortal(); + log(AutoRush.playersIn); + + const cainRescued = Misc.poll(function () { + Attack.securePosition(me.x, me.y, { range: 15, duration: 1000 }); + return gibbet.mode; + }, Time.minutes(2)); + + if (!cainRescued) { + log(timedOut(nick)); + return false; + } + } + } + + 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)) { + throw new Error("Failed to move to durance 3"); + } + Pather.moveTo(17617, 8069); + Attack.securePosition(me.x, me.y, { range: 30, duration: 3000, skipIds: [sdk.monsters.Mephisto] }); + Attack.securePosition(17591, 8070, { range: 20, duration: 3000, skipIds: [sdk.monsters.Mephisto] }); + 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)) { + timedOut(nick); + 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"; + + // TODO: Handle multi's abusing the vote by having multiple accounts in the game + // most multi's use similar names so we can check for that then need to update votes needed based on that + // since their vote should only count as 1 + /** @typedef {"yes" | "no" | "undecided"} NgVote */ + const ngVote = { + /** @type {Map} */ + votes: new Map(), + active: false, + tick: 0, + nextGame: false, + undecidedAskTick: 0, + lastVotePeriod: { + startedBy: "", + endedAt: 0, + }, + + /** + * Calculate the number of votes needed for a decision + * @returns {number} + */ + votesNeeded: function () { + return Math.max(1, Math.floor((Misc.getPartyCount()) / 2)); + }, + + /** + * Get the time since the last vote period ended + * @returns {number} + */ + timeSinceLastVote: function () { + return getTickCount() - this.lastVotePeriod.endedAt; + }, + + /** + * Reset the voting state + */ + reset: function () { + this.votes.clear(); + this.tick = 0; + this.active = false; + this.lastVotePeriod.endedAt = getTickCount(); + }, + + /** + * Begin a new voting session + * @param {string} nick + */ + begin: function(nick) { + this.active = true; + this.votes.clear(); + for (let player of Misc.getPartyMembers()) { + this.votes.set(player.name, "undecided"); + } + this.tick = getTickCount(); + this.lastVotePeriod.startedBy = nick; + }, + + /** + * Check current count + */ + count: function () { + let [yes, no, undecided] = [0, 0, 0]; + + for (let [name, vote] of this.votes) { + if (!Misc.inMyParty(name)) { + continue; + } + + if (vote === "yes") { + yes++; + } else if (vote === "no") { + no++; + } else if (vote === "undecided") { + undecided++; + } + } + + return { + yes: yes, + no: no, + undecided: undecided + }; + }, + + stats: function () { + const { yes, no, undecided } = this.count(); + + return ("yes: " + yes + " no: " + no + " undecided: " + undecided); + }, + + /** + * Check the current vote count and determine the outcome + * @param {boolean} skipUndecided + * @returns {boolean} + */ + checkCount: function (skipUndecided = false) { + let undecided = []; + + if (!skipUndecided) { + for (let [playerName, vote] of this.votes) { + if (vote === "undecided" && Misc.inMyParty(playerName)) { + undecided.push(playerName); + } + } + + if (undecided.length) { + if (getTickCount() - ngVote.undecidedAskTick > Time.seconds(30)) { + let votingPeriodRemaining = Math.round(Time.toSeconds(Time.minutes(2) - ngVote.elapsed())); + let message = voteRequestMessages.random() + .replace("{players}", undecided.join(", ")) + .replace("{time}", votingPeriodRemaining); + Chat.say(message); + ngVote.undecidedAskTick = getTickCount(); + } + return false; + } + } + + const votesNeeded = this.votesNeeded(); + const { yes: yesVotes, no: noVotes } = this.count(); + + if (Misc.getPartyCount() === yesVotes + noVotes && yesVotes === noVotes) { + Chat.say("Not enough votes to start ng we have a draw."); + this.reset(); + return false; + } + + if (noVotes >= votesNeeded && noVotes > yesVotes) { + Chat.say("ng rejected by majority."); + this.reset(); + return false; + } + + if (yesVotes >= votesNeeded) { + Chat.say("ng approved by majority."); + this.nextGame = true; + this.reset(); + return true; + } + + return false; + }, + + /** + * Register a vote + * @param {string} nick + * @param {"yes" | "no"} type + */ + vote: function(nick, type) { + if (!this.active) return; + this.votes.set(nick, type); + }, + + /** + * Get the elapsed time since the vote started + * @returns {number} + */ + 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 sendChatMessage = say; + + /** @param {string} msg */ + global.say = function (msg) { + if (typeof msg !== "string") { + throw new TypeError("Message must be a string"); + } + Chat.say(msg); + }; + + ctx.cleanup = function () { + // restore the original say function + global.say = sendChatMessage; + }; + + /** + * @constructor + * @param {string} msg + */ + function Message (msg) { + if (typeof msg !== "string") { + throw new TypeError("Message must be a string"); + } + this.msg = msg; + this.createdAt = getTickCount(); + } + + const Chat = { + overheadTick: 0, + /** @type {Message[]} */ + queue: [], + + /** + * Send a message in chat + * @param {string} msg + */ + say: function (msg) { + Chat.queue.push(new Message(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); + sendChatMessage("!" + 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(new Message("/w " + who + " " + msg)); + }, + + /** + * Private message a chat to a user + * @param {string} nick + * @param {string} msg + */ + message: function (nick, msg) { + Chat.queue.push(new Message("/m " + nick + " " + msg)); + }, + }; + + Worker.runInBackground.chat = (function () { + let tick = getTickCount(); + let burstCount = 0; + let burstStartTime = 0; + const BURST_LIMIT = 4; + const BURST_WINDOW = Time.seconds(16); + const BURST_COOLDOWN = Time.seconds(10); + + 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].msg.startsWith("/w")) { + // check if the player is in the game and if not, don't send the whisper + } + + // don't immediately respond, seems to trigger temp mutes more often + if (getTickCount() - Chat.queue[0].createdAt < 500) { + // don't send messages that are too new + return true; + } + + // Burst protection (prevent too many messages in short time) + let currentTime = getTickCount(); + + // Reset burst count if window has passed + if (currentTime - burstStartTime > BURST_WINDOW) { + burstCount = 0; + burstStartTime = currentTime; + } + + // If we've hit burst limit, enforce cooldown + if (burstCount >= BURST_LIMIT) { + if (currentTime - burstStartTime < BURST_COOLDOWN) { + return true; // Still in cooldown period + } + burstCount = 0; + burstStartTime = currentTime; + } + + if (burstCount === 0) { + burstStartTime = currentTime; + } + + // allow say messages every ~1.7 seconds + tick = getTickCount() + Time.seconds(1) + rand(500, 950); + burstCount += 1; + + console.debug("(" + Chat.queue[0].msg + ") [Burst: " + burstCount + "/" + BURST_LIMIT + "]"); + if (Chat.queue[0].msg.length > MAX_CHAT_LENGTH) { + console.debug("Message too long, splitting."); + Chat.queue[0].msg = Chat.queue[0].msg.substring(0, MAX_CHAT_LENGTH); + } + sendChatMessage(Chat.queue.shift().msg); + return true; + }; + })(); + + Worker.runInBackground.flooders = (function () { + let tick = getTickCount(); + + return function () { + if (getTickCount() - tick < 0) return true; + // check every 1 second + tick = getTickCount() + Time.seconds(1) + rand(500, 950); + + for (let [key, player] of playerTracker) { + if (getTickCount() - player.ignoredAt > Time.minutes(1)) { + if (!player.ignored) continue; + let party = getParty(key); + if (!party || !getPlayerFlag(me.gid, party.gid, sdk.player.flag.Squelch)) { + continue; + } + + clickParty(party, sdk.party.controls.Squelch); + player.unIgnore(); + } + } + + return true; + }; + })(); + + Worker.runInBackground.ngVote = (function () { + let tick = getTickCount(); + + return function () { + if (getTickCount() - tick < 0) return true; + // check every 1 second + tick = getTickCount() + Time.seconds(1); + if (!ngVote.active) return true; + + if (ngVote.elapsed() > Time.minutes(2)) { + ngVote.checkCount(true); + if (!ngVote.nextGame) { + Chat.say("Not enough votes to start ng." + ngVote.stats()); + ngVote.reset(); + } + } else if (ngVote.elapsed() > Time.seconds(30) && !ngVote.nextGame) { + ngVote.checkCount(); + } + + return true; + }; + })(); + + /** @constructor */ + function PlayerTracker () { + this.firstCmd = getTickCount(); + this.commands = 0; + this.ignored = false; + this.ignoredAt = 0; + this.toldToChill = false; + this.seenHelpMsg = false; + this.lastChant = 0; + } + + PlayerTracker.prototype.resetCmds = function () { + this.firstCmd = getTickCount(); + this.commands = 0; + }; + + /** @param {string} nick */ + PlayerTracker.prototype.ignore = function (nick) { + let party = getParty(nick); + if (!party || getPlayerFlag(me.gid, party.gid, sdk.player.flag.Squelch)) { + return; + } + + clickParty(party, sdk.party.controls.Squelch); + + this.ignored = true; + this.ignoredAt = getTickCount(); + }; + + PlayerTracker.prototype.unIgnore = function () { + this.ignored = false; + this.ignoredAt = 0; + this.commands = 0; + }; + + PlayerTracker.prototype.reChant = function () { + return getTickCount() - this.lastChant >= chantDuration - Time.minutes(1); + }; + + PlayerTracker.prototype.updateChantTracker = 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 playerTracker = new Map(); + /** @type {Map} */ + const wpNicks = new Map(); + + /** @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)) { + if (!playerTracker.has(unit.name)) { + playerTracker.set(unit.name, new PlayerTracker()); + } + playerTracker.get(unit.name).updateChantTracker(); + } + } + } while (unit.getNext()); + } else { + Chat.say("I don't see you"); + } + + let monster = Game.getMonster(); + + if (monster) { + do { + if (!monster.isEnchantable) { + continue; + } + // merc or any other owned unit + let parent = monster.getParent(); + if (!parent) continue; + if (parent.name === nick) { + Packet.enchant(monster); + delay(500); + } + } while (monster.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 { + try { + 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) + || (playerTracker.has(unit.name) && playerTracker.get(unit.name).reChant()) + ) { + Packet.enchant(unit); + if (Misc.poll(() => unit.getState(sdk.states.Enchant), 500, 50)) { + chanted.push(unit.name); + if (!playerTracker.has(unit.name)) { + playerTracker.set(unit.name, new PlayerTracker()); + } + playerTracker.get(unit.name).updateChantTracker(); + } + } + } catch (err) { + console.error(err); + } + } while (unit.getNext()); + } + + let monster = Game.getMonster(); + + if (monster) { + do { + try { + if (monster.getParent() + && monster.isEnchantable + && Misc.inMyParty(monster.getParent().name) + && playerTracker.has(monster.getParent().name) + && !monster.getState(sdk.states.Enchant) + && monster.distance <= 40) { + Packet.enchant(monster); + // not going to re-enchant the minions for now though, will think on how best to handle that later + if (Misc.poll(() => monster.getState(sdk.states.Enchant), 500, 50)) { + chanted.push(monster.name); + } + } + } catch (err) { + console.error(err); + } + } while (monster.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++) { + if (wirt) { + 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; + } + + log("starting cows"); + + 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) { + log("Failed to get tome"); + return false; + } + + let openedStash = false; + + for (let i = 0; i < 3; i++) { + if (Town.openStash()) { + openedStash = true; + + break; + } + } + + if (!openedStash) { + log("Failed to open stash"); + return false; + } + + const cubeItems = me.getItemsEx(-1, sdk.items.mode.inStorage).filter(function (item) { + return ( + item.isInCube + && item.classid !== sdk.items.quest.WirtsLeg + && item.classid !== sdk.items.TomeofTownPortal + ); + }); + + if (cubeItems.length > 0 && !Cubing.emptyCube()) { + log("Failed to empty cube"); + return false; + } + + if (!Storage.Cube.MoveTo(leg) || !Storage.Cube.MoveTo(tome)) { + log("Failed to move items to cube"); + return false; + } + + if (!Cubing.openCube()) { + log("Failed to open cube"); + 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) { + let stop = false; + /** + * @param {string} who + * @param {string} msg + */ + const stopWatcher = function (who, msg) { + if (who !== nick) return; + if (msg === "stop" || msg === "abort" || msg === "cancel") { + stop = true; + } + }; + + 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; + + addEventListener("chatmsg", stopWatcher); + Pather.useWaypoint(areaId, true); + if (Config.ControlBot.Wps.SecurePortal) { + Attack.securePosition(me.x, me.y, { range: 20, duration: 1000 }); + } + Pather.makePortal(); + Chat.say(getAreaName(me.area) + " TP up"); + + if (!Misc.poll(() => (stop || Game.getPlayer(nick)), Time.seconds(30), Time.seconds(1))) { + Chat.say(nick + " didn't show up. 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; + } finally { + removeEventListener("chatmsg", stopWatcher); + } + }; + + /** + * @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" || msg === "abort" || msg === "cancel") { + 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; + Chat.say("Giving wps for act " + act); + + 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, { range: 20, duration: 1000 }); + } + Pather.makePortal(); + Chat.say(getAreaName(me.area) + " TP up"); + + if (!Misc.poll(() => (Game.getPlayer(nick) || next || stop), Time.seconds(30), Time.seconds(1))) { + Chat.say(nick + " didn't show up. Aborting wp giving."); + + break; + } + next = false; + + Misc.poll(() => next || stop, Time.seconds(5), 500); + } 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 (!playerTracker.has(nick)) { + playerTracker.set(nick, new PlayerTracker()); + } + const player = playerTracker.get(nick); + + // with new flooder worker we shouldn't get here unless it failed + if (player.ignored) { + return true; + } + + player.commands += 1; + + if (getTickCount() - player.firstCmd < Time.seconds(10)) { + if (player.commands > 5) { + Chat.say("spamming gets you nonwhere but a timeout, enjoy a minute of being ignored"); + player.ignore(nick); + return true; + } + } 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) && me.inTown && 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"); + } + } + }; + + const dropTrollGold = function (nick) { + try { + if (givenGold.has(nick)) { + throw new ScriptError("Already gifted you this game. Don't be greedy."); + } + + let unit = Game.getPlayer(nick); + + if (unit && unit.distance > 5) { + 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(1); + /** @type {ItemUnit} */ + let droppedGold = Misc.poll(function () { + let _gold = Game.getItem(sdk.items.Gold); + if (_gold && _gold.onGroundOrDropping && _gold.getStat(sdk.stats.Gold) === 1) { + 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"); + } + } + }; + + /** + * Finds commands that closely match the input + * @param {string} input - User entered command + * @returns {string[]} - Array of matching command suggestions + */ + function findSimilarCommands(input) { + if (!input || input.length < 2) return []; + + let matches = []; + + for (let [key, value] of actions) { + if (!value.desc) continue; + + if (key.startsWith(input)) { + matches.push(key); + } + } + + if (matches.length > 0) { + return matches; + } + + // check for typos (commands with at most 1 character different) + if (input.length >= 3) { + for (let [key, value] of actions) { + if (!value.desc) continue; + + // Check for similar commands with at most 1 character different + if (Math.abs(key.length - input.length) <= 1) { + let diffCount = 0; + + for (let j = 0; j < Math.min(key.length, input.length); j++) { + if (key[j] !== input[j]) diffCount += 1; + if (diffCount > 1) break; + } + + // Account for length difference as well + diffCount += Math.abs(key.length - input.length); + + // Match with at most 1 character different + if (diffCount <= 1) { + matches.push(key); + } + } + } + } + + return matches; + } + + /** + * @param {string} nick + * @param {string} msg + * @returns {boolean} + */ + function chatEvent (nick, msg) { + if (!nick || !msg) return; + if (nick === me.name) return; + /** + * @param {string} input + */ + const cleanMsg = function (input) { + return input + .replace(/[\'\<\>\[\]\{\}\(\)\!\@\#\$\%\^\&\*\_\+\=\|\~\`\;\:\"\?\,\.\/\\]|plz|please/g, "") + .toLowerCase() + .trim(); + }; + const full = cleanMsg(msg); + const denRegex = /^\b(den|den ?of ?evil)\b/; + const forgeRegex = /^\b(forge|hell ?forge)\b/; + let chatCmd = full; + + if (chatCmd === "givewp") { + Chat.say("givewp must be used with an area, i.e givewp cold plains"); + + return; + } + + if (ngVote.active) { + if (chatCmd === "yes" || chatCmd === "no") { + Chat.say("Were you trying to vote? Type ngyes or ngno instead."); + return; + } + } + + if (chatCmd.match(/^rush /gi)) { + chatCmd = chatCmd.split(" ")[1]; + } else if (chatCmd.match(/^givewp /gi)) { + chatCmd = chatCmd.slice(0, 6).trim(); + } else if (chatCmd.match(/^cancel /gi)) { + chatCmd = chatCmd.slice("cancel ".length); + + if (chatCmd === running.command && String.isEqual(nick, running.nick)) { + Chat.say("Can't cancel the active action"); + + return; + } + + if (chatCmd === "ngvote" && ngVote.active) { + Chat.say("Can't cancel ngvote, it is already active. Cast your vote instead with ngyes/ngno"); + return; + } + + if (commandAliases.has(chatCmd)) { + chatCmd = commandAliases.get(chatCmd); + } + + const cmdIndex = queue.findIndex(function (item) { + const [cmd, commander] = item; + if (!String.isEqual(nick, commander)) { + return false; + } + + return String.isEqual(chatCmd, cmd); + }); + + if (cmdIndex !== -1) { + Chat.say("Removing " + chatCmd + " from the queue"); + queue.splice(cmdIndex, 1); + + return; + } + return; + } + + if (denRegex.test(chatCmd) || forgeRegex.test(chatCmd)) { + Chat.say(chatCmd + " is not one of the commands"); + + return; + } + + if (chatCmd === "cancel") { + Chat.say("Cancel must be used with a command, i.e cancel andy"); + + return; + } + + if (commandAliases.has(chatCmd)) { + chatCmd = commandAliases.get(chatCmd); + } + + if ((chatCmd.match(/^drop/gi) || chatCmd.match(/^give ?gold$/)) && !Config.ControlBot.DropGold) { + chatCmd = "troll"; + } + + if (!actions.has(chatCmd)) { + let similarCommands = findSimilarCommands(chatCmd); + + if (similarCommands.length === 1) { + Chat.whisper(nick, "Did you mean '" + similarCommands[0] + "'?"); + } else if (similarCommands.length > 1 && similarCommands.length <= 5) { + Chat.whisper(nick, "Did you mean one of these: " + similarCommands.join(", ") + "?"); + } + + return; + } + + if (me.shitList.has(nick)) { + Chat.say("No commands for the shitlisted."); + } else { + if (running.nick === nick && running.command === chatCmd) { + console.debug("Command already running. active ", running); + if (playerTracker.get(nick).toldToChill) return; + if (running.command === "wps") { + Chat.whisper(nick, "chill I'm already running wps if you want me to stop type stop"); + } else { + Chat.whisper(nick, "chill I've already started. spamming doesn't make this go faster"); + } + playerTracker.get(nick).toldToChill = true; + return; + } + if (actions.get(chatCmd).desc.toLowerCase().includes("rush")) { + if (running.command === chatCmd) { + Chat.whisper(nick, "I'm already runnning that for " + running.nick); + + return; + } + } + if (!floodCheck([chatCmd, nick])) { + if (["help", "timeleft", "ngyes", "ngno"].includes(chatCmd)) { + actions.get(chatCmd).run(nick); + return; + } + } + + if (ngVote.nextGame && running.command) { + Chat.say("Not accepting new commands, ngvote passed. ng will be made after I finish " + running.command); + + return; + } + let index = queue.findIndex(function (cmd) { + return cmd[0] === chatCmd && cmd[1] === nick; + }); + if (index > -1) { + Chat.whisper(nick, "You already requested this command. Queue position: " + (index + 1)); + } else { + if (queue.length > 1) { + let commandsToCheck = []; + + if (running.command && running.nick) { + commandsToCheck.push([running.command, running.nick]); + } + + commandsToCheck = commandsToCheck.concat(queue.slice(0, 2)); + commandsToCheck.push([chatCmd, nick]); + + const isUserHoggingQueue = commandsToCheck.every(function (item) { + return item[1] === nick; + }); + + if (isUserHoggingQueue && commandsToCheck.length >= 4) { + Chat.whisper(nick, "You are hogging the queue. Max 3 commands per user at a time."); + return; + } + } + + queue.push([chatCmd, nick, full]); + if (queue.length > 1 || running.nick !== "") { + let queueMessage = queuePositionMessages.random() + .replace(/{command}/g, chatCmd) + .replace(/{position}/g, queue.length + 1); + Chat.whisper(nick, queueMessage); + if (running.command) { + let runningMessage = (nick === running.nick ? stillRunningMessages : currentlyRunningMessages).random() + .replace(/{command}/g, running.command) + .replace(/{nick}/g, running.nick); + Chat.say(runningMessage); + } + } + } + } + } + + // eslint-disable-next-line no-unused-vars + /** + * @param {number} mode + * @param {string} param1 + * @param {string} param2 + * @param {string} name1 + * @param {string} name2 + */ + function gameEvent (mode, param1, param2, name1, name2) { + switch (mode) { + case 0x02: // "%Name1(%Name2) joined our world. Diablo's minions grow stronger." + if (name1 && ngVote.active) { + ngVote.votes.set(name1, "undecided"); + } + if (name1 && !playerTracker.has(name1)) { + playerTracker.set(name1, new PlayerTracker()); + } + if (name2) { + players.set(name1, "*" + name2); + } else { + players.set(name1, ""); + } + + try { + // autosqelch shitlisted players + if (me.shitList.has(name1)) { + clickParty(getParty(name1), sdk.party.controls.Squelch); + } else { + // Handle greeting new players + Chat.say("Welcome, " + name1 + "! For a list of commands say help"); + } + } catch (err) { + console.error(err); + } + + 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.votes.delete(name1); + } + + break; + } + } + + /** + * @typedef {Object} Action + * @property {string} desc + * @property {boolean} hostileCheck + * @property {boolean} [complete] + * @property {function(): void} [markAsComplete] + * @property {function(): boolean | void} run + * @property {string} type + */ + /** @type {Map boolean} run + */ + function RushAction (desc, run) { + this.desc = desc; + this.hostileCheck = true; + this.complete = false; + this.completedBy = ""; + this.run = run; + this.type = "rush"; + } + /** @param {string} who */ + RushAction.prototype.markAsComplete = function (who) { + this.complete = true; + this.completedBy = who; + }; + /** @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 === "cancel" || key === "givewp") + ? 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); + + !playerTracker.has(nick) && playerTracker.set(nick, new PlayerTracker()); + if (playerTracker.has(nick) && playerTracker.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); + }); + } + playerTracker.get(nick).seenHelpMsg = true; + } + }); + _actions.set("cancel", { + desc: "cancel ", + hostileCheck: false, + run: () => {} + }); + _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." : ".") + ); + } + }); + + if (Config.ControlBot.NGVoting) { + _actions.set("ngvote", { + desc: "Vote for next game", + hostileCheck: false, + run: function (nick) { + if (ngVote.active) { + Chat.say("NGVote is already active. Type ngyes/ngno to vote. Current count: " + ngVote.stats()); + return; + } + const { MinGameLength, NGVoteCooldown } = Config.ControlBot; + if (getTickCount() - startTime < Time.minutes(MinGameLength)) { + Chat.say( + "Can't vote for ng yet. Must be in game for at least " + MinGameLength + " minutes. Remaining: " + + Math.round((Time.minutes(MinGameLength) - (getTickCount() - startTime)) / 1000) + " seconds." + ); + return; + } + if (nick === ngVote.lastVotePeriod.startedBy && ngVote.timeSinceLastVote() < Time.minutes(NGVoteCooldown)) { + Chat.say( + "You can't vote for ng yet. Last vote was less than " + NGVoteCooldown + " minutes ago. Remaining: " + + Math.round((Time.minutes(NGVoteCooldown) - (ngVote.timeSinceLastVote())) / 1000) + " seconds." + ); + return; + } + ngVote.begin(nick); + ngVote.vote(nick, "yes"); + const partyCount = Misc.getPartyCount(); + const votesNeeded = ngVote.votesNeeded(); + + if (partyCount === 1) { + Chat.say(nick + " since you're the only player in party, skipping wait period. NG"); + ngVote.nextGame = true; + } else { + 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; + if (ngVote.votes.get(nick) === "yes") { + Chat.say(alreadyCountedMessages.random()); + return; + } + ngVote.vote(nick, "yes"); + let { undecided } = ngVote.count(); + if (undecided > 0) { + let message = thankYouMessages.random() + .replace("{name}", nick) + .replace("{stats}", ngVote.stats()); + Chat.say(message); + } else { + let message = voteCompletionMessages.random().replace("{name}", nick); + Chat.say(message); + } + ngVote.checkCount(); + } + }); + _actions.set("ngno", { + desc: "", + hostileCheck: false, + run: function (nick) { + if (!ngVote.active) return; + if (ngVote.votes.get(nick) === "no") { + Chat.say(alreadyCountedMessages.random()); + return; + } + ngVote.vote(nick, "no"); + let { undecided } = ngVote.count(); + if (undecided > 0) { + let message = thankYouMessages.random() + .replace("{name}", nick) + .replace("{stats}", ngVote.stats()); + Chat.say(message); + } else { + let message = voteCompletionMessages.random().replace("{name}", nick); + Chat.say(message); + } + ngVote.checkCount(); + } + }); + } + + if (Config.ControlBot.DropGold) { + _actions.set("dropgold", { + desc: "Drop 5k gold", + hostileCheck: false, + run: dropGold + }); + } else { + _actions.set("troll", { + desc: "", + hostileCheck: false, + run: dropTrollGold + }); + } + + 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 ", + 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.Cain) { + _actions.set("cain", new RushAction("Rush Cain", cain)); + } + 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"], + ["malus", "smith"], + ["radament", "rada"], + ["amulet", "amu"], + ["ammy", "amu"], + ["duriel", "duri"], + ["dury", "duri"], + ["talrasha", "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.findPlayer(nick)) { + Chat.say("Seems " + nick + " left? Skipping " + cmd); + return false; + } + + 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) { + let cleanedAreaName = areaName.replace(/[<>\[\]{}()]/g, "").trim(); + /** @param {AreaDataObj} area */ + const areaFilter = function (area) { + return area.Waypoint !== 255 && area.Index !== sdk.areas.HallsofPain; + }; + /** @type {AreaDataObj} */ + let area = AreaData.findByName(cleanedAreaName, areaFilter); + if (area.Waypoint === 255) { + Chat.say(area.LocaleString + " isn't a valid wp area to ask for"); + + return false; + } else if (area.Index === sdk.areas.HallsofPain) { + return false; + } + + running.nick = nick; + running.command = cmd + " " + area.Index; + 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) { + Town.getDistance("stash") > 8 && Town.move("stash"); + + 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.split(" ")[0]).desc.includes("Rush")) { + console.log("Disabling " + running.command + " from actions"); + actions.get(running.command).markAsComplete(running.nick); + } + playerTracker.get(running.nick).toldToChill = false; + } + } + } 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 (getTickCount() - startTime >= maxTime || ngVote.nextGame) { + if (Config.ControlBot.EndMessage) { + Chat.say(Config.ControlBot.EndMessage); + } + delay(2000); + + 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, + /** + * @param {ControlBotContext} ctx + */ + cleanup: function (ctx) { + ctx.cleanup(); + } + } +); diff --git a/d2bs/kolbot/libs/scripts/Corpsefire.js b/d2bs/kolbot/libs/scripts/Corpsefire.js new file mode 100644 index 000000000..2101d5597 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Corpsefire.js @@ -0,0 +1,28 @@ +/** +* @filename Corpsefire.js +* @author kolton, theBGuy +* @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.moveToPresetMonster(sdk.areas.DenofEvil, sdk.monsters.preset.Corpsefire, { pop: 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..f432265eb --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Cows.js @@ -0,0 +1,159 @@ +/** +* @filename Cows.js +* @author kolton, theBGuy +* @desc clear the Moo Moo Farm without killing the Cow King +* +*/ + +const Cows = new Runnable( + function Cows () { + 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..2bd243c0d --- /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)) { + console.log("GetGame: " + obj.profile); + sendCopyData(null, obj.profile, 4, me.gamename + "/" + me.gamepassword); + + gameRequest = true; + } + + break; + case "GetSetInfo": + if (info.Collectors.includes(obj.profile)) { + console.log("GetSetInfo: " + obj.profile); + + rval = []; + + for (let i = 0; i < info.Sets.length; i += 1) { + rval.push(info.Sets[i].Enabled ? 1 : 0); + } + + console.log(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 + console.log("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 + console.log("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) { + console.log("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)) { + + //console.log("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..21e3280ab --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Diablo.js @@ -0,0 +1,96 @@ +/** +* @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 () { + 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..f688d9d35 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/DiabloHelper.js @@ -0,0 +1,225 @@ +/** +* @filename DiabloHelper.js +* @author kolton, theBGuy +* @desc help leading player in clearing Chaos Sanctuary and killing Diablo +* +*/ + +const DiabloHelper = new Runnable( + function DiabloHelper () { + Common.Diablo.waitForGlow = true; + Common.Diablo.clearRadius = Config.DiabloHelper.ClearRadius; + const Worker = require("../modules/Worker"); + + let leader = Config.Leader; + + const leaderTracker = (function (ignoreDelay = false) { + let leadTick = getTickCount(); + + return function () { + if (Common.Diablo.done) return false; + // check every 3 seconds + if (!ignoreDelay && 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 ScriptError("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)) { + throw new ScriptError("Leader is running baal"); + } + } while (party.getNext()); + } + + return true; + }; + })(); + + const waitForPortal = function () { + if (!leader) { + 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)) { + console.error("Player wait timed out (" + (Config.Leader ? "No leader" : "No player") + " portals found)"); + throw new ScriptError("Player wait timed out"); + } + }; + + try { + Common.Diablo.addLightsEventListener(); + + if (Config.DiabloHelper.SkipIfBaal) { + // start tracking leader - run once manually to initialize + leaderTracker(); + Worker.runInBackground.leaderTracker = leaderTracker; + } + + let portalCheck = Pather.getPortal(sdk.areas.ChaosSanctuary, Config.Leader || null); + if (portalCheck) { + console.log("Found portal to Chaos Sanctuary"); + } + + try { + Common.Diablo.diaSpawnWatcher(); + Worker.runInBackground.diaSpawned = Common.Diablo.diaSpawnWatcher(); + + Config.DiabloHelper.SafePrecast && Precast.needOutOfTownCast() + ? Precast.doRandomPrecast( + true, + (Config.DiabloHelper.SkipTP && !portalCheck) ? sdk.areas.RiverofFlame : sdk.areas.PandemoniumFortress + ) + : Precast.doPrecast(true); + + if (Config.DiabloHelper.SkipTP && !portalCheck) { + !me.inArea(sdk.areas.RiverofFlame) && Pather.useWaypoint(sdk.areas.RiverofFlame); + + if (!Pather.moveTo(7790, 5544)) throw new ScriptError("Failed to move to Chaos Sanctuary"); + !Config.DiabloHelper.Entrance && Pather.moveTo(7774, 5305); + + if (!Misc.poll(() => { + let party = getParty(); + + if (party) { + do { + if ((!leader || party.name === leader) + && party.area === sdk.areas.ChaosSanctuary) { + return true; + } + } while (party.getNext()); + } + + Attack.clear(30, 0, false, Common.Diablo.sort); + + if (Misc.getPlayerCount() <= 1) { + throw new ScriptError("Empty game"); + } + + return false; + }, Time.minutes(Config.DiabloHelper.Wait), 1000)) { + console.error("Player wait timed out (" + (Config.Leader ? "Leader not" : "No players") + " found in Chaos)"); + throw new ScriptError("Player wait timed out"); + } + } else { + Town.goToTown(4); + Town.move("portalspot"); + waitForPortal(); + } + } catch (e) { + if (e instanceof ScriptError) { + if (e.message === "Empty game" || e.message === "Leader is running baal" || e.message === "Player wait timed out") { + throw e; + } + if (e.message === "Failed to move to Chaos Sanctuary") { + Town.goToTown(4); + Town.move("portalspot"); + waitForPortal(); + } + if ((e.message === "Diablo spawned")) { + Town.goToTown(4); + Town.move("portalspot"); + if (!Pather.usePortal(sdk.areas.ChaosSanctuary, Config.Leader || null)) { + throw new Error("Failed to use portal to Chaos Sanctuary after Diablo spawn"); + } + } + } else { + console.error(e); + } + } + + Common.Diablo.initLayout(); + + try { + Common.Diablo.diaSpawnWatcher(); + + 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(leader))) { + throw new ScriptError("Leader is running baal"); + } + return false; + }, Time.minutes(2), 500); + } catch (e) { + if (e instanceof ScriptError) { + if (e.message === "Empty game" || e.message === "Leader is running baal") { + throw e; + } + } + console.error(e); + } + + 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) { + // + } + } + } + + Config.DiabloHelper.HurtDiablo > 0 + ? Attack.hurt(sdk.monsters.Diablo, Config.DiabloHelper.HurtDiablo) + : Attack.kill(sdk.monsters.Diablo); + Pickit.pickItems(); + } catch (e) { + console.error(e); + } finally { + delete Worker.runInBackground.leaderTracker; + delete Worker.runInBackground.diaSpawned; + + Common.Diablo.done = true; + Common.Diablo.removeLightsEventListener(); + } + + 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..900da32c8 --- /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[me.classid].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..3e83d7358 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Follower.js @@ -0,0 +1,1042 @@ +/** +* @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.MaggotLairLvl3); + 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); + } + } + // Uber portals + const uberPortals = [ + sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain, sdk.areas.UberTristram + ]; + if (me.inArea(sdk.areas.Harrogath) && uberPortals.includes(area)) { + Town.moveToSpot("stash"); + let uberPortal = Pather.getPortal(area); + if (uberPortal) { + announce("Special transit to " + getAreaName(area)); + return Pather.usePortal(null, null, uberPortal); + } + } + 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..639b4323e --- /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) { + console.log("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..b0c86314c --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GemHunter.js @@ -0,0 +1,99 @@ +/** +* @filename GemHunter.js +* @author icommitdesnet, theBGuy +* @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 () { + if (!Town.prepareForGemShrine()) { + console.log("ÿc4GemHunterÿc0: no gems in inventory - aborting."); + return false; + } + + /** @param {number} area */ + const findGemShrines = function (area) { + const shrineLocs = []; + const units = Game.getPresetObjects(area) + .filter(function (preset) { + return sdk.shrines.Presets.includes(preset.id); + }); + + if (units.length) { + for (let shrine of units) { + shrineLocs.push(shrine.realCoords()); + } + } + + try { + NodeAction.shrinesToIgnore.push(sdk.shrines.Gem); + + while (shrineLocs.length > 0) { + shrineLocs.sort(Sort.units); + let coords = shrineLocs.shift(); + + Pather.move(coords, { minDist: Skill.haveTK ? 20 : 5, callback: () => { + let shrine = Game.getObject("shrine"); + return !!shrine && shrine.x === coords.x && shrine.y === coords.y; + } }); + + let shrine = Game.getObject("shrine"); + + if (shrine) { + do { + if (shrine.objtype === sdk.shrines.Gem && shrine.mode === sdk.objects.mode.Inactive) { + (!Skill.haveTK || !use) && Pather.moveTo(shrine.x - 2, shrine.y - 2); + + console.log("ÿc4GemHunterÿc0: found a gem Shrine"); + if (Misc.getShrine(shrine)) { + Pickit.pickItems(); + + if (!Town.prepareForGemShrine()) { + console.error("ÿc4GemHunterÿc0: failed to prepare for next shrine - aborting."); + return false; + } + } + } + } while (shrine.getNext()); + } + } + + return true; + } finally { + NodeAction.shrinesToIgnore.remove(sdk.shrines.Gem); + } + }; + + for (let area of Config.GemHunter.AreaList) { + console.log("ÿc4GemHunterÿc0: Moving to " + getAreaName(area)); + Pather.journeyTo(area); + Precast.doPrecast(false); + + if (!findGemShrines(area)) { + console.debug("ending early"); + break; + } + } + return true; + }, + { + preAction: function (ctx) { + ctx.preGidList = new Set(Town.dontStashGids); + }, + cleanup: function (ctx) { + for (let gid of Town.dontStashGids) { + if (!ctx.preGidList.has(gid)) { + Town.dontStashGids.delete(gid); + } + } + } + } +); 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..e808c30cc --- /dev/null +++ b/d2bs/kolbot/libs/scripts/IPHunter.js @@ -0,0 +1,65 @@ +/** +* @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)) { + require("../modules/workers/AntiIdle"); + + 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(NPC.Akara, false); + Town.move("stash", false); + } 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..e6842f6e8 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Idle.js @@ -0,0 +1,100 @@ +/** +* @filename Idle.js +* @author theBGuy +* @desc Idle companion script +* +*/ + +/** + * @typedef {ScriptContext & { cleanup: () => void }} IdleContext + */ + +const Idle = new Runnable( + /** + * @param {IdleContext} ctx + */ + function Idle (ctx) { + const greet = []; + + /** + * @param {number} mode + * @param {number} param1 + * @param {number} param2 + * @param {string} name1 + * @param {string} name2 + */ + function gameEvent (mode, param1, param2, name1, _name2) { + switch (mode) { + case 0x02: // "%Name1(%Name2) joined our world. Diablo's minions grow stronger." + // idle in town + if (me.inTown && me.mode === sdk.player.mode.StandingInTown) { + greet.push(name1); + } + break; + } + } + + ctx.cleanup = function () { + removeEventListener("gameevent", gameEvent); + }; + + const startTime = getTickCount(); + // send anti-idle packet every ~20 minutes + let idleTick = getTickCount() + Time.seconds(rand(1200, 1500)); + // move to waypoint/stash every 10-12 minutes + let moveTick = getTickCount() + Time.seconds(rand(600, 720)); + + 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()) + ")"); + } + + if (getTickCount() - moveTick > 0) { + if (me.inTown && me.mode === sdk.player.mode.StandingInTown) { + Town.move("waypoint"); + Town.move("stash"); + } + moveTick += Time.seconds(rand(600, 720)); + console.log("Moved to waypoint/stash, next move in: (" + Time.format(moveTick - getTickCount()) + ")"); + } + + delay(1000); + } + + return true; + }, + { + startArea: sdk.areas.RogueEncampment, + preAction: null, + /** + * @param {IdleContext} ctx + */ + cleanup: function (ctx) { + ctx.cleanup(); + } + } +); 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..7ccc20dd7 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/MFHelper.js @@ -0,0 +1,305 @@ +/** +* @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)) { + 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..b39f9d11d --- /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[me.classid].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) && Misc.openChests(5); + Pather.moveTo(17572, 8125) && Misc.openChests(5); + Pather.moveTo(17515, 8061) && Misc.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..cbf62dcf1 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/OrgTorch.js @@ -0,0 +1,689 @@ +/** +* @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 () { + if (Config.OrgTorch.UseSalvation) { + Config.AdvancedCustomAttack.push({ + check: function (unit) { + return ( + unit.classid === sdk.monsters.UberMephisto + || (me.inArea(sdk.areas.UberTristram) && Game.getMonster(sdk.monsters.UberMephisto)) + ); + }, + attack: [Config.AttackSkill[1], sdk.skills.Salvation] + }); + } + + Config.MFLeader = true; + let currentGameInfo = null; + /** @type {Player | null} */ + let taxiPlayer = null; + let taxiUp = false; + + const OrgTorchData = require("../systems/torch/OrgTorchData"); + const portalMode = { + MiniUbers: 0, + UberTristram: 1 + }; + + /** + * @param {string} nick + * @param {string} msg + */ + function chatEvent (nick, msg) { + if (!nick || !msg) return; + if (msg !== "up") return; + + if (!taxiPlayer && String.isEqual(Config.OrgTorch.TaxiChar, nick)) { + taxiPlayer = Misc.findPlayer(nick); + } + + if (taxiPlayer && taxiPlayer.name === nick) { + taxiUp = true; + console.log("ÿc7OrgTorch :: Taxi is up"); + } + } + + /** + * @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); + + // 25121/5157 - bottom left corner of top left building + // 25141/5175 - top left corner of bottom left building + // 25131/5187 - area we want to lure meph to + if (unit && !unit.dead) { + 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 () { + const organs = new Map([ + [sdk.items.quest.DiablosHorn, 0], + [sdk.items.quest.MephistosBrain, 0], + [sdk.items.quest.BaalsEye, 0] + ]); + /** @param {ItemUnit} item */ + const filterInvo = function (item) { + return item.isInStorage && organs.has(item.classid) && item.normal; + }; + /** @param {ItemUnit} item */ + const countOrgans = function (item) { + if (organs.has(item.classid)) { + organs.set(item.classid, organs.get(item.classid) + 1); + } + }; + me.getItemsEx() + .filter(filterInvo) + .forEach(countOrgans); + + const horns = organs.get(sdk.items.quest.DiablosHorn); + const brains = organs.get(sdk.items.quest.MephistosBrain); + const eyes = organs.get(sdk.items.quest.BaalsEye); + + 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) return true; + if (me.getState(sdk.states.Fade)) return true; + + // lets figure out what fade item we have before we leave town + 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.have) { + console.log(sdk.colors.Orange + "OrgTorch :: " + sdk.colors.White + "Getting Fade"); + + Pather.useWaypoint(sdk.areas.RiverofFlame); + Precast.doPrecast(true); + // check if item is on switch + let mainSlot; + + Pather.moveTo(7811, 5872); + + if (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); + + const nodes = [ + new PathNode(20196, 8694), + new PathNode(20308, 8588), + new PathNode(20187, 8639), + new PathNode(20100, 8550), + new PathNode(20103, 8688), + new PathNode(20144, 8709), + new PathNode(20263, 8811), + new PathNode(20247, 8665), + ]; + + const foundDuriel = function () { + return !!Game.getMonster(sdk.monsters.UberDuriel); + }; + + try { + for (let node of nodes) { + Pather.move(node, { callback: foundDuriel }); + 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; + + const foundIzual = function () { + return !!Game.getMonster(sdk.monsters.UberIzual) || Attack.haveKilled(sdk.monsters.UberIzual); + }; + Precast.doPrecast(true); + Pather.moveToPresetObject( + sdk.areas.FurnaceofPain, + sdk.objects.SmallSparklyChest, + { offX: 2, offY: 2, callback: foundIzual } + ); + 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 () { + Pather.moveTo(25068, 5078); + + if (Precast.checkCTA() && !me.getState(sdk.states.BattleOrders) && Misc.getNearbyPlayerCount() === 0) { + Config.UseCta = true; // override + } + Precast.doPrecast(true); + + const nodes = [ + new PathNode(25040, 5101), + new PathNode(25040, 5166), + new PathNode(25131, 5187), + // new PathNode(25122, 5170), + new PathNode(25131, 5187), + ]; + + for (let node of nodes) { + Pather.move(node, { callback: function () { + let meph = Game.getMonster(sdk.monsters.UberMephisto); + let diablo = Game.getMonster(sdk.monsters.UberDiablo); + let baal = Game.getMonster(sdk.monsters.UberBaal); + return ( + (meph && meph.distance < 10) + || (diablo && diablo.distance < 10) + || (baal && baal.distance < 10) + ); + } }); + } + + lure(sdk.monsters.UberMephisto, 25131, 5187); + + if (!Game.getMonster(sdk.monsters.UberMephisto)) { + Pather.moveTo(25122, 5170); + } + + Attack.kill(sdk.monsters.UberMephisto); + + Pather.move(new PathNode(25162, 5141), { callback: function () { + return Attack.haveKilled(sdk.monsters.UberDiablo); + } }); + + const uberDiableCB = function () { + if (Attack.haveKilled(sdk.monsters.UberDiablo)) { + return true; + } + let diablo = Game.getMonster(sdk.monsters.UberDiablo); + return (diablo && diablo.distance < 10) || diablo.dead; + }; + Misc.poll(uberDiableCB, 3250, 50); + + if (!Game.getMonster(sdk.monsters.UberDiablo)) { + Pather.move(new PathNode(25122, 5170), { callback: uberDiableCB }); + } + + Attack.kill(sdk.monsters.UberDiablo); + + if (!Game.getMonster(sdk.monsters.UberBaal)) { + Pather.move(new PathNode(25122, 5170), { callback: function () { + if (Attack.haveKilled(sdk.monsters.UberBaal)) { + return true; + } + let baal = Game.getMonster(sdk.monsters.UberBaal); + return (baal && baal.distance < 10) || baal.dead; + } }); + } + + 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) return; + const portalArea = portal.objtype; + 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); + } + say("Starting " + portal.objtype); + + if (Config.OrgTorch.TaxiChar && portalArea !== sdk.areas.UberTristram) { + Town.move("portalspot"); + + const taxiReady = function () { + return taxiUp && Pather.usePortal(portalArea, taxiPlayer.name); + }; + + if (Misc.poll(taxiReady, Time.minutes(1), 1000)) { + taxiUp = false; + console.log("taking portal: " + portalArea); + pandemoniumRun(portalArea); + + return; + } else { + console.log("OrgTorch :: Taxi not ready, taking red portal."); + Town.move("stash"); + } + } else { + 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); + + OrgTorchData.exists() && (currentGameInfo = OrgTorchData.read()); + + if (!currentGameInfo || currentGameInfo.gamename !== me.gamename) { + currentGameInfo = OrgTorchData.create(); + } + + let portal; + + const ingredients = new Map([ + [sdk.items.quest.KeyofTerror, 0], + [sdk.items.quest.KeyofHate, 0], + [sdk.items.quest.KeyofDestruction, 0], + [sdk.items.quest.DiablosHorn, 0], + [sdk.items.quest.MephistosBrain, 0], + [sdk.items.quest.BaalsEye, 0] + ]); + + const checkIngredients = function () { + /** @param {ItemUnit} item */ + const filterIngredients = function (item) { + return item.isInStorage && ingredients.has(item.classid) && item.normal; + }; + /** @param {ItemUnit} item */ + const countIngredients = function (item) { + if (ingredients.has(item.classid)) { + ingredients.set(item.classid, ingredients.get(item.classid) + 1); + } + }; + + me.getItemsEx() + .filter(filterIngredients) + .forEach(countIngredients); + }; + + // Initialize our ingredients map + checkIngredients(); + + // 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"); + + const uberPortals = [ + sdk.areas.MatronsDen, + sdk.areas.ForgottenSands, + sdk.areas.FurnaceofPain, + sdk.areas.UberTristram + ]; + const redPortals = getUnits(sdk.unittype.Object, sdk.objects.RedPortal) + .filter(function (el) { + return uberPortals.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()); + } + + /** + * @param {number} req + * @returns {boolean} + */ + const validKeyCount = function (req) { + return ingredients.get(sdk.items.quest.KeyofTerror) >= req + && ingredients.get(sdk.items.quest.KeyofHate) >= req + && ingredients.get(sdk.items.quest.KeyofDestruction) >= req; + }; + + const validOrganCount = function () { + return ingredients.get(sdk.items.quest.DiablosHorn) >= 1 + && ingredients.get(sdk.items.quest.MephistosBrain) >= 1 + && ingredients.get(sdk.items.quest.BaalsEye) >= 1; + }; + + // End the script if we don't have enough keys nor organs + if (!validKeyCount(keySetsReq) && !validOrganCount() && !tristOpen) { + console.log("Not enough keys or organs."); + OrgTorchData.remove(); + + return true; + } + + Config.UseMerc = false; + + // We have enough keys, do mini ubers + if (validKeyCount(keySetsReq)) { + getFade(); + Town.goToTown(5); + console.log("Making organs."); + D2Bot.printToConsole("OrgTorch: Making organs.", sdk.colors.D2Bot.DarkGold); + Town.move("stash"); + + if (Config.OrgTorch.TaxiChar) { + addEventListener("chatmsg", chatEvent); + + if (!taxiPlayer) { + taxiPlayer = Misc.findPlayer(Config.OrgTorch.TaxiChar); + } + } + + // 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 + checkIngredients(); + + // 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 (validOrganCount() || 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/OrgTorchHelper.js b/d2bs/kolbot/libs/scripts/OrgTorchHelper.js new file mode 100644 index 000000000..5c1f1d933 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/OrgTorchHelper.js @@ -0,0 +1,430 @@ +/** +* @filename OrgTorchHelper.js +* @author theBGuy +* @desc Run alongside OrgTorch to help with Uber Tristram and Uber bosses +* +*/ + + +const OrgTorchHelper = new Runnable( + function OrgTorchHelper () { + // TODO: Temp remove organs from nip so this doesn't interfere with OrgTorch + Config.FindItem = false; + + let quitting = false; + /** @type {Party} */ + let player = null; + /** @type {number} */ + let lastPrecast; + + /** @type {{ task: string, id?: number | string, at: number, area: number }[]} */ + const taskList = []; + const tasks = ["kill", "clear", "quit", "starting"]; + + /** + * @param {string} name + * @param {string} msg + */ + function chatEvent (name, msg) { + if (!msg) return; + const cmd = msg.toLowerCase().split(" "); + const task = cmd.length ? cmd.shift() : ""; + if (!tasks.includes(task)) { + return; + } + const idSplit = cmd.join(" "); + const id = (function () { + try { + return parseInt(idSplit, 10) || idSplit; + } catch (e) { + console.warn(e.message || "Failed to parse id from message split"); + return; + } + })(); + + if (!player) { + // anything else we need to consider here? + player = Misc.findPlayer(name); + } + + if (player && name === player.name) { + if (Config.OrgTorchHelper.SkipTp && me.inArea(player.area)) { + return; + } + if (Config.OrgTorchHelper.Taxi && task !== "starting") { + return; + } + if (Config.OrgTorchHelper.Taxi && id === sdk.areas.UberTristram) { + return; + } + taskList.push({ task: task, id: id, at: getTickCount(), area: player.area }); + } + } + + function handleDeath () { + while (me.mode === sdk.player.mode.Death) { + delay(3); + } + + if (me.hardcore) { + D2Bot.printToConsole( + "(OrgTorchHelper) :: " + 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!"); + } + + /** + * 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.OrgTorchHelper.GetFade) return true; + if (me.getState(sdk.states.Fade)) return true; + + // lets figure out what fade item we have before we leave town + 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.have) { + console.log(sdk.colors.Orange + "OrgTorchHelper :: " + sdk.colors.White + "Getting Fade"); + + Pather.useWaypoint(sdk.areas.RiverofFlame); + Precast.doPrecast(true); + // check if item is on switch + let mainSlot; + + Pather.moveTo(7811, 5872); + + if (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 + "OrgTorchHelper :: " + sdk.colors.Green + "Fade Achieved"); + } + + return true; + }; + + const portalUp = function () { + return Config.OrgTorchHelper.Helper ? Pather.makePortal() : Town.goToTown(); + }; + + const matronsDen = function () { + Precast.doPrecast(true); + Pather.moveToPresetObject( + sdk.areas.MatronsDen, + sdk.objects.SmallSparklyChest, + { offX: 2, offY: 2, useWalkPath: Config.OrgTorchHelper.UseWalkPath, } + ); + + if (Config.OrgTorchHelper.Taxi) { + portalUp() && say("up"); + + if (!Config.OrgTorchHelper.Helper) { + return; + } + } + // TODO: allow callback to end clearing - in this case stop if lilith is dead + Attack.clear(25, sdk.monsters.spectype.All, sdk.monsters.Lilith); + Pickit.pickItems(); + Town.goToTown(); + }; + + const forgottenSands = function () { + Precast.doPrecast(true); + + const nodes = [ + new PathNode(20196, 8694), + new PathNode(20308, 8588), + new PathNode(20187, 8639), + new PathNode(20100, 8550), + new PathNode(20103, 8688), + new PathNode(20144, 8709), + new PathNode(20263, 8811), + new PathNode(20247, 8665), + ]; + + /** @type {Monster | null} */ + let dury = null; + const foundDuriel = function () { + dury = Game.getMonster(sdk.monsters.UberDuriel); + return !!dury; + }; + + try { + for (let node of nodes) { + Pather.move(node, { useWalkPath: Config.OrgTorchHelper.UseWalkPath, callback: foundDuriel }); + delay(500); + + if (foundDuriel()) { + break; + } + } + + if (Config.OrgTorchHelper.Taxi) { + dury && Pather.move(dury); + portalUp() && say("up"); + + if (!Config.OrgTorchHelper.Helper) { + return; + } + } + Attack.clear(25, sdk.monsters.spectype.All, sdk.monsters.UberDuriel); + Pickit.pickItems(); + Town.goToTown(); + } catch (e) { + // + } + }; + + const furnance = function () { + Precast.doPrecast(true); + Pather.moveToPresetObject( + sdk.areas.FurnaceofPain, + sdk.objects.SmallSparklyChest, + { offX: 2, offY: 2, useWalkPath: Config.OrgTorchHelper.UseWalkPath, } + ); + + if (Config.OrgTorchHelper.Taxi) { + portalUp() && say("up"); + + if (!Config.OrgTorchHelper.Helper) { + return; + } + } + Attack.clear(25, sdk.monsters.spectype.All, sdk.monsters.UberIzual); + Pickit.pickItems(); + Town.goToTown(); + }; + + const uberTrist = function () { + Config.MercWatch = false; + + Pather.moveTo(25068, 5078); + Precast.doPrecast(true); + + const validIds = [ + sdk.monsters.UberDiablo, + sdk.monsters.UberMephisto, + sdk.monsters.UberBaal + ]; + + // poll for the killer to have started so we don't interfere with lure + Misc.poll(function () { + let killCmd = taskList.find(el => el.task === "kill" && validIds.includes(el.id)); + return killCmd && killCmd.at < getTickCount() - Time.minutes(3); + }, Time.seconds(15), 50); + + let nodes = [ + new PathNode(25040, 5101), + new PathNode(25040, 5166), + new PathNode(25122, 5170), + ]; + + for (let node of nodes) { + Pather.move(node); + } + + if (!Game.getMonster(sdk.monsters.UberMephisto)) { + Pather.moveTo(25122, 5170); + } + + Attack.clear(15, sdk.monsters.spectype.All, sdk.monsters.UberMephisto); + + Pather.moveTo(25162, 5141); + Misc.poll(function () { + let diablo = Game.getMonster(sdk.monsters.UberDiablo); + return diablo && (diablo.distance < 10 || diablo.dead); + }, 3250, 50); + + if (!Game.getMonster(sdk.monsters.UberDiablo)) { + Pather.moveTo(25122, 5170); + } + + Attack.clear(15, sdk.monsters.spectype.All, sdk.monsters.UberDiablo); + + if (!Game.getMonster(sdk.monsters.UberBaal)) { + Pather.moveTo(25122, 5170); + } + + Attack.clear(15, sdk.monsters.spectype.All, sdk.monsters.UberBaal); + Pather.moveTo(25105, 5140); + Pather.usePortal(sdk.areas.Harrogath); + }; + + const useLeaderPortal = function () { + return Pather.usePortal(null, player.name); + }; + + addEventListener("chatmsg", chatEvent); + + // ################# // + /* ##### START ##### */ + // ################# // + + getFade(); + Town.doChores(); + Town.goToTown(5); + 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); + } + + // START + while (!quitting) { + if (me.dead) { + handleDeath(); + } + + if (player) { + if (me.needHealing() && Town.heal()) { + Town.move("portalspot"); + } + + 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; + + // handled pre-reqs, now perform normal checks + let { task, id, at, area } = taskList.shift(); + + if (!id) { + console.warn("OrgTorchHelper :: No id found in taskList, skipping task " + task); + continue; + } + + if ((task === "kill" || task === "clear" && Attack._killed.has(id))) { + continue; + } + + // 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 + */ + if (me.area !== area) { + !me.inTown && Town.goToTown(); + + if (me.act !== sdk.areas.actOf(area)) { + Town.goToTown(sdk.areas.actOf(area)); + Town.move("portalspot"); + } + + if (player.area !== area && !player.inTown) { + area = player.area; + } + + try { + Misc.poll(useLeaderPortal, 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(5); + continue; + } + + switch (task) { + case "starting": + console.log("ÿc4OrgTorchHelperÿc0: Starting " + id); + + if (!Config.OrgTorchHelper.SkipTp && id === sdk.areas.UberTristram) { + continue; + } + + Pather.usePortal(id); + + if (me.inArea(sdk.areas.MatronsDen)) { + matronsDen(); + } else if (me.inArea(sdk.areas.FurnaceofPain)) { + furnance(); + } else if (me.inArea(sdk.areas.ForgottenSands)) { + forgottenSands(); + } else if (me.inArea(sdk.areas.UberTristram)) { + uberTrist(); + } + + break; + case "kill": + console.log("ÿc4OrgTorchHelperÿc0: Kill " + id); + + try { + Attack.kill(id); + } catch (err) { + console.error(err); + } + + break; + case "clear": + console.log("ÿc4OrgTorchHelperÿc0: Clear " + id); + + try { + Attack.clear(15, 0, id); + } catch (err) { + console.error(err); + } + + break; + } + + if (!Pather.getPortal(sdk.areas.townOf(me.act)) || !Pather.usePortal(sdk.areas.townOf(me.act))) { + Town.goToTown(); + } + } + } + + delay(100); + } + + 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..81318e939 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Questing.js @@ -0,0 +1,417 @@ +/** +* @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"); + Common.Smith(); + }; + + const cain = function () { + 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 () { + 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..4e8e7b2d8 --- /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(); + Misc.openChests(20); + + return true; + }, + { + startArea: sdk.areas.A2SewersLvl2, + bossid: sdk.monsters.Radament, + } +); diff --git a/d2bs/kolbot/libs/scripts/RaiseArmy.js b/d2bs/kolbot/libs/scripts/RaiseArmy.js new file mode 100644 index 000000000..774e64a33 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/RaiseArmy.js @@ -0,0 +1,32 @@ +/** +* @filename RaiseArmy.js +* @author theBGuy +* @desc go through pindle portal and raise an army of skeletons, then return to town +* +*/ + +const RaiseArmy = new Runnable( + function RaiseArmy () { + if (!me.necromancer || (Config.Skeletons + Config.SkeletonMages + Config.Revives === 0)) { + console.warn("This script is only meant to be used with a necromancer configured to raise an army of skeletons and/or revives."); + return true; + } + + Town.goToTown(5); + + if (!Pather.journeyTo(sdk.areas.NihlathaksTemple)) { + throw new Error("Failed to use portal."); + } + Precast.doPrecast(true); + Pather.moveTo(10053, 13278); + + ClassAttack[sdk.player.class.Necromancer].raiseArmy(); + Town.goToTown(); + + return true; + }, + { + startArea: sdk.areas.Harrogath, + bossid: getLocaleString(sdk.locale.monsters.Pindleskin), + } +); diff --git a/d2bs/kolbot/libs/scripts/Rakanishu.js b/d2bs/kolbot/libs/scripts/Rakanishu.js new file mode 100644 index 000000000..e01a0323a --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rakanishu.js @@ -0,0 +1,37 @@ +/** +* @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.moveToPresetMonster(sdk.areas.StonyField, sdk.monsters.preset.Rakanishu, { pop: true })) { + // sometime bad map seed will not allow us to find Rakanishu to use stone preset + if (!Pather.moveToPresetObject(sdk.areas.StonyField, sdk.quest.chest.StoneAlpha)) { + 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..1e3dd4b4b --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rushee.js @@ -0,0 +1,1249 @@ +/* eslint-disable max-len */ +/** +* @filename Rushee.js +* @author kolton, theBGuy +* @desc Rushee script that works with Rusher +* +*/ + + +const Rushee = new Runnable( + function Rushee () { + const Overrides = require("../modules/Override"); + const { log, getBumperLvlReq } = require("../systems/autorush/AutoRush"); + const { + RushConfig + } = require("../systems/autorush/RushConfig"); + const { sequenceCheck, RushModes, AutoRush } = require("../systems/autorush/RushConstants"); + const rushConfig = RushConfig[me.profile]; + + if (!rushConfig) { + throw new Error("No rush config found for profile: " + me.profile); + } + + function applyOverrides() { + new Overrides.Override(Town, Town.goToTown, function (orignal, act, wpmenu) { + try { + return orignal(act, wpmenu); + } catch (e) { + console.error(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) continue; + + let coords = preset.realCoords(); + if (!me.inTown && coords.distance > 15) { + return false; + } + + Skill.haveTK + ? this.moveNearUnit(coords, 20, { clearSettings: { clearPath: clearPath } }) + : this.moveToUnit(coords, 0, 0, clearPath); + + let wp = Game.getObject("waypoint"); + if (!wp) continue; + + 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(); + } + + /** + * @param {string} who + * @param {string} msg + */ + function chatEvent (who, msg) { + if (msg === "i am rusher") { + Config.Leader = who; + console.debug("Assigned Leader: " + Config.Leader); + } + if (!Config.Leader && msg.includes("questinfo")) { + Config.Leader = who; + console.debug("Assigned Leader: " + Config.Leader); + } + if (who === Config.Leader) { + // we can ignore starting messages as they are just for the rusher to log what they are doing + if (msg.toLowerCase().startsWith("starting")) return; + if (msg.toLowerCase().includes("rush complete")) { + commands.push("exit"); + } else { + console.debug("Leader: " + msg); + commands.push(msg); + } + } + } + + /** @param {number | null} area */ + const usePortal = function (area) { + return Pather.usePortal(area, Config.Leader); + }; + + const useScrollOfRes = function () { + let scroll = me.scrollofresistance; + if (scroll) { + clickItem(sdk.clicktypes.click.item.Right, scroll); + console.log("Using scroll of resistance"); + } + }; + + const revive = function () { + while (me.mode === sdk.player.mode.Death) { + nativeDelay(40); + } + + if (me.mode === sdk.player.mode.Dead) { + me.revive(); + + while (!me.inTown) { + nativeDelay(3); + } + } + }; + + // todo - map the chest to classid so we only need to pass in one value + /** + * @param {number} classid + * @param {number} chestid + */ + const getQuestItem = function (classid, chestid) { + let tick = getTickCount(); + + if (me.getItem(classid)) { + log("Already have: " + classid); + return true; + } + + if (me.inTown) return false; + + let chest = Game.getObject(chestid); + + if (!chest) { + log("Couldn't find: " + chestid); + return false; + } + + for (let i = 0; i < 5; i++) { + if (Game.getItem(classid)) { + break; + } + if (Misc.openChest(chest)) { + break; + } + 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); + }; + + const tyraelTalk = function () { + const bridgeNode = new PathNode(22577, 15609); + if (me.inArea(sdk.areas.DurielsLair) && bridgeNode.distance > 10) { + Pather.move(bridgeNode, { callback: function () { + return Game.getNPC(NPC.Tyrael); + } }); + } + 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) || usePortal(null); + }; + + const cube = (function () { + const staff = { + ingreds: [sdk.quest.item.ShaftoftheHoradricStaff, sdk.quest.item.ViperAmulet], + outcome: sdk.quest.item.HoradricStaff, + }; + const flail = { + ingreds: [ + sdk.quest.item.KhalimsFlail, sdk.quest.item.KhalimsEye, + sdk.quest.item.KhalimsBrain, sdk.quest.item.KhalimsHeart + ], + outcome: sdk.quest.item.KhalimsWill, + }; + /** @param {{ ingreds: number[], outcome: number }} item */ + const make = function (item) { + if (me.getItem(item.outcome)) return true; + const ingreds = item.ingreds.map(function (itemId) { + return me.getItem(itemId); + }); + if (!ingreds.every(i => i)) return false; + ingreds.forEach(i => Storage.Cube.MoveTo(i)); + Cubing.openCube(); + transmute(); + delay(750 + me.ping); + + let outcome = me.getItem(item.outcome); + if (!outcome) return false; + + Storage.Inventory.MoveTo(outcome); + me.cancel(); + + return true; + }; + return { + Staff: function () { + log("Making staff", Config.LocalChat.Enabled); + return make(staff); + }, + Flail: function () { + log("Making flail", Config.LocalChat.Enabled); + return make(flail); + }, + }; + })(); + + const 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; + }; + + /** @param {Act} act */ + const changeAct = function (act) { + revive(); + + const actGoal = Number(act); + if (me.act === actGoal || me.act > actGoal) return true; + const preArea = me.area; + console.debug("Changing act to: " + actGoal); + + try { + switch (actGoal) { + 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 (rushConfig.type !== RushModes.quester) { + // Talk to Atma + if (!Town.npcInteract("Atma")) { + break; + } + } + + usePortal(sdk.areas.HaremLvl1); + Pather.moveToExit(sdk.areas.LutGholein, true); + + if (!Town.npcInteract("Jerhyn")) { + Pather.moveTo(5166, 5206); + + return false; + } + + me.cancel(); + Pather.moveToExit(sdk.areas.HaremLvl1, true); + usePortal(sdk.areas.LutGholein); + + if (!Town.npcInteract("Meshif", false)) return false; + Misc.useMenu(sdk.menu.SailEast); + + break; + case 4: + if (me.inTown) { + Town.npcInteract("Cain"); + usePortal(sdk.areas.DuranceofHateLvl3); + } else { + delay(1500); + } + + Pather.moveTo(17591, 8070); + // Pather.usePortal(null); + Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct4, sdk.areas.PandemoniumFortress); + + 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; + default: + console.warn("Invalid act: " + act); + return false; + } + + delay(1000 + me.ping * 2); + + while (!me.area) { + delay(500); + } + + if (me.area === preArea) { + me.cancel(); + Town.move("portalspot"); + log("Act change failed.", Config.LocalChat.Enabled); + + return false; + } + + if (me.act === 2 && Game.getNPC(NPC.Jerhyn)) { + Town.npcInteract("Jerhyn"); + } else if (me.act === 3) { + Town.npcInteract("Hratli"); + } + + log("Act change done.", Config.LocalChat.Enabled); + } catch (e) { + return false; + } + + return true; + }; + + const getQuestInfo = function () { + return Object.keys(sequenceCheck).reverse().find(function (key) { + return sequenceCheck[key].check(); + }) || "none"; + }; + + const syncToLeaderAct = function () { + while (!leader.area) { + delay(500); + } + + act = Misc.getPlayerAct(leader); + + if (me.act !== act) { + Town.goToTown(act); + Town.move("portalspot"); + } + }; + + /** + * @param {() => boolean} checkQuestReady + * @param {string} npcName + * @param {string} doneLog + */ + const waitForQuestAndTalkToNpc = function (checkQuestReady, npcName, doneLog) { + Misc.poll(function () { + return !!checkQuestReady(); + }, Time.seconds(20), 1000); + + if (Town.npcInteract(npcName)) { + console.debug(doneLog + " done"); + return true; + } + + return false; + }; + + const handleNonQuesterNpcTalk = function () { + if (!nonQuesterNPCTalk) { + return null; + } + + console.debug("Leader Area: " + getAreaName(leader.area)); + + switch (leader.area) { + case sdk.areas.ClawViperTempleLvl2: + return waitForQuestAndTalkToNpc(function () { + return (Misc.checkQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.ReqComplete) + || Misc.checkQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.PartyMemberComplete)); + }, "Drognan", "drognan"); + case sdk.areas.ArcaneSanctuary: + return waitForQuestAndTalkToNpc(function () { + return (Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.ReqComplete) + || Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.PartyMemberComplete)); + }, "Atma", "atma"); + case sdk.areas.Travincal: + return waitForQuestAndTalkToNpc(function () { + return (Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, 4) + || Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.PartyMemberComplete) + || Misc.checkQuest(sdk.quest.id.TheGuardian, 8)); + }, "Cain", "cain"); + case sdk.areas.ArreatSummit: + return waitForQuestAndTalkToNpc(function () { + return (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.ReqComplete) + || Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.PartyMemberComplete)); + }, "Malah", "malah"); + default: + me.inTown && Town.move("portalspot"); + return null; + } + }; + + const goToPortalSpot = function () { + if (me.inTown) { + Town.move("portalspot"); + } + + return true; + }; + + /** + * @param {number} townArea + * @param {string} [leaderName] + */ + const returnToTown = function (townArea, leaderName) { + if (me.inTown) { + return true; + } + + if (leaderName) { + if (leaderName === Config.Leader) { + return usePortal(townArea); + } + + return Pather.usePortal(townArea, leaderName); + } + + return Pather.usePortal(townArea); + }; + + const handlePlayersOutNonQuester = function () { + // Non-questers can piggyback off quester out messages + switch (leader.area) { + case sdk.areas.OuterSteppes: + case sdk.areas.PlainsofDespair: + if (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"); + useScrollOfRes(); + } + + break; + } + + commands.shift(); + + return true; + }; + + const handlePlayersOutQuester = function () { + revive(); + + switch (me.area) { + case sdk.areas.CatacombsLvl4: + // Go to town if not there, break if procedure fails + if (!returnToTown(sdk.areas.RogueEncampment)) { + return false; + } + + if (!Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, 4)) { + D2Bot.printToConsole("Andariel quest failed", sdk.colors.D2Bot.Red); + scriptBroadcast("quit"); + } + + return true; + case sdk.areas.A2SewersLvl3: + if (!returnToTown(sdk.areas.LutGholein, Config.Leader)) { + return false; + } + + return true; + case sdk.areas.ArcaneSanctuary: + if (!returnToTown(sdk.areas.LutGholein, Config.Leader)) { + return false; + } + + Town.npcInteract("Atma"); + + if (!Misc.checkQuest(sdk.quest.id.TheSummoner, sdk.quest.states.Completed)) { + D2Bot.printToConsole("Summoner quest failed", sdk.colors.D2Bot.Red); + scriptBroadcast("quit"); + } + + goToPortalSpot(); + return true; + case sdk.areas.Travincal: + if (!returnToTown(sdk.areas.KurastDocktown, Config.Leader)) { + return false; + } + + Town.npcInteract("Cain"); + + if (!Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)) { + D2Bot.printToConsole("Travincal quest failed", sdk.colors.D2Bot.Red); + scriptBroadcast("quit"); + } + + goToPortalSpot(); + + return true; + case sdk.areas.DuranceofHateLvl3: + return usePortal(sdk.areas.KurastDocktown); + case sdk.areas.OuterSteppes: + case sdk.areas.PlainsofDespair: + if (!returnToTown(sdk.areas.PandemoniumFortress, Config.Leader)) { + return false; + } + + if (Misc.checkQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.ReqComplete)) { + Town.npcInteract("Tyrael"); + goToPortalSpot(); + } + + return true; + case sdk.areas.ChaosSanctuary: + me.classic && D2Bot.restart(); + + if (!returnToTown(sdk.areas.PandemoniumFortress, Config.Leader)) { + return false; + } + + return true; + case sdk.areas.BloodyFoothills: + if (!returnToTown(sdk.areas.Harrogath, Config.Leader)) { + return false; + } + + Town.npcInteract("Larzuk"); + goToPortalSpot(); + return true; + case sdk.areas.FrozenRiver: + if (!returnToTown(sdk.areas.FrozenRiver, Config.Leader)) { + return false; + } + + Town.npcInteract("Malah"); + useScrollOfRes(); + goToPortalSpot(); + + return true; + default: + if (!returnToTown(sdk.areas.townOf(me.area))) { + return false; + } + goToPortalSpot(); + return true; + } + }; + + const tryGetLeaderWaypoint = function () { + if (usePortal(null) + && Pather.getWP(me.area) + && usePortal(sdk.areas.townOf(me.area)) + && goToPortalSpot()) { + me.inTown && Config.LocalChat.Enabled && say("gotwp"); + return true; + } + + // check for bugged portal + let p = Game.getObject("portal"); + let preArea = me.area; + + if (!!p + && Misc.click(0, 0, p) + && Misc.poll(function () { + return me.area !== preArea; + }, 1000, 100) + && Pather.getWP(me.area) + && (usePortal(sdk.areas.townOf(me.area)) + || Pather.useWaypoint(sdk.areas.townOf(me.area)))) { + me.inTown && Config.LocalChat.Enabled && say("gotwp"); + return true; + } + + log("Failed to get wp", Config.LocalChat.Enabled); + !me.inTown && Town.goToTown(); + + return false; + }; + + /** @param {number} area */ + const isTalRashasTombArea = function (area) { + return [ + sdk.areas.TalRashasTomb1, + sdk.areas.TalRashasTomb2, + sdk.areas.TalRashasTomb3, + sdk.areas.TalRashasTomb4, + sdk.areas.TalRashasTomb5, + sdk.areas.TalRashasTomb6, + sdk.areas.TalRashasTomb7, + ].includes(area); + }; + + const handlePlayersInStonyField = function () { + if (!me.getItem(sdk.quest.item.KeytotheCairnStones)) { + log("Failed to pick up scroll", Config.LocalChat.Enabled); + return false; + } + + if (!usePortal(sdk.areas.StonyField)) { + log("Failed to use portal to stony field", Config.LocalChat.Enabled); + return false; + } + + 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) + ]; + + let stoneTick = getTickCount(); + for (let i = 0; i < 5; i++) { + for (let stone of stones) { + if (!stone || stone.mode) continue; + clickUnitAndWait(sdk.clicktypes.click.map.LeftDown, sdk.clicktypes.shift.NoShift, stone); + } + } + + while (stones.some(function (stone) { + return !stone.mode; + })) { + if (getTickCount() - stoneTick > Time.minutes(2)) { + log("Failed to activate stones", Config.LocalChat.Enabled); + return false; + } + 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)) { + usePortal(sdk.areas.RogueEncampment); + + break; + } + } + Town.move("portalspot"); + + return true; + }; + + const handlePlayersInDarkWood = function () { + if (!usePortal(sdk.areas.DarkWood)) { + log("Failed to use portal to dark wood", Config.LocalChat.Enabled); + return false; + } + + getQuestItem(sdk.items.quest.ScrollofInifuss, sdk.quest.chest.InifussTree); + delay(500); + usePortal(sdk.areas.RogueEncampment); + + if (Town.npcInteract("Akara")) { + log("Akara done", Config.LocalChat.Enabled); + } + + Town.move("portalspot"); + + return true; + }; + + const handlePlayersInTristram = function () { + if (!usePortal(sdk.areas.Tristram)) { + log("Failed to use portal to Tristram", Config.LocalChat.Enabled); + return true; + } + + let gibbet = Game.getObject(sdk.quest.chest.CainsJail); + + if (gibbet && !gibbet.mode) { + Pather.moveTo(gibbet.x, gibbet.y); + if (Misc.poll(function () { + return Misc.openChest(gibbet); + }, 2000, 100)) { + usePortal(sdk.areas.RogueEncampment); + Town.npcInteract("Akara") && log("Akara done", Config.LocalChat.Enabled); + } + } + Town.move("portalspot"); + commands.shift(); + + return true; + }; + + const handlePlayersInFrozenRiver = function () { + Town.npcInteract("Malah"); + + usePortal(sdk.areas.FrozenRiver); + delay(500); + + target = Game.getObject(sdk.objects.FrozenAnya); + + if (target) { + Pather.moveToUnit(target); + Misc.poll(function () { + Packet.entityInteract(target); + delay(100); + return !Game.getObject(sdk.objects.FrozenAnya); + }, 1000, 200); + delay(1000); + me.cancel(); + } + + return true; + }; + + const handlePlayersInArea = function () { + switch (leader.area) { + case sdk.areas.StonyField: + return handlePlayersInStonyField(); + case sdk.areas.DarkWood: + return handlePlayersInDarkWood(); + case sdk.areas.Tristram: + return handlePlayersInTristram(); + case sdk.areas.CatacombsLvl4: + if (!usePortal(sdk.areas.CatacombsLvl4)) { + log("Failed to use portal to catacombs", Config.LocalChat.Enabled); + return false; + } + + target = Pather.getPortal(null, Config.Leader); + target && Pather.walkTo(target.x, target.y); + + return true; + case sdk.areas.A2SewersLvl3: + Town.move("portalspot"); + + return usePortal(sdk.areas.A2SewersLvl3); + case sdk.areas.HallsoftheDeadLvl3: + usePortal(sdk.areas.HallsoftheDeadLvl3); + getQuestItem(sdk.quest.item.Cube, sdk.quest.chest.HoradricCubeChest); + return usePortal(sdk.areas.LutGholein); + case sdk.areas.ClawViperTempleLvl2: + usePortal(sdk.areas.ClawViperTempleLvl2); + getQuestItem(sdk.quest.item.ViperAmulet, sdk.quest.chest.ViperAmuletChest); + usePortal(sdk.areas.LutGholein); + + if (Town.npcInteract("Drognan")) { + log("drognan done", Config.LocalChat.Enabled); + Town.move("portalspot"); + return true; + } + + return false; + case sdk.areas.MaggotLairLvl3: + usePortal(sdk.areas.MaggotLairLvl3); + getQuestItem(sdk.quest.item.ShaftoftheHoradricStaff, sdk.quest.chest.ShaftoftheHoradricStaffChest); + delay(500); + usePortal(sdk.areas.LutGholein); + + return cube.Staff(); + case sdk.areas.ArcaneSanctuary: + return usePortal(sdk.areas.ArcaneSanctuary); + case sdk.areas.DurielsLair: + usePortal(sdk.areas.DurielsLair); + tyraelTalk(); + + return true; + case sdk.areas.Travincal: + if (!usePortal(sdk.areas.Travincal)) { + me.cancel(); + + return false; + } + + return true; + case sdk.areas.RuinedTemple: + if (!usePortal(sdk.areas.RuinedTemple)) { + me.cancel(); + + return false; + } + + getQuestItem(sdk.quest.item.LamEsensTome, sdk.quest.chest.LamEsensTomeHolder); + usePortal(sdk.areas.KurastDocktown); + Town.npcInteract("Alkor"); + Town.move("portalspot"); + return true; + case sdk.areas.DuranceofHateLvl3: + if (!usePortal(sdk.areas.DuranceofHateLvl3)) { + me.cancel(); + + return false; + } + + return true; + case sdk.areas.OuterSteppes: + case sdk.areas.PlainsofDespair: + return usePortal(null); + case sdk.areas.ChaosSanctuary: + usePortal(sdk.areas.ChaosSanctuary); + 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); + return true; + case sdk.areas.BloodyFoothills: + return usePortal(sdk.areas.BloodyFoothills); + case sdk.areas.FrozenRiver: + return handlePlayersInFrozenRiver(); + default: + if (isTalRashasTombArea(leader.area)) { + usePortal(null); + placeStaff(); + usePortal(sdk.areas.LutGholein); + } + + return true; + } + }; + + const actions = new Map([ + [AutoRush.allIn, function () { + switch (leader.area) { + case sdk.areas.A2SewersLvl3: + // Pick Book of Skill, use Book of Skill + Town.move("portalspot"); + usePortal(sdk.areas.A2SewersLvl3); + 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) { + console.log("Using book of skill"); + clickItem(sdk.clicktypes.click.item.Right, target); + + break; + } + } + + usePortal(sdk.areas.LutGholein); + + return true; + default: + if (!weAreBumper) { + if (!me.hell || me.charlvl < bumperLevelReq) { + console.debug("Not bumper, waiting for leader to enter area"); + return true; + } + } + + syncToLeaderAct(); + + switch (leader.area) { + case sdk.areas.ArreatSummit: + if (!usePortal(sdk.areas.ArreatSummit)) { + return false; + } + + // 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 (!usePortal(sdk.areas.Harrogath)) { + return false; + } + + return true; + case sdk.areas.WorldstoneChamber: + if (!usePortal(sdk.areas.WorldstoneChamber)) { + return false; + } + + return true; + } + } + return true; + }], + [AutoRush.playersIn, function () { + syncToLeaderAct(); + + // we need to talk to certain npcs in order to be able to grab waypoints as a non-quester + let npcTalkResult = handleNonQuesterNpcTalk(); + if (npcTalkResult !== null) { + return npcTalkResult; + } + + if (rushConfig.type !== RushModes.quester) { + return true; + } + + return handlePlayersInArea(); + }], + [AutoRush.playersOut, function () { + if (rushConfig.type !== RushModes.quester) { + return handlePlayersOutNonQuester(); + } + + return handlePlayersOutQuester(); + }], + ["flail", function () { + if (rushConfig.type !== RushModes.quester) { + return true; + } + return true; + }], + ["questinfo", function () { + if (rushConfig.type !== RushModes.quester) { + return true; + } + say("qinfo " + getQuestInfo()); + return true; + }], + ["wpinfo", function () { + if (rushConfig.type !== RushModes.quester) { + return true; + } + // go activate wp if we don't know our wps yet + !Pather.initialized && Pather.init(true); + const includeWorldstone = me.ancients; + const highestAct = me.highestAct; + + 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 (me.haveWaypoint(area)) return false; + switch (highestAct) { + case 1: + return (area < sdk.areas.LutGholein); + case 2: + return (area < sdk.areas.KurastDocktown); + case 3: + return (area < sdk.areas.PandemoniumFortress); + case 4: + return (area < sdk.areas.Harrogath); + } + if (!includeWorldstone && area === sdk.areas.WorldstoneLvl2) return false; + return true; // this returns a list of what we don't have + }); + + // say("wpinfo " + JSON.stringify({ areas: myWps })); + say("wpinfo " + JSON.stringify(myWps)); + return true; + }], + ["wp", function () { + if (!me.inTown && !Town.goToTown()) { + log("I can't get to town :(", Config.LocalChat.Enabled); + return false; + } + + let leaderArea = Misc.getPlayerArea(leader); + if (leaderArea && me.haveWaypoint(leaderArea)) { + say("alreadyhave"); + return true; + } + + syncToLeaderAct(); + + // make sure we talk to cain to access durance + if ( + 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 + (rushConfig.type !== RushModes.quester && !nonQuesterNPCTalk) && (nonQuesterNPCTalk = true); + + Town.getDistance("portalspot") > 10 && Town.move("portalspot"); + tryGetLeaderWaypoint(); + + return true; + }], + ["changeact", /** @param {number} act */ function (act) { + if (!changeAct(act)) { + return false; + } + + Town.move("portalspot"); + return true; + }], + ["quit", function () { + return (done = true); + }], + ["exit", function () { + if (!nextGame) { + D2Bot.printToConsole("Rush Complete"); + D2Bot.stop(); + } else { + // if (rushConfig && rushConfig.create && rushConfig.create.charsPerAcc > 1) { + // const charNumbers = "abcdefghijklmnopqrstuvwxyz"; + // let currName = me.charname; + // let suffix = currName.slice(-2); + + // if (suffix[1] === "z") { + // if (suffix[0] === "z") { + // // both characters were z, so wrap around + // D2Bot.setProfile(null, null, currName.slice(0, -2) + "aa"); + // } else { + // let nextCharIdx = charNumbers.indexOf(suffix[0]); + // let newSuffix = charNumbers[nextCharIdx + 1] + "a"; + // D2Bot.setProfile(null, null, currName.slice(0, -2) + newSuffix); + // } + // } else { + // let nextCharIdx = charNumbers.indexOf(suffix[1]); + // let newSuffix = suffix[0] + charNumbers[nextCharIdx + 1]; + // D2Bot.setProfile(null, null, currName.slice(0, -2) + newSuffix); + // } + // } + D2Bot.restart(); + } + return true; + }], + ["leader", function () { + log(Config.Leader + " is my leader in my config. " + leader.name + " is my leader right now", Config.LocalChat.Enabled); + return true; + }], + [me.name + "-quest", function () { + say("I am quester."); + rushConfig.type = RushModes.quester; + + return true; + }], + ]); + + // -------------------------------------------------- + // START MAIN SCRIPT EXECUTION + // -------------------------------------------------- + + // Apply overrides + applyOverrides(); + addEventListener("chatmsg", chatEvent); + + const curr = { + cmd: "", + retry: 0 + }; + let nonQuesterNPCTalk = false; + let act, target, done = false; + /** @type {string[]} */ + const commands = []; + + if (Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, sdk.quest.states.ReqComplete)) { + changeAct(2); + } + + if (Misc.checkQuest(sdk.quest.id.TheSevenTombs, sdk.quest.states.ReqComplete)) { + changeAct(3); + } + + if (Misc.checkQuest(sdk.quest.id.TerrorsEnd, sdk.quest.states.ReqComplete)) { + changeAct(5); + } + + Town.goToTown(me.highestAct); + me.inTown && Town.move("portalspot"); + + if (me.inArea(sdk.areas.RogueEncampment) + && !me.getQuest(sdk.quest.id.SpokeToWarriv, sdk.quest.states.Completed) + ) { + Town.npcInteract("Warriv"); + 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 + let checkIn = getTickCount() + Time.minutes(1); + const leader = Misc.poll(function () { + if (getTickCount() > checkIn) { + say("who is rusher"); + checkIn = getTickCount() + Time.minutes(1); + } + return Misc.findPlayer(Config.Leader); + }, Time.minutes(5), 1000); + if (!leader) throw new Error("Failed to find my rusher"); + + const maybeBumper = [RushModes.bumper, RushModes.quester].includes(rushConfig.type); + (rushConfig.type === RushModes.quester) + ? log("(Quester) Leader Found: " + Config.Leader, Config.LocalChat.Enabled) + : console.log("(NonQuester) 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 + const bumperLevelReq = getBumperLvlReq(); + const weAreBumper = maybeBumper && me.charlvl >= bumperLevelReq; + // ensure we are the right level to go to next difficulty if not on classic + let nextGame = ( + (maybeBumper && (me.classic || me.charlvl >= bumperLevelReq)) + // || (rushConfig && rushConfig.create && rushConfig.create.charsPerAcc > 1) // unsupported currentlly + ); + 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")); + + const processNextCommand = function () { + if (commands.length < 1) { + return; + } + + /** @type {string[]} */ + let args = commands[0].toLowerCase().split(" "); + let command = args.shift(); + + if (!actions.has(command)) { + console.debug("Command not found: " + command); + commands.shift(); + + return; + } + + curr.cmd = command; + + if (actions.get(command).apply(null, args)) { + commands.shift(); + curr.retry = 0; + } else { + console.debug("Command retry: " + command); + curr.retry++; + if (curr.retry > 3) { + log("Failed to do " + command, Config.LocalChat.Enabled); + if (command === "changeact") { + // well that's no good + scriptBroadcast("quit"); + } + commands.shift(); + } + } + }; + + while (true) { + // todo - clean all this up so there is clear distinction between quester/non-quester and no repeat sequnces + try { + if (me.inTown && me.needHealing()) { + Town.heal(); + Town.move("portalspot"); + } + + processNextCommand(); + } catch (e) { + console.error(e); + commands.shift(); + if (me.mode === sdk.player.mode.Dead) { + revive(); + } + } + + if (getUIFlag(sdk.uiflags.TradePrompt)) { + me.cancel(); + } + + if (done) { + break; + } + + delay(500); + } + done && scriptBroadcast("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..391ada27c --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rusher.js @@ -0,0 +1,286 @@ +/// +/** +* @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 +* +*/ + +const Rusher = new Runnable( + function Rusher () { + load("threads/rushthread.js"); + delay(500); + + const { + RushConfig, + } = require("../systems/autorush/RushConfig"); + const { RushModes, AutoRush } = require("../systems/autorush/RushConstants"); + + const commands = []; + /** @type {RusherConfig} */ + const rushProfile = RushConfig[me.profile]; + + let master = ""; + let done = false; + let gotQInfo = false; + + const RushThread = { + /** @type {Script} */ + _thread: null, + path: "threads/rushthread.js", + + get thread() { + 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.thread.send("rush-" + msg); + }, + /** + * @param {string} action + * @param {object} blob + */ + sendBlob: function (action, blob) { + // sign the msg so we can ignore other threads' messages + this.thread.send({ type: "rush", action: action, data: blob }); + }, + pause: function () { + say("Pausing"); + console.log("Pausing rush thread"); + this.thread.pause(); + }, + resume: function () { + say("Resuming"); + console.log("Resuming rush thread"); + this.thread.resume(); + }, + start: function () { + load(this.path); + nativeDelay(500); + + while (!this.thread) { + nativeDelay(500); + } + }, + stop: function () { + this.thread.stop(); + }, + reload: function () { + this.stop(); + + while (this.thread.running) { + nativeDelay(50); + } + this._thread = null; + this.start(); + }, + }; + + const getPartyAct = function () { + let party = getParty(); + let minArea = 999; + + do { + if (party.name !== me.name) { + Misc.poll(function () { + me.overhead("Waiting for party area info from " + party.name); + return party.area; + }, Time.seconds(5), 500); + + if (party.area < minArea) { + minArea = party.area; + } + } + } while (party.getNext()); + + return sdk.areas.actOf(minArea); + }; + + /** + * @param {string} nick + * @param {string} msg + */ + const chatEvent = function (nick, msg) { + if (!nick || !msg) return; + if (nick === me.name) return; + if (typeof msg !== "string") return; + if (msg === "who is rusher") { + say("i am rusher"); + return; + } + try { + if (msg.includes("qinfo")) { + if ((!!master && nick === master) || !master) { + RushThread.sendBlob("highestquest", { + highestquest: msg.split("qinfo ")[1], + quester: nick, + }); + console.log("Received quest info from master"); + gotQInfo = true; + } + return; + } + if (msg.includes("wpinfo")) { + if ((!!master && nick === master) || !master) { + /** @type {string[]} */ + let wps = JSON.parse(msg.split("wpinfo ")[1]); + RushThread.sendBlob("wps", { + wps: wps.map(wp => parseInt(wp, 10)), + }); + console.log("Received wp info from master"); + } + return; + } + switch (msg) { + case "master": + if (master) { + if (master !== nick) { + throw new Error("I already have a master."); + } + } else { + say(nick + " is my master."); + + master = nick; + } + + break; + case "release": + if (nick !== master) { + throw new Error("I'm only accepting commands from my master."); + } + say("I have no master now."); + master = false; + + break; + case "quit": + if (nick !== master) { + throw new Error("I'm only accepting commands from my master."); + } + done = true; + say("bye ~"); + scriptBroadcast("quit"); + + break; + default: + if (msg && msg.match(/^do \w|^clear \d|^pause$|^resume$/gi)) { + if (nick !== master) { + throw new Error("I'm only accepting commands from my master."); + } + commands.push(msg); + } + + break; + } + } catch (e) { + say("Error: " + e.message); + } + }; + + addEventListener("chatmsg", chatEvent); + + const playerWaitTimeout = getTickCount() + Time.minutes(2); + const { WaitPlayerCount } = rushProfile.config; + + while (Misc.getPartyCount() < Math.min(8, WaitPlayerCount)) { + if (getTickCount() > playerWaitTimeout) { + say("Player wait timed out. Expected: " + WaitPlayerCount + ", Found: " + Misc.getPartyCount()); + break; + } + me.overhead("Waiting " + Math.round((playerWaitTimeout - getTickCount()) / 1000) + "s for players to join"); + delay(500); + } + + // Skip to a higher act if all party members are there + let partyAct = getPartyAct(); + if (partyAct > 1) { + say("Party is in act " + partyAct + ", skipping to act " + partyAct); + RushThread.send("skiptoact " + partyAct); + } + + if (rushProfile.type === RushModes.rusher) { + // get quest info from master + let tick = getTickCount(); + let askAgain = 1; + say("questinfo"); + + while (!gotQInfo) { + // wait up to 3 minutes + if (getTickCount() - tick > Time.minutes(3)) { + break; + } + + if (getTickCount() - tick > Time.minutes(askAgain)) { + say("questinfo"); + askAgain++; + } + } + } + + delay(200); + RushThread.send("go"); + + while (!done) { + if (commands.length > 0) { + let command = commands.shift(); + + switch (command) { + case "pause": + RushThread.pause(); + + break; + case "resume": + RushThread.resume(); + + break; + default: + if (typeof command === "string") { + let commandSplit0 = command.split(" ")[0]; + + if (commandSplit0 === undefined) { + break; + } + + if (commandSplit0.toLowerCase() === "do") { + let script = (command.split(" ")[1] || "").toLowerCase(); + if (!script || !AutoRush.sequences.some(el => el.match(script, "gi"))) { + say("Invalid sequence"); + break; + } + RushThread.reload(); + RushThread.send(script); + } else if (commandSplit0.toLowerCase() === "clear") { + let area = command.split(" ")[1]; + if (!area) break; + let areaId = parseInt(area, 10); + if (isNaN(areaId) || areaId < sdk.areas.RogueEncampment || areaId > sdk.areas.WorldstoneChamber) { + say("Invalid area"); + break; + } + RushThread.reload(); + RushThread.send(command); + } + } + + break; + } + } + + delay(100); + } + + 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..86ffeb6e1 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ShopBot.js @@ -0,0 +1,312 @@ +/** +* @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 item of items) { + const rval = NTIP.CheckItem(item, pickEntries, true); + if (!rval.result) continue; + + if (Storage.Inventory.CanFit(item) + && Pickit.canPick(item) + && me.gold >= item.getItemCost(sdk.items.cost.ToBuy) + ) { + beep(); + D2Bot.printToConsole("Match found!", sdk.colors.D2Bot.DarkGold); + delay(1000); + + if (npc.startTrade(menuId)) { + Item.logItem("Shopped", item, rval.line); + item.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..0b7c9dedc --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Summoner.js @@ -0,0 +1,78 @@ +/** +* @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)) { + let portal = Game.getObject(sdk.objects.ArcaneSanctuaryPortal); + if (!portal || !Pather.usePortal(null, null, portal)) { + 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..325046736 --- /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() { + console.log("ÿc8TESTING"); + + let c; + + function KeyDown(key) { + key === sdk.keys.Insert && (c = true); + } + + addEventListener("keydown", KeyDown); + + while (true) { + if (c) { + try { + doTest(); + } catch (qq) { + console.log("failed"); + console.log(qq + " " + qq.fileName + " " + qq.lineNumber); + } + + c = false; + } + + delay(10); + } +} + +function doTest() { + console.log("test"); + console.log("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..a710684ab --- /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, { range: 25, duration: 2000 }); + Attack.securePosition(orgX + 100, orgY - 139, { range: 25, duration: 2000 }); + Attack.securePosition(orgX + 85, orgY - 139, { range: 25, duration: 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..df24603f8 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/TravincalLeech.js @@ -0,0 +1,90 @@ +/** +* @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 () { + 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..b6ae8b845 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Tristram.js @@ -0,0 +1,74 @@ +/** +* @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)) { + 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, { range: 10, duration: 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..5a24ac258 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/TristramLeech.js @@ -0,0 +1,128 @@ +/** +* @filename TristramLeech.js +* @author ToS/XxXGoD/YGM, theBGuy +* @desc Tristram Leech (Helper) +* +*/ + +const TristramLeech = new Runnable( + function TristramLeech () { + 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..e18ce25f2 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Wakka.js @@ -0,0 +1,450 @@ +/** +* @filename Wakka.js +* @author kolton, theBGuy +* @desc walking Chaos Sanctuary leecher +* +*/ + +const Wakka = new Runnable( + function Wakka () { + 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); + }; + + const levelTracker = (function () { + let levelTick = getTickCount(); + + return function () { + if (Common.Diablo.done) 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(); + throw new ScriptError("Reached wanted level"); + } + + return true; + }; + })(); + + const leaderTracker = (function () { + let leadTick = getTickCount(); + + return 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 ScriptError("Empty game"); + } + + // Player is in Throne of Destruction or Worldstone Chamber + if ([sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(getLeaderUnitArea())) { + Common.Diablo.done = true; + throw new ScriptError("Party leader is running baal"); + } + + return true; + }; + })(); + + // 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"); + } + + Common.Diablo.addLightsEventListener(); + const Worker = require("../modules/Worker"); + + try { + if (Config.Wakka.SkipIfBaal) { + Worker.runInBackground.leaderTracker = leaderTracker; + } + + Worker.runInBackground.levelTracker = levelTracker; + + Worker.runInBackground.diaSpawned = Common.Diablo.diaSpawnWatcher(function () { + internals.vizClear = true; + internals.seisClear = true; + internals.infClear = 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) { + if (!(e instanceof ScriptError)) { + console.error(e); + } + } finally { + delete Worker.runInBackground.leaderTracker; + delete Worker.runInBackground.levelTracker; + delete Worker.runInBackground.diaSpawned; + + Common.Diablo.removeLightsEventListener(); + } + + 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/systems/automule/AutoMule.js b/d2bs/kolbot/libs/systems/automule/AutoMule.js new file mode 100644 index 000000000..1b2f77abc --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/AutoMule.js @@ -0,0 +1,704 @@ +/** +* @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; + + /** @param {muleObj} muleObj */ + const handleLadderCheck = function (muleObj) { + if (muleObj.ladder) { + return me.ladder; + } + return !me.ladder; + }; + + for (let i in this.Mules) { + if (this.Mules.hasOwnProperty(i)) { + for (let profile of this.Mules[i].enabledProfiles) { + if ( + String.isEqual(profile, me.profile) + || (String.isEqual(profile, "all") && handleLadderCheck(this.Mules[i])) + ) { + !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, me.profile) + || (String.isEqual(profile, "all") && handleLadderCheck(this.TorchAnniMules[i])) + ) { + !info && (info = {}); + info.torchMuleInfo = this.TorchAnniMules[i]; + + break; + } + } + } + } + + return info; + }, + + muleCheck: function () { + const info = this.getInfo(); + + /** + * @param {unknown} info + * @returns {info is {muleInfo: muleObj}} + */ + function hasMuleInfo(info) { + return info && info.hasOwnProperty("muleInfo"); + } + + if (hasMuleInfo(info)) { + const items = this.getMuleItems(); + const { muleInfo } = info; + + if (Object.hasOwn(muleInfo, "usedStashTrigger") && Object.hasOwn(muleInfo, "usedInventoryTrigger") + && Storage.Inventory.UsedSpacePercent() >= muleInfo.usedInventoryTrigger + && Storage.Stash.UsedSpacePercent() >= muleInfo.usedStashTrigger + && items.length > 0 + ) { + console.debug( + "usedStashAmount: " + Storage.Stash.UsedSpacePercent() + "%" + + " usedInventoryAmount: " + Storage.Inventory.UsedSpacePercent() + "%" + ); + 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 profile " + muleObj.muleProfile, 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/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..4b249cade --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/AutoRush.js @@ -0,0 +1,1449 @@ +/** +* @filename AutoRush.js +* @author theBGuy +* @desc Scripts for the AutoRush system +* +*/ + +(function (module) { + const { + AutoRush, + RushModes, + } = require("./RushConstants"); + const { RushConfig } = require("./RushConfig"); + + /** + * @param {string} msg + * @param {boolean} sayMsg + */ + const log = function (msg = "", sayMsg = true) { + console.log(msg); + sayMsg && say(msg); + }; + + const timedOut = function (nick = "") { + return nick ? "timed out waiting for " + nick : "timed out"; + }; + + /** + * @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 getBumperLvlReq = function () { + return [20, 40, 60][me.diff]; + }; + + /** + * @param {string} nick + * @returns {boolean} + */ + const bumperCheck = function (nick) { + return nick + ? Misc.findPlayer(nick).level >= getBumperLvlReq() + : Misc.checkPartyLevel(getBumperLvlReq()); + }; + + /** @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; + }; + + /** @param {string} [nick] */ + const cain = function cain (nick) { + 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, { range: 40, duration: 3000, skipBlocked: 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, { range: 25, duration: 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, { range: 40, duration: 3000, skipBlocked: 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)) { + let playersleftStony = Misc.poll(function () { + if (playerIn(sdk.areas.RogueEncampment, nick)) { + return true; + } + return false; + }, AutoRush.playerWaitTimeout, 1000); + + if (playersleftStony && Pather.usePortal(sdk.areas.Tristram)) { + break; + } + } + Attack.securePosition(StoneAlpha.x, StoneAlpha.y, { range: 35, duration: 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, { range: 25, duration: 3000 }); + Pather.makePortal(); + log(AutoRush.playersIn); + + tick = getTickCount(); + + while (getTickCount() - tick < Time.minutes(2)) { + if (gibbet.mode) { + break; + } + Attack.securePosition(me.x, me.y, { range: 15, duration: 1000 }); + } + + const playersLeftTrist = Misc.poll(function () { + if (playerIn(me.area, nick)) { + return true; + } + Pather.move(gibbet, { minDist: 8 }); + return false; + }, AutoRush.playerWaitTimeout, 1000); + + if (!playersLeftTrist) { + log(timedOut(nick)); + return false; + } + } + } + + return true; + }; + + /** @param {string} [nick] */ + const andariel = function andariel (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, { range: 40, duration: 3000, skipBlocked: 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(timedOut(nick)); + 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); + for (let i = 0; i < 3; i++) { + log("changeact 2"); + + let playersMoved = Misc.poll(function () { + return playersInAct(2); + }, Time.seconds(30), Time.seconds(1)); + + if (playersMoved) { + break; + } + } + Town.goToTown(2); + } + + return true; + }; + + /** @param {string} [nick] */ + const bloodraven = function bloodraven (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, { range: 10, duration: 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(timedOut(nick)); + 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 smith (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, { range: 30, duration: 3000, skipBlocked: true }); + Pather.makePortal(); + log(AutoRush.playersIn); + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 radament (nick) { + log("starting radament"); + + /** + * @param {Monster} unit + * @param {number} range + * @returns {boolean} + */ + 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, { range: 35, duration: 3000 }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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, { range: 30, duration: 3000 }); + + if (!Misc.poll(function () { + return !playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + return false; + } + + Pather.moveToUnit(returnSpot); + Pather.makePortal(); + log(AutoRush.allIn); + + if (!Misc.poll(function () { + // often happens with chanter mode where users don't listen to the commands + if (!Game.getItem(sdk.quest.item.BookofSkill)) { + return true; + } + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 cube (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, { range: 30, duration: 3000, skipBlocked: true }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 amulet (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, { range: 25, duration: 3000, skipBlocked: me.hell, useRedemption: me.hell }); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 staff (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, { range: 30, duration: 3000, skipBlocked: true }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 summoner (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(); + const spot = (function () { + switch (preset.x) { + case 25011: + return new PathNode(25081, 5446); + case 25866: + return new PathNode(25830, 5447); + case 25431: + switch (preset.y) { + case 5011: + return new PathNode(25449, 5081); + case 5861: + return new PathNode(25447, 5822); + } + } + return null; + })(); + + if (!spot || !Pather.moveToUnit(spot)) { + throw new Error("summoner failed"); + } + + Attack.securePosition(spot.x, spot.y, { range: 25, duration: 3000, skipIds: [sdk.monsters.Summoner] }); + 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, { range: 25, duration: 500 }); + return false; + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 duriel (nick) { + log("starting duriel"); + + if (me.inTown) { + Town.doChores(); + Pather.useWaypoint(sdk.areas.CanyonofMagic, true); + } + + 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, { range: 30, duration: 3000, skipBlocked: true, useRedemption: me.hell }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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(timedOut(nick)); + 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.moveToEx(22579, 15706, { allowTeleport: false }); + 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(timedOut(nick)); + return false; + } + + if (!Pather.usePortal(null, me.name)) { + Town.goToTown(); + } + + Pather.useWaypoint(sdk.areas.PalaceCellarLvl1); + if (!Pather.moveToExit([sdk.areas.HaremLvl2, sdk.areas.HaremLvl1], true)) { + // try again - rate but have seen it fail so safety check here + if (!Pather.moveToExit([sdk.areas.HaremLvl2, sdk.areas.HaremLvl1], true)) { + throw new Error("duriel failed to move to harem"); + } + } + Pather.moveTo(10022, 5047); + + if (AutoRush.rushMode !== RushModes.chanter) { + for (let i = 0; i < 3; i++) { + if (!Pather.getPortal(sdk.areas.LutGholein, me.name)) { + Pather.makePortal(); + } + log("changeact 3"); + + 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.makePortal(); + Pather.moveToExit([sdk.areas.HaremLvl1, sdk.areas.LutGholein], true); + Pather.useWaypoint(sdk.areas.RogueEncampment); + } + + return true; + }; + /** @param {string} [nick] */ + const gidbinn = function gidbinn (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"); + } + + Attack.securePosition(me.x, me.y, { range: 30, duration: 3000, skipBlocked: true }); + Pather.makePortal(); + log(AutoRush.playersIn); + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + return false; + } + + Misc.poll(function () { + Attack.clear(15); + altar.distance > 5 && Pather.moveToUnit(altar); + return altar.mode === sdk.objects.mode.Active; + }, Time.minutes(1), Time.seconds(1)); + + Misc.poll(function () { + const gidbinn = Game.getItem(sdk.quest.item.TheGidbinn); + if (gidbinn) { + Attack.securePosition(gidbinn.x, gidbinn.y, { range: 30, duration: 3000, skipBlocked: true }); + Pather.moveToUnit(gidbinn); + return true; + } + Attack.clear(25); + altar.distance > 5 && Pather.moveToUnit(altar); + return false; + }, Time.minutes(1), Time.seconds(1)); + + 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 lamesen (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, { range: 30, duration: 2000 }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + return false; + } + + if (AutoRush.rushMode !== RushModes.chanter) { + if (!Misc.poll(function () { + return !playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + return false; + } + } + + Pather.usePortal(null, null); + + return true; + }; + /** @param {string} [nick] */ + const brain = function brain (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, { range: 30, duration: 3000, skipBlocked: me.hell, useRedemption: me.hell }); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 eye (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, { range: 30, duration: 3000, skipBlocked: me.hell, useRedemption: me.hell }); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 heart (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, { range: 30, duration: 3000, skipBlocked: me.hell, useRedemption: me.hell }); + Pather.makePortal(); + + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 travincal (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(wpCoords.x + 4, wpCoords.y - 47), + new PathNode(wpCoords.x - 4, wpCoords.y - 92), + new PathNode(wpCoords.x + 25, wpCoords.y - 70), + new PathNode(wpCoords.x + 20, wpCoords.y - 123), + ].forEach(function (node) { + Pather.move(node) && Attack.securePosition(node.x, node.y, { range: 25, duration: 2500 }); + }); + + Pather.move(portalSpot); + Attack.securePosition(portalSpot.x, portalSpot.y, { range: 40, duration: 4000 }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + Attack.securePosition(portalSpot.x, portalSpot.y, { range: 25, duration: 1000 }); + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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, { range: 40, duration: 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 mephisto (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(timedOut(nick)); + 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, { range: 40, duration: 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("changeact 4"); + + 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 izual (nick) { + log("starting izual"); + + /** + * @param {Monster} unit + * @param {number} range + * @returns {boolean} + */ + 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, { range: 30, duration: 3000, skipIds: [sdk.monsters.Izual] }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 diablo (nick) { + 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(timedOut(nick)); + 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) { + if (error instanceof ScriptError) { + throw 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("changeact 5"); + + 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 shenk (nick) { + log("starting shenk"); + + Pather.useWaypoint(sdk.areas.FrigidHighlands, true) && Precast.doPrecast(false); + Pather.moveTo(3846, 5120); + Attack.securePosition(me.x, me.y, { range: 30, duration: 3000 }); + Pather.makePortal(); + log(AutoRush.playersIn); + + if (!Misc.poll(function () { + return playerIn(me.area, nick); + }, AutoRush.playerWaitTimeout, 1000)) { + log(timedOut(nick)); + 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 anya (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, { range: 30, duration: 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) { + let malahspotion = me.getItem(sdk.quest.item.MalahsPotion); + if (malahspotion) { + malahspotion.drop(); + } + 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(timedOut(nick)); + 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 ancients (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 " + getBumperLvlReq()); + + return false; + } + if (!RushConfig[me.profile].config.Wps) { + log("No eligible bumpers detected. Rush complete~"); + delay(500); + quit(); + } + + return false; + } + + 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(timedOut(nick)); + 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 baal (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(); + } + scriptBroadcast("rush-removewp " + sdk.areas.WorldstoneLvl2); + + 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(); + } + scriptBroadcast("rush-removewp " + sdk.areas.WorldstoneLvl2); + + return false; + } + + if (AutoRush.rushMode === RushModes.chanter && !bumperCheck(nick)) { + log(nick + " you are not eligible for baal. You need to be at least level " + getBumperLvlReq()); + + return false; + } + + 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, + timedOut: timedOut, + playerIn: playerIn, + playersInAct: playersInAct, + bumperCheck: bumperCheck, + getBumperLvlReq: getBumperLvlReq, + 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..41b9e1573 --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/RushConfig.js @@ -0,0 +1,149 @@ +/** +* @filename RushConfig.js +* @author theBGuy +* @desc Configuration file for AutoRush system +* +*/ + +(function (module) { + // no touchy - need these imports + const { RushModes } = require("./RushConstants"); + + /** + * This is what you edit. + * + * Each key is a profile name. + * The value shape depends on the selected mode (`type`). + * + * Rushee modes: + * - `RushModes.quester` + * - `RushModes.follower` + * - `RushModes.bumper` + * + * Rusher modes: + * - `RushModes.rusher` + * - `RushModes.manual` + * + * `create` usage variants: + * - Account + character creation: provide `account`, `password`, `charName`, and `charInfo`. + * - Character-only creation: provide only `charName` and `charInfo`. You can skip charName for automatic name generation (uses soloplay's NameGen if available, otherwise will error). + * + * Rusher config defaults: + * - Quest toggles default to `true`. + * - `Wps` defaults to `false`. + * - You do not need to define every `config` property when you want default behavior. + * + * @example + * // Quester with account + character creation + * "quester-profile": { + * type: RushModes.quester, + * startProfiles: ["bumper-profile", "follower-profile", "rusher-profile"], + * create: { + * account: "myacc", + * password: "mypassword", + * charName: "quester", + * charInfo: "scl-sorc", + * }, + * config: { + * Leader: "my-rusher-char", + * }, + * } + * + * @example + * // Follower with character-only creation + * "follower-profile": { + * type: RushModes.follower, + * leader: "quester-profile", + * create: { + * charName: "follower", + * charInfo: "scl-zon", + * }, + * config: { + * Leader: "my-rusher-char", + * }, + * } + * + * @example + * // Bumper with minimal config + * "bumper-profile": { + * type: RushModes.bumper, + * leader: "quester-profile", + * config: { + * Leader: "my-rusher-char", + * }, + * } + * + * @example + * // Rusher with minimal config (uses defaults) + * "rusher-profile": { + * type: RushModes.rusher, + * leader: "quester-profile", + * config: { + * Wps: true, + * LastRun: "baal", + * }, + * } + * + * @example + * // Rusher with explicit full config + * "rusher-profile-full": { + * type: RushModes.rusher, + * leader: "quester-profile", + * playerWaitTimeout: Time.minutes(1), + * 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: "", + * }, + * } + * + * @example + * // Manual mode (same config shape as rusher) + * "manual-profile": { + * type: RushModes.manual, + * leader: "quester-profile", + * config: { + * Wps: true, + * }, + * } + * + * @type {Object.} + */ + const RushConfig = { + "liberaloutrun": { + type: RushModes.rusher, + leader: "quester", + config: { + Wps: true, + LastRun: "andariel", + } + }, + "quester": { + type: RushModes.quester, + startProfiles: ["liberaloutrun"], + create: { + account: "bgrushtesb", + password: "pass", + charInfo: "scl-sorc", + }, + config: { + Leader: "liberaloutrun", + } + }, + }; + + module.exports = { + RushConfig: RushConfig, + }; +})(module); diff --git a/d2bs/kolbot/libs/systems/autorush/RushConstants.js b/d2bs/kolbot/libs/systems/autorush/RushConstants.js new file mode 100644 index 000000000..0c6837e34 --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/RushConstants.js @@ -0,0 +1,328 @@ +/** +* @filename RushConstants.js +* @author theBGuy +* @desc Constants for AutoRush system - no touchy +* +*/ + +(function (module) { + /** + * @const + */ + 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 sequenceCheck = { + cain: { + check: function () { + return me.cain; + } + }, + andariel: { + check: function () { + return me.andariel; + } + }, + radament: { + check: function () { + return me.radament; + } + }, + cube: { + check: function () { + return !!me.cube; + } + }, + amulet: { + check: function () { + return (me.horadricstaff || me.amulet || me.completestaff); + } + }, + staff: { + check: function () { + return (me.horadricstaff || me.shaft || me.completestaff); + } + }, + summoner: { + check: function () { + return me.summoner; + } + }, + duriel: { + check: function () { + return me.duriel; + } + }, + lamesen: { + check: function () { + return me.lamessen; + } + }, + travincal: { + check: function () { + return me.travincal; + } + }, + mephisto: { + check: function () { + return me.mephisto; + } + }, + izual: { + check: function () { + return me.izual; + } + }, + diablo: { + check: function () { + return me.diablo; + } + }, + shenk: { + check: function () { + return me.shenk; + } + }, + anya: { + check: function () { + return me.anya || Misc.checkQuest(sdk.quest.id.PrisonofIce, 8/** Recieved the scroll */); + } + }, + ancients: { + check: function () { + return me.ancients; + } + }, + baal: { + check: function () { + return me.baal; + } + }, + }; + + const _defaultRusheeConfig = { + /** @type {RushModes} */ + type: RushModes.quester, + /** @type {string[]} */ + startProfiles: [], + leader: "", + create: { + /** @type {string} */ + account: "", + /** @type {string} */ + password: "", + /** @type {string} */ + charName: "", + /** + * @type {string} + * @desc Format: "scl-sorc" - "scl" = softcore ladder, "sorc" = sorceress + */ + charInfo: "", + }, + config: { + /** Rusher in game character name */ + Leader: "", + } + }; + + const _defaultRusherConfig = { + /** @type {RushModes.rusher} */ + type: RushModes.rusher, + /** How long to wait for a player to leave/enter an area before ending quest script with failed */ + playerWaitTimeout: Time.minutes(1), + 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: "", + }, + /** @type {string[]} */ + startProfiles: [], + allowPickit: true, + }; + + /** + * Merge defaults into a single rush profile while preserving nested defaults. + * Mutates the passed profile so other consumers of RushConfig[me.profile] see normalized values. + * @param {DefaultConfig} rushProfile + * @returns {DefaultConfig} + */ + function normalizeRushProfileConfig (rushProfile) { + let mergedProfile; + + if (rushProfile.type === RushModes.rusher) { + mergedProfile = Object.assign({}, _defaultRusherConfig, rushProfile); + mergedProfile.config = Object.assign( + {}, + _defaultRusherConfig.config, + rushProfile.config ? rushProfile.config : {} + ); + mergedProfile.config.Ancients = Object.assign( + {}, + _defaultRusherConfig.config.Ancients, + rushProfile.config && rushProfile.config.Ancients ? rushProfile.config.Ancients : {} + ); + } else { + mergedProfile = Object.assign({}, _defaultRusheeConfig, rushProfile); + mergedProfile.create = Object.assign( + {}, + _defaultRusheeConfig.create, + rushProfile.create ? rushProfile.create : {} + ); + // Keep existing access pattern in rushConfigInit where Config.Leader reads rushProfile.config.Leader. + mergedProfile.config = Object.assign({}, rushProfile.config ? rushProfile.config : {}); + } + + mergedProfile.startProfiles = Array.isArray(mergedProfile.startProfiles) + ? mergedProfile.startProfiles.slice(0) + : []; + + Object.keys(rushProfile).forEach(function (key) { + delete rushProfile[key]; + }); + + Object.assign(rushProfile, mergedProfile); + + return rushProfile; + } + + /** @param {DefaultConfig} rushProfile */ + function rushConfigInit (rushProfile) { + if (!rushProfile) { + throw new Error("No rush config found for this profile. Please create one in RushConfig.js"); + } + + normalizeRushProfileConfig(rushProfile); + + // disabling all other scripts so nothing interfers + Object.keys(Scripts).forEach(function (script) { + Scripts[script] = false; + }); + + if (rushProfile.type === RushModes.rusher) { + Scripts.Rusher = true; + if (!rushProfile.allowPickit) { + Pickit.enabled = false; + } + } else { + Scripts.Rushee = true; + Config.Leader = rushProfile.config.Leader + ? rushProfile.config.Leader + : /** TODO: iterate config & try to find rusher profile with our game leader */ rushProfile.rusherProfile + ? (function () { + // we've been passed the rushers profile name so lets try to determine thier in game name + if (FileTools.exists("data/" + rushProfile.rusherProfile + ".json")) { + let string = FileAction.read("data/" + rushProfile.rusherProfile + ".json"); + + if (string) { + let obj = JSON.parse(string); + + if (obj && obj.hasOwnProperty("name")) { + return obj.name; + } + } + } + return ""; + })() + : ""; + Pickit.enabled = false; + } + + const { RushConfig } = require("./RushConfig"); + + // everyone except main rushee + if (me.profile !== rushProfile.leader) { + Config.QuitList.push(rushProfile.leader); + } else { + Object.keys(RushConfig).forEach(function (profile) { + if ( + // is a rusher type profile + RushConfig[profile].type === RushModes.rusher + // we are thier game maker + && RushConfig[profile].leader === me.profile + ) { + Config.QuitList.push(profile); + } + }); + } + Config.QuitList = Config.QuitList.filter(function (el) { + return !!el; + }); + + Config.QuitListMode = 1; + !Config.PublicMode && (Config.PublicMode = true); + !Config.LocalChat.Enabled && (Config.LocalChat.Enabled = true); + if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0) { + // so we don't get the warning + Config.AttackSkill[1] = 0; + Config.AttackSkill[3] = 0; + } + Config.MFLeader = false; + Config.Gamble = false; + } + + module.exports = { + RushModes: RushModes, + AutoRush: AutoRush, + sequenceCheck: sequenceCheck, + normalizeRushProfileConfig: normalizeRushProfileConfig, + rushConfigInit: rushConfigInit, + }; +})(typeof module === "undefined" ? this.AutoRushConstants = {} : 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..5813edeab --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/index.d.ts @@ -0,0 +1,55 @@ +declare global { + enum RushModes { + quester = 0, + follower = 1, + bumper = 2, + rusher = 3, + chanter = 4, + manual = 5, + } + type RusheeConfig = { + type: RushModes.bumper | RushModes.quester | RushModes.follower; + startProfiles: string[]; + leader: string; + create: { + account: string; + password: string; + charName: string; + /** + * @desc Format: "scl-sorc" - "scl" = softcore ladder, "sorc" = sorceress + */ + charInfo: string; + }; + config: { + /** Rusher in game character name */ + Leader: string; + }; + }; + type RusherConfig = { + type: RushModes.rusher | RushModes.manual; + /** How long to wait for a player to leave/enter an area before ending quest script with failed */ + playerWaitTimeout: number; + 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; + }; + startProfiles: string[]; + }; + type DefaultConfig = RusheeConfig | RusherConfig; + type RushConfig = { + [key: string]: DefaultConfig; + }; +} +export {}; diff --git a/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.d.ts b/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.d.ts new file mode 100644 index 000000000..9aec1c5fc --- /dev/null +++ b/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.d.ts @@ -0,0 +1,20 @@ +export interface CharRefresherType { + LobbyTime: number | number[]; + /** + * @param hash - The hash value. + * @returns The loaded data. + */ + load(hash: string): string; + + /** + * @param hash - The hash value. + * @param data - The data to save. + */ + save(hash: string, data: string): void; + + remove(): void; +} + +declare global { + const CharRefresher: CharRefresherType; +} diff --git a/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.js b/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.js new file mode 100644 index 000000000..318189ed7 --- /dev/null +++ b/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.js @@ -0,0 +1,36 @@ +/** +* @filename CharRefresher.js +* @author theBGuy +* @desc Refresh characters so they don't expire, for setup @see RefresherConfig.js +* +* @typedef {import("../../../sdk/globals")} +*/ + +/** @type {import("./CharRefresher").CharRefresherType} */ +const CharRefresher = { + LobbyTime: [15, 30], + /** + * @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/CharRefresher.json"); + }, +}; 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/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/gameaction/GameAction.d.ts b/d2bs/kolbot/libs/systems/gameaction/GameAction.d.ts new file mode 100644 index 000000000..7ba59c0d7 --- /dev/null +++ b/d2bs/kolbot/libs/systems/gameaction/GameAction.d.ts @@ -0,0 +1,35 @@ +// @ts-nocheck +export interface GameActionType { + LogNames: boolean; + LogItemLevel: boolean; + LogEquipped: boolean; + LogMerc: boolean; + SaveScreenShot: boolean; + IngameTime: number; + task: { hash: string; profile: string; action: string; data: unknown } | null; + + // Methods + init(task: string): void; + update(action: string, data: string | object): void; + gameInfo(): { gameName: string; gamePass: string }; + getLogin(): { realm: string; account: string; password: string }; + getCharacters(): string[]; + inGameCheck(): boolean; + load(hash: string): string; + save(hash: string, data: string): void; + dropItems(dropList: string[]): void; + convertLadderFiles(): void; +} + +declare global { + type DoDropGameActionData = { + items: Array<{ title: string; character: string; realm: string; account: string; itemid: string }>; + gameName: string; + gamePass: string; + realm: string; + account: string; + chars: string[]; + }; + + const GameAction: GameActionType; +} diff --git a/d2bs/kolbot/libs/systems/gameaction/GameAction.js b/d2bs/kolbot/libs/systems/gameaction/GameAction.js new file mode 100644 index 000000000..2badd4276 --- /dev/null +++ b/d2bs/kolbot/libs/systems/gameaction/GameAction.js @@ -0,0 +1,288 @@ +/* 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"); + +/** @type {import("./GameAction").GameActionType} */ +const GameAction = { + task: null, + + /** + * @param {string} task - JSON string containing task information + * @returns {boolean} + */ + init: function (task) { + try { + GameAction.task = JSON.parse(task); + const { action } = GameAction.task; + console.log("ÿc4GameActionÿc0: Task: " + action); + + if (this.task["data"] && typeof this.task.data === "string") { + this.task.data = JSON.parse(this.task.data); + } + + if (this.task.action === "doDrop" && this.task.data && Array.isArray(this.task.data.items)) { + /** @type {GameActionData} */ + let data = (this.task.data); + for (let i = 0; i < data.items.length; i += 1) { + console.log("ÿc4GameActionÿc0: Item: " + data.items[i].title); + } + } + + MuleLogger.LogNames = this.LogNames; + MuleLogger.LogItemLevel = this.LogItemLevel; + MuleLogger.LogEquipped = this.LogEquipped; + MuleLogger.LogMerc = this.LogMerc; + MuleLogger.SaveScreenShot = this.SaveScreenShot; + + return true; + } catch (err) { + console.log("ÿc4GameActionÿc0: Error in init: " + err); + this.update("done", "Error in init: " + err); + D2Bot.stop(); + + return false; + } + }, + + /** + * @param {string} action + * @param {string | Object} data + */ + 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 = (function () { + try { + return JSON.parse(JSON.stringify(GameAction.task)); // deep copy + } catch (err) { + console.log("ÿc4GameActionÿc0: Error in update: " + err); + return {}; + } + })(); + 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")) { + return false; + } + + 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) { + const elapsedMs = getTickCount() - me.gamestarttime; + const totalMs = this.IngameTime * 1000; + const remainingSeconds = Math.round((totalMs - elapsedMs) / 1000); + + me.overhead("Stalling for " + remainingSeconds + " Seconds"); + delay(1000); + } + + try { + quit(); + } finally { + while (me.ingame) { + delay(100); + } + } + + return true; + }, + + 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; + } + + //unit.gid ":" + unit.classid + ":" + unit.location + ":" + unit.x + ":" + unit.y; + let info = droplist[i].itemid.split(":"); + + let classid = info[1]; + let loc = info[2]; + let unitX = info[3]; + let unitY = info[4]; + + // for debug purposes + console.log("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(); + } + } + } + }, + + convertLadderFiles: function () { + console.log("ÿc4GameActionÿc0: Converting ladder files to non-ladder..."); + if (!this.task || !this.task.data || this.task.action !== "doConvertNL") { + return; + } + + /** @type {{ realm: string, account: string, character: string}[]} */ + const data = this.task.data; + let converted = 0; + + if (!data || !Array.isArray(data)) { + this.update("done", "Invalid data for conversion!"); + D2Bot.stop(); + delay(5000); + } + + for (let i = 0; i < data.length; i++) { + const { realm, account, character } = data[i]; + + if (!realm || !account || !character) { + continue; + } + + const fileName = "mules/" + realm + "/" + account + "/" + character + ".txt"; + if (!FileTools.exists(fileName)) { + continue; + } + + const fileContent = FileTools.readText(fileName); + if (!fileContent) { + continue; + } + const [charName, ext] = character.split("."); + const newFileName = "mules/" + realm + "/" + account + "/" + charName + "." + ext.replace("l", "n") + ".txt"; + FileTools.writeText(newFileName, fileContent); + FileTools.remove(fileName); + console.log("Converted " + fileName + " to " + newFileName); + converted++; + } + + this.update("done", "Conversion complete! Converted " + converted + " files."); + D2Bot.stop(me.profile, true); + }, +}; + +// load configuration file and apply settings to GameAction, has to be after the namespace is created +(function () { + Object.assign(GameAction, require("./GameActionConfig", null, false)); +})(); 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..eb8404bbf --- /dev/null +++ b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.d.ts @@ -0,0 +1,72 @@ +export interface MuleLoggerType { + LogGame: [string, string]; + LogNames: boolean; + LogItemLevel: boolean; + LogEquipped: boolean; + LogMerc: boolean; + SaveScreenShot: boolean; + AutoPerm: boolean; + IngameTime: number; + LogAccounts: { [account: string]: string[] }; + + // Methods + inGameCheck(): boolean; + + /** + * Save perm status to logs/MuleLogPermInfo.json. + * @param charPermInfo - The character's permanent status information. + */ + savePermedStatus(charPermInfo?: { charname: string; perm: boolean }): void; + + /** + * Load perm status from logs/MuleLogPermInfo.json. + * @returns The character's permanent status information. + */ + loadPermedStatus(): { charname: string; perm: boolean }; + + /** + * @param hash - The hash value. + * @returns The loaded data. + */ + load(hash: string): string; + + /** + * @param hash - The hash value. + * @param data - The data to save. + */ + save(hash: string, data: string): void; + + remove(): void; + + dumpItemStats(item: ItemUnit): Record; + + /** + * 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. + */ + logItem( + unit: ItemUnit, + logIlvl?: boolean, + ): { + itemColor: number; + image: string; + title: string; + description: string; + header: string; + sockets: ItemUnit[]; + }; + + /** + * 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. + */ + logChar(logIlvl?: boolean, logName?: boolean, saveImg?: boolean): void; +} + +declare global { + const MuleLogger: MuleLoggerType; +} diff --git a/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.js b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.js new file mode 100644 index 000000000..2c912a89a --- /dev/null +++ b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.js @@ -0,0 +1,398 @@ +/* eslint-disable max-len */ +/** +* @filename MuleLogger.js +* @author kolton, theBGuy +* @desc Log items and perm accounts/characters, for setup @see LoggerConfig.js +* +* @typedef {import("../../../sdk/globals")} +*/ + +/** @type {import("./MuleLogger").MuleLoggerType} */ +const MuleLogger = { + inGameCheck: function () { + if (getScript("D2BotMuleLog.dbj") && this.LogGame[0] && me.gamename.match(this.LogGame[0], "i")) { + console.log("ÿ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"); + }, + + /** + * @param {ItemUnit} item + * @returns {Record} + */ + dumpItemStats: function (item) { + const stats = item.getStat(-2); + const dump = {}; + + for (let i = 0; i < stats.length; i += 1) { + if (stats[i]) { + for (let n in NTIPAliasStat) { + let val; + + if (NTIPAliasStat.hasOwnProperty(n)) { + switch (typeof NTIPAliasStat[n]) { + case "number": + if (NTIPAliasStat[n] === i) { + switch (NTIPAliasStat[n]) { + case sdk.stats.ToBlock: + case sdk.stats.MinDamage: + case sdk.stats.MaxDamage: + case sdk.stats.SecondaryMinDamage: + case sdk.stats.SecondaryMaxDamage: + case sdk.stats.Defense: + case sdk.stats.AddClassSkills: + case sdk.stats.AddSkillTab: + case sdk.stats.ThrowMinDamage: + case sdk.stats.ThrowMaxDamage: + val = item.getStatEx(NTIPAliasStat[n]); + + if (val) { + dump[n] = val; + } + + break; + // poison damage stuff + case sdk.stats.PoisonMinDamage: + case sdk.stats.PoisonMaxDamage: + case sdk.stats.PoisonLength: + case sdk.stats.PoisonCount: + if (!dump.hasOwnProperty("poisondamage")) { + val = item.getStatEx(sdk.stats.PoisonMinDamage, 1); + + if (val) { + dump.poisondamage = val; + } + } + + break; + case sdk.stats.SkillOnAttack: + case sdk.stats.SkillOnStrike: + case sdk.stats.ChargedSkill: + if (stats[i]) { + dump[n] = stats[i].skill; + } + + break; + default: + if (stats[i][0]) { + dump[n] = stats[i][0]; + } + + break; + } + } + + break; + case "object": + val = item.getStatEx(NTIPAliasStat[n][0], NTIPAliasStat[n][1]); + if (val) { + dump[n] = val; + } + break; + } + } + } + } + } + + return dump; + }, + + /** + * 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 = ""; + const name = ( + unit.itemType + "_" + + unit.fname + .split("\n") + .reverse() + .join(" ") + .replace(/(y|ÿ)c[0-9!"+<:;.*]|\/|\\/g, "") + .trim() + ); + const color = unit.getColor(); + const code = Item.getItemCode(unit); + const sock = unit.getItemsEx(); + const { btoa } = require("../../modules/external/base64"); + const itemInfo = { + id: unit.classid, + code: unit.code, + mode: unit.mode, + name: name, + prefix: unit.prefix, + suffix: unit.suffix, + prefixes: unit.prefixes, + suffixes: unit.suffixes, + prefixnum: unit.prefixnum, + suffixnum: unit.suffixnum, + prefixnums: unit.prefixnums, + suffixnums: unit.suffixnums, + itemType: unit.itemType, + itemClass: unit.itemclass, + quality: unit.quality, + sockets: unit.sockets, + gfx: unit.gfx, + color: color, + ilvl: unit.ilvl, + lvlreq: unit.lvlreq, + strreq: unit.strreq, + dexreq: unit.dexreq, + flags: unit.getFlags(), + ethereal: unit.getFlag(sdk.items.flags.Ethereal), + runeword: unit.getFlag(sdk.items.flags.Runeword), + stats: MuleLogger.dumpItemStats(unit), + equipped: unit.isEquipped, + }; + + let desc = ( + Item.getItemDesc(unit, logIlvl) + "$" + + unit.gid + ":" + + unit.classid + ":" + + unit.location + ":" + + unit.x + ":" + + unit.y + ":" + + btoa(JSON.stringify(itemInfo)) + ":" + ); + + 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, + invTrans: (getBaseStat("items", unit.classid, "InvTrans") || 0), + 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")} */ + // @ts-ignore + while (!ItemDB.init(false)) { + delay(1000); + } + } + + let items = me.getItemsEx(); + if (!items.length) return; + + const realm = me.realm || "Single Player"; + let folder; + 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.header && (parsedItem.header = (me.account || "Single Player") + " / " + me.name); + 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/torch/OrgTorchData.js b/d2bs/kolbot/libs/systems/torch/OrgTorchData.js new file mode 100644 index 000000000..b24a89781 --- /dev/null +++ b/d2bs/kolbot/libs/systems/torch/OrgTorchData.js @@ -0,0 +1,68 @@ +/** +* @filename OrgTorchData.js +* @author theBGuy +* @desc Data file handling for OrgTorch.js +* +*/ + +(function (module) { + /** + * @typedef {Object} OrgTorchDataObject + * @property {string} gamename - The name of the game. + * @property {string} gamepassword - The password for the game. + * @property {number} active - The index of the active area. + * @property {Array} doneAreas - An array of completed areas. + */ + + const OrgTorchData = { + _path: "data/" + me.profile + "/orgtorch.json", + /** @type {OrgTorchDataObject} */ + _default: { gamename: "", gamepassword: "", active: -1, doneAreas: [] }, + + exists: function () { + return FileTools.exists(this._path); + }, + + create: function () { + if (!FileTools.exists("data/" + me.profile)) { + let folder = dopen("data"); + folder.create(me.profile); + } + + const obj = Object.assign({}, this._default); + + if (me.gamename) { + obj.gamename = me.gamename; + } + + if (me.gamepassword) { + obj.gamepassword = me.gamepassword; + } + + FileAction.write(this._path, JSON.stringify(obj)); + return obj; + }, + + /** @returns {OrgTorchDataObject} */ + read: function () { + try { + return FileAction.parse(this._path); + } catch (e) { + return this._default; + } + }, + + /** @param {Partial} newData */ + update: function (newData) { + let data = this.read(); + Object.assign(data, newData); + FileTools.writeText(this._path, JSON.stringify(data)); + }, + + remove: function () { + return FileTools.remove(this._path); + } + }; + + module.exports = OrgTorchData; +})(module); diff --git a/d2bs/kolbot/libs/systems/torch/TorchSystem.d.ts b/d2bs/kolbot/libs/systems/torch/TorchSystem.d.ts new file mode 100644 index 000000000..decb82181 --- /dev/null +++ b/d2bs/kolbot/libs/systems/torch/TorchSystem.d.ts @@ -0,0 +1,23 @@ +/// + +declare global { + namespace TorchSystem { + interface FarmerProfile { + KeyFinderProfiles: string[]; + FarmGame: string; + profile?: string; + } + + let FarmerProfiles: { [key: string]: FarmerProfile }; + let inGame: boolean; + let check: boolean; + + function getFarmers(): FarmerProfile[] | false; + function isFarmer(): FarmerProfile | false; + function inGameCheck(): boolean; + function keyCheck(): number[]; + function outOfGameCheck(): boolean; + function waitForKeys(): void; + } +} +export {}; \ No newline at end of file diff --git a/d2bs/kolbot/libs/systems/torch/TorchSystem.js b/d2bs/kolbot/libs/systems/torch/TorchSystem.js new file mode 100644 index 000000000..21623ddf9 --- /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())) { + console.log("ÿ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)) { + console.log("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)) { + console.log("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..31892a070 100644 --- a/d2bs/kolbot/sdk/globals.d.ts +++ b/d2bs/kolbot/sdk/globals.d.ts @@ -1,262 +1,1638 @@ -declare type PathNode = { x: number, y: number } +// @ts-nocheck +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +declare global { + type IncludePath = import("./types/include-paths").IncludePath; + type KolbotScript = import("./types/kolbot-scripts").KolbotScript; + type EventsInstance = InstanceType; + + /** + * @description A string that can be used in NTIP lists, which supports some additional syntax for item properties. + */ + type NipString = string; + + 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[]; + filterNull(): T[]; + filterHighDistance(step: number): any[]; + isEqual(t: T[]): boolean; + remove(val: T): T[]; + random(): T; + shuffle(): 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[]; + /** + * Finds the first element that matches the predicate and removes it from the array. + * @param predicate A function to test each element of the array. + * @returns The modified array. + */ + findAndRemove(predicate: (value: T, index: number, array: T[]) => boolean): this; + } + + 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 { + 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; + hasOwn(obj: T, prop: keyof T): boolean; + hasOwn(obj: T, prop: K): prop is keyof T; + } + + 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; + } + + /** + * Environment configuration + * @namespace + */ + interface Env { + /** + * Updates environment settings with provided values + * @param settings Object containing settings to update + * @returns The updated env object for chaining + */ + update(settings: Record): Env; + + /** + * Any additional custom properties + */ + [key: string]: any; + } + + /** + * Global environment object + * This object is lazily loaded when first accessed. + */ + const env: Env; + + 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; + click(): void; + hover(): 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: 1 | 2 | 3 | 4 | 5; + 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; + getMobCount(range: number, coll: number, type: number, noSpecialMobs: boolean): number; + checkForMobs(givenSettings: { range?: number; count?: number; coll?: number; spectype: number }): boolean; + } + + type PlayerType = 0; + class Player extends Unit { + public type: PlayerType; + readonly size: number; + } + + 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 isSoul: 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 isDruidVine: 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; + readonly size: number; + readonly isEnchantable: boolean; + + 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 prefix?: string; + readonly suffix?: string; + readonly prefixes: string[]; + readonly suffixes: string[]; + readonly prefixnum: number; + readonly suffixnum: number; + readonly prefixnums: 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; + readonly itemclass: 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 ItemInfo { + classid?: number; + itemtype?: number; + quality?: number; + runeword?: boolean; + ethereal?: boolean; + equipped?: boolean | number; + basetype?: boolean; + name?: string | number; + } + + interface MeType extends Unit { + 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; + readonly size: number; + /** + * @description max gold capacity (cLvl * 10000) + */ + readonly maxgold: number; + waypoints: boolean[]; + /** + * @private + * Don't use directly, use `me.shitList` + */ + _shitList: Set; + shitList: Set; + /** + * @private + * Don't use directly, use `me.qutting` + */ + _quitting: boolean; + quitting: boolean; + + // 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 }; + findFirst(itemInfo: ItemInfo): { 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; + update({ x, y }: { x?: number; y?: number }): void; + } + + class PathNode { + constructor(x: number, y: 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 D2BotProfile = { + type: number; + ip: number; + username: string; + gateway: string; + character: string; + difficulty: number; + maxloginTime: number; + maxCharacterSelectTime: number; + }; + function Profile(): D2BotProfile; + + 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: IncludePath): boolean; + function include(file: IncludePath): 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 = + | "itemaction" + | "gameevent" + | "copydata" + | "chatmsg" + | "chatinput" + | "whispermsg" + | "chatmsgblocker" + | "chatinputblocker" + | "whispermsgblocker" + | "mousemove" + | "ScreenHookHover" + | "mouseclick" + | "keyup" + | "keydownblocker" + | "keydown" + | "memana" + | "melife" + | "playerassign" + | "ScreenHookClick" + | "Command" + | "scriptmsg" + | "gamepacket" + | "gamepacketsent" + | "realmpacket"; + + function addEventListener(eventType: "realmpacket", callback: (bytes: ArrayBufferLike) => boolean): void; + 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" | "keydownblocker", + callback: (key: number | string) => void, + ): void; + function addEventListener( + eventType: "chatmsg" | "chatinput" | "whispermsg", + callback: (nick: string, msg: string) => void, + ): void; + function addEventListener( + eventType: "chatmsgblocker" | "chatinputblocker" | "whispermsgblocker", + callback: (arg1: string, arg2: string) => void, + ): void; + function addEventListener(eventType: "mousemove", callback: (arg1: string, arg2: string) => void): void; + function addEventListener(eventType: "ScreenHookHover", callback: (arg1: string, arg2: string) => void): void; + function addEventListener( + eventType: "mouseclick", + callback: (arg1: string, arg2: string, arg3: string, arg4: string) => void, + ): void; + function addEventListener(eventType: "memana" | "melife", callback: (arg1: string, arg2: string) => void): void; + function addEventListener(eventType: "playerassign", callback: (arg1: string, arg4: string) => void): void; + function addEventListener( + eventType: "ScreenHookClick", + callback: (arg1: any, arg2: any, arg3: any, arg4: any) => 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 { + log(...whatever: any[]): void; + debug(...whatever: any[]): void; + warn(...whatever: any[]): void; + error(...whatever: any[]): void; + time(name: string): void; + timeEnd(name: string): void; + trace(): void; + 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; + GameDescription: string; + + /** + * Seconds to wait before opening the join game window after a game + */ + JoinGameDelay: number; + 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; + ftjCount: number; + lastLocation: number[]; + + sayMsg(string: string): void; + /** + * Cache for waypoints by character name and difficulty + */ + waypointCache: { [charName: string]: number[] }; + + /** + * Handles script message events (object version) + * @param msg - The message object + */ + scriptMsgEvent(msg: string | object): void; + + /** + * Handles copy data event + * @param mode - The mode + * @param msg - The message (object or string) + */ + receiveCopyData(mode: number, msg: object | string): void; + + /** + * Returns a random string of given length + * @param len - Length of string + * @param useNumbers - Whether to use numbers + */ + randomString(len?: number, useNumbers?: boolean): string; + + /** + * Returns a random number string of given length + * @param len - Length of string + */ + randomNumberString(len?: number): string; + + /** + * Formats a timer string from a tick value + * @param tick - The tick value + */ + timer(tick: number): string; + + /** + * Waits for a location to change or timeout + * @param time - Timeout in ms + * @param location - Location to wait for + */ + locationTimeout(time: number, location: number): boolean; + + /** + * Sets the next game name in the stats + * @param gameInfo - Game info object + */ + setNextGame(gameInfo?: { gameName?: string }): void; + + /** + * Updates the game count and performs relog actions + */ + updateCount(): void; + + /** + * Location event handlers + */ + LocationEvents: { + selectDifficultySP(): boolean; + loginError(): void; + charSelectError(): boolean; + realmDown(): void; + waitingInLine(): void; + gameDoesNotExist(): void; + unableToConnect(): void; + openCreateGameWindow(): boolean; + openJoinGameWindow(): void; + login(otherMultiCheck?: boolean): boolean; + otherMultiplayerSelect(): void; + charSelectConnecting(): boolean; + }; + } + + 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]; + + /** + * This method sleeps the caller thread for the duration in ms passed to it + * @note Use sparingly, this method stops the background workers on the callers thread + */ + function nativeDelay(ms: number): void; + + /** + * @description blocks the thread for specified milliseconds - use sparingly this does not yield to the other threads so it + * can potentially cause the heartbeat thread to crash + * @param ms + */ + function hardDelay(ms: number): void; + + /** + * Deep merge objects, handling nested properties properly + * @param target - Target object to merge into + * @param source - Source object to merge from + * @returns Merged object + */ + function deepMerge(target: object, source: object): object; } - -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..1d566806b --- /dev/null +++ b/d2bs/kolbot/sdk/types/Attack.d.ts @@ -0,0 +1,123 @@ +declare global { + type DamageType = "physical" | "fire" | "lightning" | "magic" | "cold" | "poison" | "holybolt"; + + interface AttackResult { + 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 + FAILED_POSITION: 5; + } + + namespace Attack { + const infinity: boolean; + const auradin: boolean; + const monsterObjects: Set; + 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; + + interface SecurePositionOptions { + range?: number; + timer?: number; + skipBlocked?: boolean; + useRedemption?: boolean; + skipIds?: number[]; + /** + * @default 300000 (5 minutes) + * @description Timeout in milliseconds for attempting to secure area. + */ + timeout?: number; + } + function securePosition(x: number, y: number, options: SecurePositionOptions): boolean; + function countUniques(): void; + function storeStatistics(area: number): void; + function clearRoom(room: Room, spectype?: number): boolean; + function clearLevel(spectype?: number): boolean; + function sortMonsters(unitA: Unit, unitB: Unit): boolean; + function validSpot(x: number, y: number, skill?: number, unitid?: number): boolean; + /** @deprecated Use Misc.openChests instead */ + 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: number, + xmax: number, + ymin: number, + ymax: number, + spread: number, + ): { + x: number; + y: number; + 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?: { + skipBlocked?: boolean; + skipImmune?: boolean; + skipGid?: number; + }): Monster | false; + 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; + } +} +export {}; 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/ClassAttack.d.ts b/d2bs/kolbot/sdk/types/ClassAttack.d.ts new file mode 100644 index 000000000..c20426fbc --- /dev/null +++ b/d2bs/kolbot/sdk/types/ClassAttack.d.ts @@ -0,0 +1,40 @@ +type AttackModules = + | SDK["player"]["class"]["Amazon"] + | SDK["player"]["class"]["Assassin"] + | SDK["player"]["class"]["Barbarian"] + | SDK["player"]["class"]["Druid"] + | SDK["player"]["class"]["Sorceress"] + | SDK["player"]["class"]["Paladin"] + | SDK["player"]["class"]["Necromancer"] + | "Wereform"; + +declare global { + interface IClassAttack { + load: (moduleName: AttackModules) => unknown; + + [sdk.player.class.Amazon]: typeof import("../../libs/core/Attacks/Amazon"); + [sdk.player.class.Assassin]: typeof import("../../libs/core/Attacks/Assassin"); + [sdk.player.class.Barbarian]: typeof import("../../libs/core/Attacks/Barbarian"); + [sdk.player.class.Druid]: typeof import("../../libs/core/Attacks/Druid"); + [sdk.player.class.Sorceress]: typeof import("../../libs/core/Attacks/Sorceress"); + [sdk.player.class.Paladin]: typeof import("../../libs/core/Attacks/Paladin"); + [sdk.player.class.Necromancer]: typeof import("../../libs/core/Attacks/Necromancer"); + Wereform: typeof import("../../libs/core/Attacks/Wereform"); + + /** + * @deprecated Use the specific class attack modules instead. ClassAttack[me.classid].doAttack(...) + */ + doAttack: (unit: Monster | Player, preattack?: boolean) => AttackResult; + /** + * @deprecated Use the specific class attack modules instead. ClassAttack[me.classid].afterAttack() + */ + afterAttack: () => void; + /** + * @deprecated Use the specific class attack modules instead. ClassAttack[me.classid].doCast(...) + */ + doCast: (unit: Monster | Player, timedSkill: number, untimedSkill: number) => AttackResult; + } + + const ClassAttack: IClassAttack; +} +export {}; diff --git a/d2bs/kolbot/sdk/types/CollMap.d.ts b/d2bs/kolbot/sdk/types/CollMap.d.ts new file mode 100644 index 000000000..abe09e456 --- /dev/null +++ b/d2bs/kolbot/sdk/types/CollMap.d.ts @@ -0,0 +1,139 @@ +declare global { + interface CollMapRoom { + x: number; + y: number; + xsize: number; + ysize: number; + } + + interface CollMapHook { + room: Room; + lines: Line[]; + } + + interface CollMapColors { + readonly green: 0x84; + readonly red: 0x0a; + readonly black: 0x00; + readonly white: 0xff; + readonly purple: 0x9b; + readonly blue: 0x97; + } + + type CollMapColorName = "green" | "red" | "black" | "white" | "purple" | "blue"; + + interface CollMapInstance { + rooms: CollMapRoom[]; + maps: number[][][]; + hooks: CollMapHook[]; + readonly colors: CollMapColors; + + /** + * Draw room outline on screen + * @param room The room to draw + * @param color Color name or color code + * @param update Whether to update existing drawing + */ + drawRoom(room: Room, color?: CollMapColorName | number, update?: boolean): void; + + /** + * Remove hook for specific room + * @param room The room to remove hook for + */ + removeHookForRoom(room: Room): void; + + /** + * Remove all hooks + */ + removeHooks(): void; + + /** + * Get nearby rooms for given coordinates + * @param x X coordinate + * @param y Y coordinate + * @returns Success status + */ + getNearbyRooms(x: number, y: number): boolean; + + /** + * Add room to collision map + * @param x Room object or X coordinate + * @param y Y coordinate (optional if first param is Room) + * @returns Success status + */ + addRoom(x: Room | number, y?: number): boolean; + + /** + * Get collision value at coordinates + * @param x X coordinate + * @param y Y coordinate + * @param cacheOnly Only check cache + * @returns Collision value + */ + getColl(x: number, y: number, cacheOnly?: boolean): number; + + /** + * Get room index for coordinates + * @param x X coordinate + * @param y Y coordinate + * @param cacheOnly Only check cache + * @returns Room index or undefined + */ + getRoomIndex(x: number, y: number, cacheOnly?: boolean): number | undefined; + + /** + * Check if coordinates are in room + * @param x X coordinate + * @param y Y coordinate + * @param room Room to check + * @returns Whether coordinates are in room + */ + coordsInRoom(x: number, y: number, room: CollMapRoom): boolean; + + /** + * Reset collision map + */ + reset(): void; + + /** + * Check collision between two units + * @param unitA First unit + * @param unitB Second unit + * @param coll Collision flags + * @param thickness Line thickness + * @returns Whether collision exists + */ + checkColl(unitA: Unit | PathNode, unitB: Unit | PathNode, coll: number, thickness?: number): boolean; + + /** + * Get teleport point for room + * @param room Room to get teleport point for + * @returns Teleport point or null + */ + getTelePoint(room: Room): PathNode | null; + + /** + * Get random valid coordinate + * @param cX Center X coordinate + * @param xmin Minimum X offset + * @param xmax Maximum X offset + * @param cY Center Y coordinate + * @param ymin Minimum Y offset + * @param ymax Maximum Y offset + * @param factor Scale factor + * @returns Random valid coordinate + */ + getRandCoordinate( + cX: number, + xmin: number, + xmax: number, + cY: number, + ymin: number, + ymax: number, + factor?: number, + ): PathNode; + } + + const CollMap: CollMapInstance; +} +export {}; diff --git a/d2bs/kolbot/sdk/types/Common.d.ts b/d2bs/kolbot/sdk/types/Common.d.ts new file mode 100644 index 000000000..7ddffdda8 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Common.d.ts @@ -0,0 +1,15 @@ +declare global { + const Common: { + load: (moduleName: string) => unknown; + + Ancients: typeof import("../../libs/core/Common/Ancients"); + Baal: typeof import("../../libs/core/Common/Baal"); + Cain: typeof import("../../libs/core/Common/Cain"); + Cows: typeof import("../../libs/core/Common/Cows"); + Diablo: typeof import("../../libs/core/Common/Diablo") & EventsInstance; + Leecher: typeof import("../../libs/core/Common/Leecher"); + Smith: typeof import("../../libs/core/Common/Smith"); + Toolsthread: typeof import("../../libs/core/Common/Tools"); + }; +} +export {}; diff --git a/d2bs/kolbot/sdk/types/Config.d.ts b/d2bs/kolbot/sdk/types/Config.d.ts new file mode 100644 index 000000000..8adbfe341 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Config.d.ts @@ -0,0 +1,672 @@ +export type DiabloSeal = "vizier" | "seis" | "infector"; + +declare global { + // interface Scripts { [data: string]: Partial | boolean } + type ExtendedCubingOpts = { Ethereal: number; MaxQuantity: number; condition: () => boolean }; + type CubingRecipe = [number, string] | [number, string, number] | [number, string, ExtendedCubingOpts]; + + interface IConfig { + init(notify: boolean): void; + Loaded: boolean; + DebugMode: { + Path: boolean; + Stack: boolean; + Memory: boolean; + Skill: boolean; + Town: boolean; + Shrines: boolean; + }; + + // Experimental + FastParty: boolean; + AutoEquip: boolean; + UseExperimentalAvoid: boolean; + /** + * Enables experimental walk clear level feature for non-teleporters + */ + UseExperimentalClearLevel: boolean; + + StartDelay: number; + PickDelay: number; + AreaDelay: number; + MinGameTime: number; + MaxGameTime: number; + UnpartyForMinGameTimeWait: boolean; + 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[][]; + SortSettings: { + SortInventory: boolean; + SortStash: boolean; + PlugYStash: boolean; + ItemsSortedFromLeft: number[]; + ItemsSortedFromRight: number[]; + PrioritySorting: boolean; + ItemsSortedFromLeftPriority: number[]; + ItemsSortedFromRightPriority: 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: number[]; + SkipEnchant: string[]; + SkipImmune: string[]; + SkipAura: string[]; + SkipException: (number | string)[]; + AdvancedSkipCheck: ( + | { + classid?: number; + name?: string; + spectype?: number; + enchant?: number[]; + aura?: number[]; + immunity?: DamageType[]; + } + | ((unit: Monster) => boolean) + )[]; + ImmunityException: DamageType[]; + ScanShrines: number[]; + AutoShriner: boolean; + UseWells: { + HpPercent: number, + MpPercent: number, + StaminaPercent: number, + StatusEffects: boolean, + }; + 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: CubingRecipe[]; + MakeRunewords: boolean; + /** + * runeword, item name or id, ethereal (Roll.Eth, Roll.NonEth, Roll.Any or undefined), priority (number or undefined) + * @example [Runeword.Enigma, 'Archon Plate', Roll.NonEth, 100] + */ + Runewords: [runeword, string | number, boolean | undefined, number | undefined][]; + KeepRunewords: any[]; + LadderOverride: boolean; + 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; + 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]; preAttack: 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; + }; + + Advertise: { + Enabled: boolean; + Message: string | string[]; + Interval: [number, number] | number; + }; + + 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; + Silent: boolean; + }; + 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[]; + HurtBaal: number; + }; + BaalHelper: { + Wait: number; + KillNihlathak: boolean; + FastChaos: boolean; + DollQuit: boolean; + SoulQuit: boolean; + KillBaal: boolean; + SkipTP: boolean; + HurtBaal: number; + }; + 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: DiabloSeal[]; + }; + DiabloHelper: { + Wait: number; + Entrance: boolean; + SkipIfBaal: boolean; + SkipTP: boolean; + OpenSeals: boolean; + SafePrecast: boolean; + ClearRadius: number; + SealOrder: DiabloSeal[]; + RecheckSeals: boolean; + HurtDiablo: number; + }; + AutoChaos: { + Leader: string; + /** + * -1 = go to town during diablo, 0 = kill to death, x > 0 = kill to x% + */ + Diablo: number; + Taxi: boolean; + /** + * set true to search for shrine only + */ + FindShrine: boolean; + /** + * true = get shrine from act 1 (requires another character running FindShrine) + */ + UseShrine: boolean; + /** + * set true for low level EXP glitcher (unimplemented) + */ + Glitcher: boolean; + /** + * true = don't enter seals after boing at river, false = normal character that fights + */ + BO: boolean; + /** + * true = hide during diablo, false = stay at star + */ + Leech: boolean; + /** + * true = ranged character, false = melee character + */ + Ranged: boolean; + /** + * Classes required to start the chaos run set to true to require that class + */ + RequireClass: Record; + /** + * true = does precast sequence at every seal, false = does not precast at seal + */ + SealPrecast: boolean; + /** + * preattack count at each seal, useful for clearing tp's for safer entry, + * enter values in the following order: [/vizier/, /seis/, /infector/] + */ + PreAttack: number[]; + /** + * order in which the taxi will go through cs, 1: vizier, 2: seis, 3: infector + */ + SealOrder: DiabloSeal[]; + /** + * number of seconds to wait before entering hot tp + */ + SealDelay: number; + }; + MFHelper: { + BreakClearLevel: boolean; + BreakOnDiaBaal: 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; + }; + Idle: { + Advertise: boolean; + AdvertiseMessage: string; + MaxGameLength: 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; + 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; + MinGameLength: number; + NGVoting: boolean; + NGVoteCooldown: 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[]; + }; + }; + }; + OrgTorchHelper: { + Taxi: boolean; + Helper: boolean; + SkipTp: boolean; + GetFade: boolean; + UseWalkPath: boolean; + }; + 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: IConfig; +} 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..3daa01aae --- /dev/null +++ b/d2bs/kolbot/sdk/types/Loader.d.ts @@ -0,0 +1,56 @@ +declare global { + type GlobalScript = () => boolean; + type ScriptContext = { [key: string]: unknown }; + + interface RunnableOptions { + setup?: (ctx: TContext) => void; + preAction?: (ctx: TContext) => void; + postAction?: (ctx: TContext) => void; + cleanup?: (ctx: TContext) => void; + forceTown?: boolean; + bossid?: number; + startArea?: number; + } + + // @ts-ignore + class Runnable { + constructor(action: (ctx: TContext) => boolean, options: Partial>); + + action: (ctx: TContext) => boolean; + startArea: number | null; + setup: ((ctx: TContext) => void) | null; + preAction: (ctx: TContext) => void; + postAction: ((ctx: TContext) => void) | null; + cleanup: ((ctx: TContext) => void) | 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 | (() => void)): boolean; + function scriptName(offset?: number): string; + } + + type Scripts = { + [key in KolbotScript]: boolean; + }; + + const Scripts: Scripts; +} +export {}; diff --git a/d2bs/kolbot/sdk/types/Misc.d.ts b/d2bs/kolbot/sdk/types/Misc.d.ts new file mode 100644 index 000000000..bac30a3f2 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Misc.d.ts @@ -0,0 +1,56 @@ +export {}; + +declare global { + interface Misc { + _diabloSpawned: boolean; + screenshotErrors: boolean; + errorConsolePrint: boolean; + useItemLog: boolean; + shrineStates: number[] | null; + _shrinerIgnore: Set; + lastShrine: { + tick: number; + duration: number; + type: number; + state: number; + update(unit: ObjectUnit): void; + remaining(): number; + isMyCurrentState(): boolean; + }; + + click(button: number, shift: number, x?: number | Unit, y?: number): boolean; + inMyParty(name: string): boolean; + findPlayer(name: string): Party | false; + getPlayerUnit(name: string): Player | false; + getPlayerAct(player: Party | string): number | false; + getNearbyPlayerCount(): number; + getPlayerCount(): number; + getPartyCount(): number; + getPartyMembers(): Party[]; + checkPartyLevel(levelCheck?: number, exclude?: string | string[]): boolean; + getPlayerArea(player: Party | string): number | false; + autoLeaderDetect(givenSettings?: { + destination?: number | number[]; + quitIf?: (area: number) => boolean; + timeout?: number; + }): string | false; + openChest(unit: Unit | number): boolean; + openChestsInArea(area?: number, chestIds?: number[]): boolean; + openChests(range?: number): boolean; + shriner(ignore: number[]): boolean; + scanShrines(range: number, ignore: number[]): boolean; + getShrine(unit: ObjectUnit): boolean; + getShrinesInArea(area: number, type: number, use: boolean): boolean; + /** @deprecated */ + townCheck(): boolean; + spy(name: string): boolean; + errorReport(error: Error | string, script?: string): void; + debugLog(msg: string): void; + useMenu(id: number): boolean; + poll(check: () => T, timeout?: number, sleep?: number, useNativeDelay?: boolean): T | false; + getUIFlags(excluded?: number[]): number[] | null; + getQuestStates(questId: number): number[]; + } + + const Misc: Misc; +} 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..63a5a642c --- /dev/null +++ b/d2bs/kolbot/sdk/types/NTIP.d.ts @@ -0,0 +1,41 @@ +export {}; +declare global { + const NTIPAliasType: Record; + const NTIPAliasClassID: Record; + const NTIPAliasCodes: 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..0a2e50c15 --- /dev/null +++ b/d2bs/kolbot/sdk/types/OOG.d.ts @@ -0,0 +1,188 @@ +// @ts-nocheck +declare global { + namespace DataFile { + const _path: string; + const _default: { + handle: number; + name: string; + level: number; + experience: number; + gold: number; + deaths: number; + runs: number; + lastArea: string; + ingameTick: number; + gameName: string; + currentGame: string; + nextGame: string; + }; + + function init(): boolean; + function create(): typeof _default; + function read(profile: string): typeof _default | null; + function getObj(): typeof _default; + function getStats(): typeof _default; + function updateStats(arg: string | string[], 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; + } + + interface D2BotProfileInfo { + Name: string; + Status: string; + Account: string; + Character: string; + Difficulty: string; + Realm: string; + Game: string; + Entry: string; + Tag: string; + } + + 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(): D2BotProfileInfo + 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; + + type Difficulty = 'Normal' | 'Nightmare' | 'Hell' | 'Highest'; + + 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: Difficulty, 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..bade1445b --- /dev/null +++ b/d2bs/kolbot/sdk/types/Pather.d.ts @@ -0,0 +1,109 @@ +export {}; +declare global { + interface PathDebug { + enableHooks: boolean; + paths: Map; + drawPath(id: number, path: PathNode[]): void; + removeHooks(id: number): void; + coordsInPath(path: PathNode[], x: number, y: number): boolean; + } + const PathDebug: PathDebug; + + 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 findSpotAtDistance(node: PathNode, distance: number, maxAttempts?: number): PathNode | false; + 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..908d5d148 --- /dev/null +++ b/d2bs/kolbot/sdk/types/Pickit.d.ts @@ -0,0 +1,40 @@ +export {}; +declare global { + type PickitResult = { + UNID: -1, + UNWANTED: 0, + WANTED: 1, + CUBING: 2, + RUNEWORD: 3, + TRASH: 4, + CRAFTING: 5, + UTILITY: 6 + }; + namespace Pickit { + const enabled: boolean; + 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[]; + const systemKeep: { reason: string, gid: 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..772ef2f4e --- /dev/null +++ b/d2bs/kolbot/sdk/types/Town.d.ts @@ -0,0 +1,88 @@ +// @ts-nocheck +declare global { + namespace Town { + let telekinesis: boolean; + let sellTimer: number; + let lastChores: number; + const dontStashGids: Set; + let choresActive: boolean; + + const tasks: Map< + Act, + { + Heal: NPC; + Shop: NPC; + Gamble: NPC; + Repair: NPC; + Merc: NPC; + Key: NPC; + CainID: NPC; + } + >; + const ignoredItemTypes: number[]; + 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; + function prepareForGemShrine(): 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/include-paths.d.ts b/d2bs/kolbot/sdk/types/include-paths.d.ts new file mode 100644 index 000000000..22dacab02 --- /dev/null +++ b/d2bs/kolbot/sdk/types/include-paths.d.ts @@ -0,0 +1,2 @@ +// Auto-generated include types for kolbot libs +export type IncludePath = "OOG.js" | "json2.js" | "core/Me.js" | "globals.js" | "require.js" | "core/NPC.js" | "critical.js" | "Polyfill.js" | "core/Item.js" | "core/Misc.js" | "core/Town.js" | "core/Util.js" | "oog/D2Bot.js" | "core/Skill.js" | "core/Attack.js" | "core/Common.js" | "core/Config.js" | "core/Cubing.js" | "core/Loader.js" | "core/Packet.js" | "core/Pather.js" | "core/Pickit.js" | "scripts/Pit.js" | "config/Druid.js" | "core/CollMap.js" | "core/Precast.js" | "core/Storage.js" | "oog/DataFile.js" | "oog/ShitList.js" | "scripts/Baal.js" | "scripts/Cows.js" | "scripts/Idle.js" | "scripts/Test.js" | "config/Amazon.js" | "oog/Locations.js" | "scripts/Izual.js" | "scripts/Smith.js" | "scripts/Synch.js" | "scripts/Tombs.js" | "scripts/Wakka.js" | "config/Paladin.js" | "core/Runewords.js" | "oog/FileAction.js" | "scripts/Diablo.js" | "scripts/Duriel.js" | "scripts/Endugu.js" | "scripts/Gamble.js" | "scripts/Rushee.js" | "scripts/Rusher.js" | "scripts/Synch2.js" | "config/Assassin.js" | "core/Experience.js" | "core/Prototypes.js" | "manualplay/main.js" | "scripts/Abaddon.js" | "scripts/BoneAsh.js" | "scripts/Bonesaw.js" | "scripts/Eyeback.js" | "scripts/GetCube.js" | "scripts/GetFade.js" | "scripts/GetKeys.js" | "scripts/Icehawk.js" | "scripts/ShopBot.js" | "config/Barbarian.js" | "config/Sorceress.js" | "scripts/Andariel.js" | "scripts/AutoBaal.js" | "scripts/Coldcrow.js" | "scripts/Coldworm.js" | "scripts/Countess.js" | "scripts/Crafting.js" | "scripts/Eldritch.js" | "scripts/Fangskin.js" | "scripts/Follower.js" | "scripts/Hephasto.js" | "scripts/IPHunter.js" | "scripts/Mephisto.js" | "scripts/MFHelper.js" | "scripts/OrgTorch.js" | "scripts/Questing.js" | "scripts/Radament.js" | "scripts/Snapchip.js" | "scripts/Summoner.js" | "scripts/Treehead.js" | "scripts/Tristram.js" | "scripts/WPGetter.js" | "SoloPlay/Core/Me.js" | "core/NTItemParser.js" | "scripts/Bishibosh.js" | "scripts/CrushTele.js" | "scripts/GemHunter.js" | "scripts/Mausoleum.js" | "scripts/Nihlathak.js" | "scripts/Rakanishu.js" | "scripts/Stormtree.js" | "scripts/Travincal.js" | "scripts/UserAddon.js" | "SoloPlay/SoloPlay.js" | "config/Necromancer.js" | "core/Attacks/Druid.js" | "core/Auto/AutoStat.js" | "manualplay/MapMode.js" | "scripts/BaalHelper.js" | "scripts/ChestMania.js" | "scripts/ControlBot.js" | "scripts/Corpsefire.js" | "scripts/KillDclone.js" | "scripts/Pindleskin.js" | "scripts/SharpTooth.js" | "scripts/Worldstone.js" | "core/Attacks/Amazon.js" | "core/Auto/AutoBuild.js" | "core/Auto/AutoSkill.js" | "scripts/Frozenstein.js" | "scripts/GetEssences.js" | "scripts/SealLeecher.js" | "SoloPlay/Core/Quest.js" | "config/_CustomConfig.js" | "core/Attacks/Paladin.js" | "scripts/BattleOrders.js" | "scripts/BoBarbHelper.js" | "scripts/ClearAnyArea.js" | "scripts/DiabloHelper.js" | "scripts/GhostBusters.js" | "scripts/OuterSteppes.js" | "scripts/ThreshSocket.js" | "SoloPlay/Scripts/den.js" | "SoloPlay/Scripts/eye.js" | "core/Attacks/Assassin.js" | "core/Attacks/Wereform.js" | "manualplay/libs/Hooks.js" | "scripts/_Abaddon copy.js" | "scripts/BaalAssistant.js" | "scripts/DeveloperMode.js" | "scripts/KurastTemples.js" | "scripts/TristramLeech.js" | "SoloPlay/Config/Druid.js" | "SoloPlay/Core/Globals.js" | "SoloPlay/Scripts/anya.js" | "SoloPlay/Scripts/baal.js" | "SoloPlay/Scripts/cave.js" | "SoloPlay/Scripts/cows.js" | "SoloPlay/Scripts/cube.js" | "SoloPlay/Scripts/jail.js" | "SoloPlay/Scripts/nith.js" | "SoloPlay/Scripts/pits.js" | "starter/StarterConfig.js" | "systems/automule/main.js" | "systems/automule/Mule.js" | "config/_BaseConfigFile.js" | "core/Attacks/Barbarian.js" | "core/Attacks/Sorceress.js" | "core/GameData/AreaData.js" | "core/GameData/GameData.js" | "core/GameData/RuneData.js" | "scripts/AncientTunnels.js" | "scripts/OrgTorchHelper.js" | "scripts/TravincalLeech.js" | "SoloPlay/Config/Amazon.js" | "SoloPlay/OOG/SoloEntry.js" | "SoloPlay/Scripts/brain.js" | "SoloPlay/Scripts/heart.js" | "SoloPlay/Scripts/izual.js" | "SoloPlay/Scripts/river.js" | "SoloPlay/Scripts/shenk.js" | "SoloPlay/Scripts/smith.js" | "SoloPlay/Scripts/staff.js" | "SoloPlay/Scripts/tombs.js" | "SoloPlay/Tools/Overlay.js" | "SoloPlay/Tools/Tracker.js" | "starter/AdvancedConfig.js" | "core/GameData/QuestData.js" | "core/GameData/SkillData.js" | "manualplay/config/Druid.js" | "scripts/CreepingFeature.js" | "SoloPlay/Config/Paladin.js" | "SoloPlay/Core/AutoBuild.js" | "SoloPlay/Core/Mercenary.js" | "SoloPlay/Core/NPCAction.js" | "SoloPlay/Core/Polyfills.js" | "SoloPlay/Core/SoloWants.js" | "SoloPlay/Scripts/amulet.js" | "SoloPlay/Scripts/diablo.js" | "SoloPlay/Scripts/duriel.js" | "SoloPlay/Scripts/pindle.js" | "SoloPlay/Tools/CharData.js" | "core/Attacks/Necromancer.js" | "core/GameData/ShrineData.js" | "manualplay/config/Amazon.js" | "scripts/BattlemaidSarina.js" | "SoloPlay/Config/Assassin.js" | "SoloPlay/Core/CharmEquip.js" | "SoloPlay/Core/SoloEvents.js" | "SoloPlay/Scripts/boneash.js" | "SoloPlay/Scripts/eyeback.js" | "SoloPlay/Scripts/fireeye.js" | "SoloPlay/Scripts/getkeys.js" | "SoloPlay/Tools/Developer.js" | "SoloPlay/Tools/SoloIndex.js" | "config/Builds/Class.Build.js" | "core/GameData/MonsterData.js" | "core/GameData/NTItemAlias.js" | "manualplay/config/Paladin.js" | "SoloPlay/Config/Barbarian.js" | "SoloPlay/Config/Sorceress.js" | "SoloPlay/OOG/OOGOverrides.js" | "SoloPlay/Scripts/a1chests.js" | "SoloPlay/Scripts/a5chests.js" | "SoloPlay/Scripts/ancients.js" | "SoloPlay/Scripts/andariel.js" | "SoloPlay/Scripts/countess.js" | "SoloPlay/Scripts/hephasto.js" | "SoloPlay/Scripts/lamessen.js" | "SoloPlay/Scripts/mephisto.js" | "SoloPlay/Scripts/orgtorch.js" | "SoloPlay/Scripts/radament.js" | "SoloPlay/Scripts/summoner.js" | "SoloPlay/Scripts/treehead.js" | "SoloPlay/Scripts/tristram.js" | "systems/automule/AutoMule.js" | "systems/autorush/AutoRush.js" | "systems/gambling/Gambling.js" | "systems/torch/TorchSystem.js" | "manualplay/config/Assassin.js" | "manualplay/hooks/ItemHooks.js" | "manualplay/hooks/TextHooks.js" | "scripts/UndergroundPassage.js" | "SoloPlay/Core/DynamicTiers.js" | "SoloPlay/OOG/StarterConfig.js" | "SoloPlay/Scripts/bishibosh.js" | "SoloPlay/Scripts/hellforge.js" | "SoloPlay/Scripts/mausoleum.js" | "SoloPlay/Scripts/savebarby.js" | "SoloPlay/Scripts/travincal.js" | "systems/torch/FarmerConfig.js" | "systems/torch/OrgTorchData.js" | "manualplay/config/Barbarian.js" | "manualplay/config/Sorceress.js" | "SoloPlay/Config/Necromancer.js" | "SoloPlay/Core/ItemOverrides.js" | "SoloPlay/Core/ItemUtilities.js" | "SoloPlay/Core/MiscOverrides.js" | "SoloPlay/Core/NTIPOverrides.js" | "SoloPlay/Core/TownOverrides.js" | "SoloPlay/Scripts/bloodraven.js" | "SoloPlay/Scripts/corpsefire.js" | "SoloPlay/Scripts/maggotlair.js" | "SoloPlay/Scripts/templeruns.js" | "SoloPlay/Scripts/worldstone.js" | "systems/autorush/RushConfig.js" | "systems/follow/FollowConfig.js" | "core/GameData/LocaleStringID.js" | "manualplay/hooks/ActionHooks.js" | "manualplay/hooks/ShrineHooks.js" | "manualplay/hooks/VectorHooks.js" | "manualplay/threads/MapHelper.js" | "SoloPlay/Core/ItemPrototypes.js" | "SoloPlay/Core/SkillOverrides.js" | "SoloPlay/Scripts/beetleburst.js" | "SoloPlay/Scripts/lowerkurast.js" | "systems/crafting/TeamsConfig.js" | "systems/gambling/TeamsConfig.js" | "manualplay/config/Necromancer.js" | "manualplay/hooks/MonsterHooks.js" | "manualplay/libs/MiscOverrides.js" | "manualplay/libs/TownOverrides.js" | "manualplay/threads/PickThread.js" | "scripts/ClassicChaosAssistant.js" | "SoloPlay/Core/AttackOverrides.js" | "SoloPlay/Core/ConfigOverrides.js" | "SoloPlay/Core/CubingOverrides.js" | "SoloPlay/Core/LoaderOverrides.js" | "SoloPlay/Core/PatherOverrides.js" | "SoloPlay/Core/PickitOverrides.js" | "systems/channel/ChannelConfig.js" | "systems/cleaner/CleanerConfig.js" | "systems/gameaction/GameAction.js" | "systems/mulelogger/MuleLogger.js" | "systems/pubjoin/PubJoinConfig.js" | "SoloPlay/Core/PrecastOverrides.js" | "SoloPlay/Core/StorageOverrides.js" | "SoloPlay/Scripts/developermode.js" | "manualplay/libs/AttackOverrides.js" | "manualplay/libs/ConfigOverrides.js" | "manualplay/libs/PatherOverrides.js" | "manualplay/libs/PickitOverrides.js" | "SoloPlay/BuildFiles/druid/druid.js" | "SoloPlay/Core/AutoMuleOverrides.js" | "SoloPlay/Core/AutoStatOverrides.js" | "SoloPlay/Scripts/ancienttunnels.js" | "systems/crafting/CraftingSystem.js" | "systems/mulelogger/LoggerConfig.js" | "SoloPlay/Core/PrototypeOverrides.js" | "SoloPlay/Core/RunewordsOverrides.js" | "SoloPlay/Scripts/creepingfeature.js" | "manualplay/threads/MapToolsThread.js" | "SoloPlay/BuildFiles/amazon/amazon.js" | "SoloPlay/BuildFiles/Runewords/Ice.js" | "SoloPlay/Core/MuleloggerOverrides.js" | "SoloPlay/BuildFiles/Runewords/Bone.js" | "SoloPlay/BuildFiles/Runewords/Fury.js" | "SoloPlay/BuildFiles/Runewords/Lore.js" | "SoloPlay/BuildFiles/Runewords/Myth.js" | "systems/automule/config/MuleConfig.js" | "SoloPlay/BuildFiles/paladin/paladin.js" | "SoloPlay/BuildFiles/Runewords/Chaos.js" | "SoloPlay/BuildFiles/Runewords/Exile.js" | "SoloPlay/BuildFiles/Runewords/Faith.js" | "SoloPlay/BuildFiles/Runewords/Grief.js" | "SoloPlay/BuildFiles/Runewords/Honor.js" | "SoloPlay/BuildFiles/Runewords/Rhyme.js" | "SoloPlay/BuildFiles/Runewords/Smoke.js" | "SoloPlay/BuildFiles/Runewords/Steel.js" | "SoloPlay/BuildFiles/Runewords/White.js" | "systems/gameaction/GameActionConfig.js" | "config/Builds/Sorceress.ExampleBuild.js" | "SoloPlay/BuildFiles/Runewords/Duress.js" | "SoloPlay/BuildFiles/Runewords/Enigma.js" | "SoloPlay/BuildFiles/Runewords/Malice.js" | "SoloPlay/BuildFiles/assassin/assassin.js" | "SoloPlay/BuildFiles/Runewords/Silence.js" | "SoloPlay/BuildFiles/Runewords/Stealth.js" | "systems/automule/config/StarterConfig.js" | "SoloPlay/BuildFiles/Runewords/LastWish.js" | "SoloPlay/BuildFiles/Runewords/MercDoom.js" | "systems/automule/config/TorchAnniMules.js" | "SoloPlay/BuildFiles/barbarian/barbarian.js" | "SoloPlay/BuildFiles/Runewords/DreamHelm.js" | "SoloPlay/BuildFiles/Runewords/Fortitude.js" | "SoloPlay/BuildFiles/Runewords/MercPride.js" | "SoloPlay/BuildFiles/Runewords/Sanctuary.js" | "SoloPlay/BuildFiles/Runewords/Treachery.js" | "SoloPlay/BuildFiles/sorceress/sorceress.js" | "SoloPlay/BuildFiles/Runewords/CallToArms.js" | "SoloPlay/BuildFiles/Runewords/KingsGrace.js" | "SoloPlay/BuildFiles/Runewords/Lawbringer.js" | "SoloPlay/BuildFiles/druid/druid.WindBuild.js" | "SoloPlay/BuildFiles/druid/druid.WolfBuild.js" | "SoloPlay/BuildFiles/Runewords/DragonArmor.js" | "SoloPlay/BuildFiles/Runewords/DreamShield.js" | "SoloPlay/BuildFiles/Runewords/MercInsight.js" | "SoloPlay/BuildFiles/Runewords/SpiritSword.js" | "SoloPlay/BuildFiles/druid/druid.StartBuild.js" | "SoloPlay/BuildFiles/Runewords/CrescentMoon.js" | "SoloPlay/BuildFiles/Runewords/MercInfinity.js" | "SoloPlay/BuildFiles/Runewords/SpiritShield.js" | "SoloPlay/BuildFiles/necromancer/necromancer.js" | "SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js" | "SoloPlay/BuildFiles/Runewords/HandOfJustice.js" | "SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js" | "SoloPlay/BuildFiles/Runewords/MercFortitude.js" | "SoloPlay/BuildFiles/Runewords/MercTreachery.js" | "SoloPlay/BuildFiles/Runewords/PhoenixShield.js" | "SoloPlay/BuildFiles/Runewords/VoiceOfReason.js" | "SoloPlay/BuildFiles/amazon/amazon.StartBuild.js" | "SoloPlay/BuildFiles/amazon/amazon.WfzonBuild.js" | "SoloPlay/BuildFiles/Runewords/AncientsPledge.js" | "SoloPlay/BuildFiles/Runewords/PDiamondShield.js" | "SoloPlay/BuildFiles/druid/druid.FirewolfBuild.js" | "SoloPlay/BuildFiles/druid/druid.LevelingBuild.js" | "SoloPlay/BuildFiles/amazon/amazon.JavazonBuild.js" | "SoloPlay/BuildFiles/druid/druid.ElementalBuild.js" | "SoloPlay/BuildFiles/druid/druid.StormbearBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.StartBuild.js" | "SoloPlay/BuildFiles/Runewords/BreathOfTheDying.js" | "SoloPlay/BuildFiles/amazon/amazon.LevelingBuild.js" | "SoloPlay/BuildFiles/amazon/amazon.SteppingBuild.js" | "SoloPlay/BuildFiles/druid/druid.PlaguewolfBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.SmiterBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.ZealerBuild.js" | "SoloPlay/Core/ClassAttackOverrides/DruidAttacks.js" | "SoloPlay/BuildFiles/amazon/amazon.WitchyzonBuild.js" | "SoloPlay/BuildFiles/assassin/assassin.StartBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.AuradinBuild.js" | "SoloPlay/Core/ClassAttackOverrides/AmazonAttacks.js" | "SoloPlay/BuildFiles/paladin/paladin.LevelingBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.ColdBuild.js" | "SoloPlay/Core/ClassAttackOverrides/PaladinAttacks.js" | "SoloPlay/BuildFiles/amazon/amazon.FaithbowzonBuild.js" | "SoloPlay/BuildFiles/amazon/amazon.FrostmaidenBuild.js" | "SoloPlay/BuildFiles/assassin/assassin.TrapsinBuild.js" | "SoloPlay/BuildFiles/barbarian/barbarian.StartBuild.js" | "SoloPlay/BuildFiles/barbarian/barbarian.ThrowBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.HammerdinBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.TorchadinBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.BlovaBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.EsorbBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.StartBuild.js" | "SoloPlay/Core/ClassAttackOverrides/AssassinAttacks.js" | "SoloPlay/BuildFiles/assassin/assassin.LevelingBuild.js" | "SoloPlay/BuildFiles/assassin/assassin.WhirlsinBuild.js" | "SoloPlay/BuildFiles/barbarian/barbarian.FrenzyBuild.js" | "SoloPlay/BuildFiles/barbarian/barbarian.SingerBuild.js" | "SoloPlay/Core/ClassAttackOverrides/BarbarianAttacks.js" | "SoloPlay/Core/ClassAttackOverrides/SorceressAttacks.js" | "SoloPlay/BuildFiles/paladin/paladin.HammershockBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.SancdreamerBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.MeteorbBuild.js" | "SoloPlay/Core/ClassAttackOverrides/AmazonAttacks-WIP.js" | "SoloPlay/BuildFiles/barbarian/barbarian.LevelingBuild.js" | "SoloPlay/BuildFiles/barbarian/barbarian.SteppingBuild.js" | "SoloPlay/BuildFiles/barbarian/barbarian.UberconcBuild.js" | "SoloPlay/BuildFiles/necromancer/necromancer.BoneBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.LevelingBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.SteppingBuild.js" | "SoloPlay/Core/ClassAttackOverrides/NecromancerAttacks.js" | "SoloPlay/BuildFiles/barbarian/barbarian.WhirlwindBuild.js" | "SoloPlay/BuildFiles/necromancer/necromancer.StartBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.LightningBuild.js" | "SoloPlay/BuildFiles/necromancer/necromancer.PoisonBuild.js" | "SoloPlay/BuildFiles/necromancer/necromancer.SummonBuild.js" | "SoloPlay/BuildFiles/paladin/paladin.ClassicauradinBuild.js" | "SoloPlay/BuildFiles/sorceress/sorceress.BlizzballerBuild.js" | "SoloPlay/BuildFiles/necromancer/necromancer.LevelingBuild.js" | "SoloPlay/BuildFiles/barbarian/barbarian.ImmortalwhirlBuild.js"; diff --git a/d2bs/kolbot/sdk/types/kolbot-scripts.d.ts b/d2bs/kolbot/sdk/types/kolbot-scripts.d.ts new file mode 100644 index 000000000..665c3bf76 --- /dev/null +++ b/d2bs/kolbot/sdk/types/kolbot-scripts.d.ts @@ -0,0 +1,89 @@ +// Auto-generated script types +export type KolbotScript = + | "Abaddon" + | "AncientTunnels" + | "Andariel" + | "AutoBaal" + | "AutoChaos" + | "Baal" + | "BaalAssistant" + | "BaalHelper" + | "BattleOrders" + | "BattlemaidSarina" + | "Bishibosh" + | "BoBarbHelper" + | "BoneAsh" + | "Bonesaw" + | "ChestMania" + | "ClassicChaosAssistant" + | "ClearAnyArea" + | "Coldcrow" + | "Coldworm" + | "ControlBot" + | "Corpsefire" + | "Countess" + | "Cows" + | "Crafting" + | "CreepingFeature" + | "CrushTele" + | "DeveloperMode" + | "Diablo" + | "DiabloHelper" + | "Duriel" + | "Eldritch" + | "Endugu" + | "Eyeback" + | "Fangskin" + | "Follower" + | "Frozenstein" + | "Gamble" + | "GemHunter" + | "GetCube" + | "GetEssences" + | "GetFade" + | "GetKeys" + | "GhostBusters" + | "Hephasto" + | "IPHunter" + | "Icehawk" + | "Idle" + | "Izual" + | "KillDclone" + | "KurastTemples" + | "MFHelper" + | "Mausoleum" + | "Mephisto" + | "Nihlathak" + | "OrgTorch" + | "OrgTorchHelper" + | "OuterSteppes" + | "Pindleskin" + | "Pit" + | "Questing" + | "Radament" + | "Rakanishu" + | "RaiseArmy" + | "Rushee" + | "Rusher" + | "SealLeecher" + | "SharpTooth" + | "ShopBot" + | "Smith" + | "Snapchip" + | "Stormtree" + | "Summoner" + | "Synch" + | "Synch2" + | "Test" + | "ThreshSocket" + | "Tombs" + | "Travincal" + | "TravincalLeech" + | "Treehead" + | "Tristram" + | "TristramLeech" + | "UndergroundPassage" + | "UserAddon" + | "WPGetter" + | "Wakka" + | "Worldstone"; diff --git a/d2bs/kolbot/sdk/types/sdk.d.ts b/d2bs/kolbot/sdk/types/sdk.d.ts new file mode 100644 index 000000000..056d40422 --- /dev/null +++ b/d2bs/kolbot/sdk/types/sdk.d.ts @@ -0,0 +1,5463 @@ +declare global { + interface SDK { + waypoints: { + Ids: [119, 145, 156, 157, 237, 238, 288, 323, 324, 398, 402, 429, 494, 496, 511, 539]; + Act1: number[]; + Act2: number[]; + Act3: number[]; + Act4: number[]; + Act5: number[]; + }; + + difficulty: { + Normal: 0; + Nightmare: 1; + Hell: 2; + Difficulties: ["Normal", "Nightmare", "Hell"]; + + nameOf: (diff: 0 | 1 | 2) => "Normal" | "Nightmare" | "Hell" | false; + }; + + party: { + NoParty: 65535; + flag: { + Invite: 0; + InParty: 1; + Accept: 2; + Cancel: 4; + }; + controls: { + Hostile: 1; + InviteOrCancel: 2; + Leave: 3; + Ignore: 4; + Squelch: 5; + }; + }; + + 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; + }; + }; + + cursortype: { + Empty: 1; + ItemOnUnitHover: 3; // see notes + ItemOnCursor: 4; // see notes + Identify: 6; + Repair: 7; + }; + + 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; + }; + + 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; + + // 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; + + // 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; + + // Act 4 + PandemoniumFortress: 103; + OuterSteppes: 104; + PlainsofDespair: 105; + CityoftheDamned: 106; + RiverofFlame: 107; + ChaosSanctuary: 108; + + // 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; + + // Ubers + MatronsDen: 133; + ForgottenSands: 134; + FurnaceofPain: 135; + UberTristram: 136; + + actOf: (act: number) => 1 | 2 | 3 | 4 | 5; + townOf: (townArea: number) => 1 | 40 | 75 | 103 | 109; + townOfAct: (act: 1 | 2 | 3 | 4 | 5) => 1 | 40 | 75 | 103 | 109; + }; + + 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; + + // 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 + 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 + 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; + + // 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 + 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; + + // General stuff + IdentifyScroll: 217; + BookofIdentify: 218; + TownPortalScroll: 219; + BookofTownPortal: 220; + + // 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; + + // 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; + + // Sorc + Fire: 8; + Lightning: 9; + Cold: 10; + + // Necro + Curses: 16; + PoisonandBone: 17; + NecroSummoning: 18; + + // Pala + PalaCombat: 24; + Offensive: 25; + Defensive: 26; + + // Barb + BarbCombat: 32; + Masteries: 33; + Warcries: 34; + + // Druid + DruidSummon: 40; + ShapeShifting: 41; + Elemental: 42; + + // Assa + Traps: 48; + ShadowDisciplines: 49; + MartialArts: 50; + }; + }; + skillTabs: undefined; + + 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; + + // act 2 + ViperAmuletChest: 149; + HoradricStaffHolder: 152; + HoradricCubeChest: 354; + HoradricScrollChest: 355; + ShaftoftheHoradricStaffChest: 356; + Journal: 357; + + // act 3 + ForestAltar: 81; + LamEsensTomeHolder: 193; + GidbinnAltar: 252; + KhalimsHeartChest: 405; + KhalimsBrainChest: 406; + KhalimsEyeChest: 407; + + // act 4 + HellForge: 376; + + // 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; + }; + }; + + // 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; + }; + + 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; + }; + + // 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; + }; + + // 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; + }; + + 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; + }; + + // unit stats + stats: { + StunLength: 66; + VelocityPercent: 67; + OtherAnimrate: 69; + HpRegen: 74; + + LastBlockFrame: 95; + State: 98; + MonsterPlayerCount: 100; + + CurseResistance: 109; + IronMaidenLevel: 129; + LifeTapLevel: 130; + + Alignment: 172; + Target0: 173; + Target1: 174; + GoldLost: 175; + MinimumRequiredLevel: 176; + ConversionLevel: 176; + ConversionMaxHp: 177; + UnitDooverlay: 178; + AttackVsMontype: 179; + DamageVsMontype: 180; + + 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; + + ModifierListSkill: 350; + ModifierListLevel: 351; + + LastSentHpPct: 352; + SourceUnitType: 353; + SourceUnitId: 354; + + 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; + + 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; + + // doesn't exist but define for prototypes + AllRes: 555; + }; + + // unit info + unittype: { + Player: 0; + NPC: 1; + Monster: 1; + Object: 2; + Missile: 3; + Item: 4; + Stairs: 5; // ToDo: might be more as stairs + }; + + player: { + flag: { + Ignore: 2; + Squelch: 4; + 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; + + nameOf: ( + classid: 0 | 1 | 2 | 3 | 4 | 5 | 6, + ) => "Amazon" | "Sorceress" | "Necromancer" | "Paladin" | "Barbarian" | "Druid" | "Assassin" | false; + }; + }; + + 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; + }; + + 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; + }; + + objects: { + mode: { + Inactive: 0; + Interacted: 1; + Active: 2; + }; + + 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 + MoldyTome: 8; + A1TownFire: 39; + A1Waypoint: 119; + StoneAlpha: 17; + StoneBeta: 18; + StoneGamma: 19; + StoneDelta: 20; + StoneLambda: 21; + StoneTheta: 22; + CainsJail: 26; + InifussTree: 30; + Malus: 108; + + // 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; + + // act 3 + A3Waypoint: 237; + ForestAltar: 81; + LamEsensTome: 193; + SewerStairsA3: 366; + SewerLever: 367; + DuranceEntryStairs: 386; + RedPortalToAct4: 342; + CompellingOrb: 404; + + // 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 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; + + FrigidHighlandsChest: 455; + IcyCellarChest: 397; + + SmallSparklyChest: 397; + LargeSparklyChest: 455; + SuperChest: 580; + + // misc + BubblingPoolofBlood: 82; + HornShrine: 83; + Stash: 267; + BluePortal: 59; + RedPortal: 60; + Smoke: 401; + }; + + 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 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 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 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; + + // 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; + }; + Raven: 419; + PoisonCreeper: 425; + CarrionVine: 426; + SolarCreeper: 427; + 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; + + // 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; + + // Mini-Bosses + TheSmith: 402; + BloodRaven: 267; + Radament: 229; + TheSummoner: 250; + Griswold: 365; + Izual: 256; + Hephasto: 409; + TalictheDefender: 540; + MadawctheGuardian: 541; + KorlictheProtector: 542; + ListerTheTormenter: 571; + TheCowKing: 743; + ColdwormtheBurrower: 284; + Nihlathak: 526; + + // 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; + + // Misc? + Youngdiablo: 368; + Left: 525; + Life: 426; + Effect: 574; + Pet: 414; + Prince: 249; + POW: 534; + Right: 524; + Sage: 424; + Town: 514; + Cow: 179; + }; + + 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; + }; + + 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; + }; + + ClayGolem: 289; + Dopplezon: 356; + Valkyrie: 357; + FireGolem: 292; + IronGolem: 291; + NecroMage: 364; + NecroSkeleton: 363; + Poppy: 425; + Wolverine: 423; + }; + + 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; + }; + + Rogue: 271; + Guard: 338; + IronWolf: 359; + A5Barb: 561; + }; + + missiles: { + DiabloLightning: 172; + FissureCrack1: 462; + FissureCrack2: 463; + }; + + storage: { + Equipped: 1; + Belt: 2; + Inventory: 3; + TradeWindow: 5; + Cube: 6; + Stash: 7; + }; + + node: { + NotOnPlayer: 0; + Storage: 1; + Belt: 2; + Equipped: 3; + Cursor: 4; + }; + + // 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; + }; + + 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; + }; + // @ts-ignore + 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; + + // 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; + Ear: 556; + Herb: 602; + anevilforce: 609; + // Potions, tomes/scrolls, gold + Key: 543; + 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; + + 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; + }; + }; + }; + + // 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; + Cap: 1930; + SkullCap: 1931; + Helm: 1932; + FullHelm: 1933; + GreatHelm: 1934; + Crown: 1935; + Mask: 1936; + QuiltedArmor: 1937; + LeatherArmor: 1938; + HardLeatherArmor: 1939; + StuddedLeather: 1940; + RingMail: 1941; + ScaleMail: 1942; + ChainMail: 1943; + BreastPlate: 1944; + SplintMail: 1945; + PlateMail: 1946; + FieldPlate: 1947; + GothicPlate: 1948; + FullPlateMail: 1949; + AncientArmor: 1950; + LightPlate: 1951; + Buckler: 1952; + SmallShield: 1953; + LargeShield: 1954; + KiteShield: 1955; + TowerShield: 1956; + GothicShield: 1957; + LeatherGloves: 1958; + HeavyGloves: 1959; + ChainGloves: 1960; + LightGauntlets: 1961; + Gauntlets: 1962; + Boots: 1963; + HeavyBoots: 1964; + ChainBoots: 1965; + LightPlatedBoots: 1966; + Greaves: 1967; + Sash: 1968; + LightBelt: 1969; + Belt: 1970; + HeavyBelt: 1971; + PlatedBelt: 1972; + BoneHelm: 1973; + BoneShield: 1974; + SpikedShield: 1975; + HandAxe: 1976; + Axe: 1977; + DoubleAxe: 1978; + MilitaryPick: 1979; + WarAxe: 1980; + LargeAxe: 1981; + BroadAxe: 1982; + BattleAxe: 1983; + GreatAxe: 1984; + GiantAxe: 1985; + Wand: 1986; + YewWand: 1987; + BoneWand: 1988; + GrimWand: 1989; + Club: 1990; + Scepter: 1991; + GrandScepter: 1992; + WarScepter: 1993; + SpikedClub: 1994; + Mace: 1995; + MorningStar: 1996; + Flail: 1997; + WarHammer: 1998; + Maul: 1999; + GreatMaul: 2000; + ShortSword: 2001; + Scimitar: 2002; + Sabre: 2003; + Falchion: 2004; + CrystalSword: 2005; + BroadSword: 2006; + LongSword: 2007; + WarSword: 2008; + TwoHandedSword: 2009; + Claymore: 2010; + GiantSword: 2011; + BastardSword: 2012; + Flamberge: 2013; + GreatSword: 2014; + Dagger: 2015; + Dirk: 2016; + Kris: 2017; + Blade: 2018; + ThrowingKnife: 2019; + ThrowingAxe: 2020; + BalancedKnife: 2021; + BalancedAxe: 2022; + Javelin: 2023; + Pilum: 2024; + ShortSpear: 2025; + Glaive: 2026; + ThrowingSpear: 2027; + Spear: 2028; + Trident: 2029; + Brandistock: 2030; + Spetum: 2031; + Pike: 2032; + Bardiche: 2033; + Voulge: 2034; + Scythe: 2035; + Poleaxe: 2036; + Halberd: 2037; + WarScythe: 2038; + ShortStaff: 2039; + LongStaff: 2040; + GnarledStaff: 2041; + BattleStaff: 2042; + WarStaff: 2043; + ShortBow: 2044; + HuntersBow: 2045; + LongBow: 2046; + CompositeBow: 2047; + ShortBattleBow: 2048; + LongBattleBow: 2049; + ShortWarBow: 2050; + LongWarBow: 2051; + LightCrossbow: 2052; + Crossbow: 2053; + HeavyCrossbow: 2054; + RepeatingCrossbow: 2055; + BarbedShield: 2056; + GrimShield: 2057; + GrimHelm: 2058; + WarBelt: 2059; + BattleBelt: 2060; + MeshBelt: 2061; + SharkskinBelt: 2062; + DemonhideSash: 2063; + WarBoots: 2064; + BattleBoots: 2065; + MeshBoots: 2066; + SharkskinBoots: 2067; + DemonhideBoots: 2068; + WarGauntlets: 2069; + BattleGauntlets: 2070; + HeavyBracers: 2071; + SharkskinGloves: 2072; + DemonhideGloves: 2073; + AncientShield: 2074; + Pavise: 2075; + DragonShield: 2076; + Scutum: 2077; + RoundShield: 2078; + Defender: 2079; + MagePlate: 2080; + OrnatePlate: 2081; + ChaosArmor: 2082; + EmbossedPlate: 2083; + SharktoothArmor: 2084; + TemplarCoat: 2085; + RussetArmor: 2086; + Cuirass: 2087; + MeshArmor: 2088; + TigulatedMail: 2089; + LinkedMail: 2090; + TrellisedArmor: 2091; + DemonhideArmor: 2092; + SerpentskinArmor: 2093; + GhostArmor: 2094; + DeathMask: 2095; + GrandCrown: 2096; + WingedHelm: 2097; + Basinet: 2098; + Casque: 2099; + Sallet: 2100; + WarHat: 2101; + ChuKoNu: 2102; + Ballista: 2103; + SiegeCrossbow: 2104; + Arbalest: 2105; + GothicBow: 2106; + RuneBow: 2107; + LargeSiegeBow: 2108; + ShortSiegeBow: 2109; + DoubleBow: 2110; + CedarBow: 2111; + RazorBow: 2112; + EdgeBow: 2113; + RuneStaff: 2114; + GothicStaff: 2115; + CedarStaff: 2116; + Quarterstaff: 2117; + JoStaff: 2118; + GrimScythe: 2119; + BecdeCorbin: 2120; + Partizan: 2121; + BattleScythe: 2122; + Bill: 2123; + LochaberAxe: 2124; + Lance: 2125; + Yari: 2126; + WarFork: 2127; + Fuscina: 2128; + WarSpear: 2129; + Harpoon: 2130; + Spiculum: 2131; + Simbilan: 2132; + GreatPilum: 2133; + WarJavelin: 2134; + Hurlbat: 2135; + WarDart: 2136; + Francisca: 2137; + BattleDart: 2138; + Stilleto: 2139; + Cinquedeas: 2140; + Rondel: 2141; + Poignard: 2142; + ExecutionerSword: 2143; + Zweihander: 2144; + TuskSword: 2145; + DacianFalx: 2146; + Espandon: 2147; + AncientSword: 2148; + RuneSword: 2149; + BattleSword: 2150; + DimensionalBlade: 2151; + Tulwar: 2152; + Shamshir: 2153; + Cutlass: 2154; + Gladius: 2155; + MarteldeFer: 2156; + WarClub: 2157; + BattleHammer: 2158; + Knout: 2159; + JaggedStar: 2160; + FlangedMace: 2161; + BarbedClub: 2162; + DivineScepter: 2163; + HolyWaterSprinkler: 2164; + RuneScepter: 2165; + Cudgel: 2166; + GraveWand: 2167; + TombWand: 2168; + PetrifiedWand: 2169; + BurntWand: 2170; + AncientAxe: 2171; + GothicAxe: 2172; + Tabar: 2173; + BeardedAxe: 2174; + MilitaryAxe: 2175; + Naga: 2176; + Crowbill: 2177; + TwinAxe: 2178; + Cleaver: 2179; + Hatchet: 2180; + GothicSword: 2181; + StranglingGasPotion: 2182; + FulminatingPotion: 2183; + ChokingGasPotion: 2184; + ExplodingPotion: 2185; + RancidGasPotion: 2186; + OilPotion: 2187; + Gidbinn: 2188; + TheGidbinn1: 2189; + DecoyGidbinn: 2190; + WirtsLeg: 2191; + HoradricMalus1: 2192; + HoradricMalus2: 2193; + HellForgeHammer: 2194; + HoradricStaff1: 2195; + ShaftoftheHoradricStaff: 2196; + Orifice: 2197; + Elixir: 2198; + TomeofTownPortal: 2199; + ScrollofTownPortal: 2200; + TomeofIdentify: 2201; + ScrollofIdentify: 2202; + RightClicktoUse: 2203; + RightClicktoOpen: 2204; + RightClicktoRead: 2205; + InsertScrolls: 2206; + StaminaPotion: 2207; + AntidotePotion: 2208; + RejuvenationPotion: 2209; + FullRejuvenationPotion: 2210; + ThawingPotion: 2211; + Amulet: 2212; + TopoftheHoradricStaff: 2213; + Ring: 2214; + Gold: 2215; + ScrollofInifuss: 2216; + KeytotheCairnStones: 2217; + Arrows: 2218; + Torch: 2219; + Bolts: 2220; + Key1: 2221; + Key2: 2222; + TheBlackTowerKey: 2223; + RightClicktoPermanentlyAdd20toLifePotionofLife: 2224; + Shrine: 2225; + TeleportPad: 2226; + AJadeFigurine: 2227; + TheGoldenBird: 2228; + LamEsensTome1: 2229; + LamEsensTome2: 2230; + HoradricCube: 2231; + HoradricScroll: 2232; + MephistosSoulstone: 2233; + RightClicktoLearnSkillofYourChoiceBookofSkill: 2234; + Ear: 2235; + ChippedAmethyst: 2236; + FlawedAmethyst: 2237; + Amethyst: 2238; + FlawlessAmethyst: 2239; + PerfectAmethyst: 2240; + ChippedTopaz: 2241; + FlawedTopaz: 2242; + Topaz: 2243; + FlawlessTopaz: 2244; + PerfectTopaz: 2245; + ChippedSapphire: 2246; + FlawedSapphire: 2247; + Sapphire: 2248; + FlawlessSapphire: 2249; + PerfectSapphire: 2250; + ChippedEmerald: 2251; + FlawedEmerald: 2252; + FlawlessEmerald: 2253; + Emerald: 2254; + PerfectEmerald: 2255; + ChippedRuby: 2256; + FlawedRuby: 2257; + Ruby: 2258; + FlawlessRuby: 2259; + PerfectRuby: 2260; + ChippedDiamond: 2261; + FlawedDiamond: 2262; + Diamond: 2263; + FlawlessDiamond: 2264; + PerfectDiamond: 2265; + MinorHealingPotion: 2266; + LightHealingPotion: 2267; + HealingPotion: 2268; + GreaterHealingPotion: 2269; + SuperHealingPotion: 2270; + MinorManaPotion: 2271; + LightManaPotion: 2272; + ManaPotion: 2273; + GreaterManaPotion: 2274; + SuperManaPotion: 2275; + Herb: 2276; + ChippedSkull: 2277; + FlawedSkull: 2278; + Skull: 2279; + FlawlessSkull: 2280; + PerfectSkull: 2281; + AmuletoftheViper: 2697; + StaffofKings: 2698; + HoradricStaff: 2699; + + // 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; + + // 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; + + // Uniques + // Quest/Misc + KeyofTerror: 11146; + KeyofHate: 11147; + KeyofDestruction: 11148; + DiablosHorn: 11149; + BaalsEye: 11150; + MephistosBrain: 11151; + StandardofHeroes: 11152; + HellfireTorch: 11153; + Annihilus: 21743; + + // 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; + Windforce: 21635; + GinthersRift: 21829; + + // 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; + }; + + text: { + Ladder: 719; + 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; + RejectedByServer: 5215; + AccountMustBeAtLeast: 5217; + AccountCantBeMoreThan: 5218; + PasswordMustBeAtLeast: 5219; + PasswordCantBeMoreThan: 5220; + LoginError: 5224; + UsernameMustBeAtLeast: 5231; + UsernameIncludedIllegalChars: 5232; + UsernameIncludedDisallowedwords: 5233; + AccountNameAlreadyExist: 5239; + UnableToCreateAccount: 5249; + CannotCreateGamesDeadHCChar: 5304; + Disconnected: 5347; + UnableToIndentifyVersion: 5245; + 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; + }; + + 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; + + // 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; + + // 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; + + // Act 4 + PandemoniumFortress: 790; + OuterSteppes: 792; + PlainsofDespair: 793; + CityoftheDamned: 794; + RiverofFlame: 791; + ChaosSanctuary: 789; + + // 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; + + // Ubers + MatronsDen: 5389; + ForgottenSands: 5389; + FurnaceofPain: 5389; + UberTristram: 5018; + }; + }; + + game: { + profiletype: { + SinglePlayer: 1; + Battlenet: 2; + OpenBattlenet: 3; + TcpIpHost: 4; + TcpIpJoin: 5; + }; + + controls: { + Disabled: 4; + }; + + 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; + }; + }; + + 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; + }; + }; + + 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; + UniqueEvents: 0x89; + WeaponSwitch: 0x97; + }; + }; + } + const sdk: SDK; +} +export {}; diff --git a/d2bs/kolbot/threads/AntiHostile.js b/d2bs/kolbot/threads/AntiHostile.js new file mode 100644 index 000000000..3ef16b3d1 --- /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[me.classid].checkTraps({ x: 15099, y: 5242, classid: 544 }); + + if (check) { + ClassAttack[me.classid].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[me.classid].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..4258f6e1f --- /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].includes(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..789ade34e --- /dev/null +++ b/d2bs/kolbot/threads/Party.js @@ -0,0 +1,251 @@ +/** +* @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; + case "unparty": + clickParty(getParty(), sdk.party.controls.Leave); + 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..f7062ed33 --- /dev/null +++ b/d2bs/kolbot/threads/RushThread.js @@ -0,0 +1,342 @@ +/* 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"); + + +function main () { + const { + log, + andariel, + cube, + amulet, + staff, + summoner, + duriel, + travincal, + mephisto, + diablo, + ancients, + baal, + cain, + radament, + lamesen, + izual, + shenk, + anya, + } = require("../libs/systems/autorush/AutoRush"); + const { AutoRush } = require("../libs/systems/autorush/RushConstants"); + const { RushConfig } = require("../libs/systems/autorush/RushConfig"); + /** @type {RusherConfig} */ + const rushProfile = RushConfig[me.profile]; + + if (!rushProfile) { + throw new Error("No rush config found for this profile. Please create one in RushConfig.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" || msg === "alreadyhave") { + 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(); + + if (me.getMobCount(15) > 0) { + Attack.securePosition(me.x, me.y, { range: 15, duration: Time.seconds(30), skipBlocked: 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 (rushProfile.config.Wps && giveWP()) || true; + } else { + console.log("failed"); + + return false; + } + }).apply(); + + const clearArea = function (area) { + Pather.journeyTo(area); + Attack.clearLevel(0); + log("Done clearing area: " + area); + }; + + const givewps = function () { + if (!rushProfile.config.Wps) return false; + + say("wpinfo"); + + Misc.poll(function () { + return gotWpInfo; + }, Time.minutes(3), 1000); + + log("Starting wps to give: " + wpsToGive.length); + 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 questerName = ""; + let gotWpInfo = false; + + const sequences = [ + cain, + andariel, + radament, + cube, + amulet, + staff, + summoner, + duriel, + lamesen, + travincal, + mephisto, + izual, + diablo, + shenk, + anya, + ancients, + baal, + // givewps + ]; + + const scriptEvent = function (msg) { + if (typeof msg === "string") { + if (!msg.startsWith("rush-")) return; + command = msg.substring(5); + } else if ( + isType(msg, "object") + && Object.hasOwn(msg, "type") + && msg.type === "rush" + ) { + switch (msg.action) { + case "highestquest": + questerName = msg.data.quester; + // hacky but don't feel like changing orginal logic in handleRushCommand right now + command = "highestquest " + msg.data.highestquest; + break; + case "wps": + wpsToGive = msg.data.wps; + gotWpInfo = true; + break; + } + } + }; + + addEventListener("scriptmsg", scriptEvent); + + /** @param {string} msg */ + const handleRushCommand = function (msg) { + if (!isType(msg, "string")) return; + + let [action, info] = msg.toLowerCase().split(" "); + console.log("Received rush command: " + action + " info: " + info); + + if (action) { + if (action === "skiptoact") { + if (!isNaN(parseInt(info, 10))) { + switch (parseInt(info, 10)) { + case 2: + current = sequences.findIndex((s) => s.name === "andariel") + 1; + Town.goToTown(2); + + break; + case 3: + current = sequences.findIndex((s) => s.name === "duriel") + 1; + Town.goToTown(3); + + break; + case 4: + current = sequences.findIndex((s) => s.name === "mephisto") + 1; + Town.goToTown(4); + + break; + case 5: + current = sequences.findIndex((s) => s.name === "diablo") + 1; + Town.goToTown(5); + + break; + } + } + + command = ""; + } else if (action === "clear") { + clearArea(Number(info)); + Town.goToTown(); + + command = "go"; + } else if (action === "highestquest" && info) { + let foundIdx = sequences.findIndex(function (s) { + return s.name === info; + }); + console.log("highestquest idx: " + foundIdx); + foundIdx > -1 && (current = foundIdx + 1); + + command = ""; + } + } else { + let foundIdx = sequences.findIndex(function (s) { + return s.name.toLowerCase() === command.toLowerCase(); + }); + + if (foundIdx > -1) { + current = foundIdx; + } + + Town.goToTown(); + + command = "go"; + } + }; + + // START + Config.init(false); + Pickit.init(false); + Attack.init(); + Storage.Init(); + CraftingSystem.buildLists(); + Runewords.init(); + Cubing.init(); + + Config.MFLeader = false; + + (function () { + let lastRunIdx = sequences.findIndex(function (s) { + return String.isEqual(rushProfile.config.LastRun, s.name); + }); + if (lastRunIdx > -1) { + let temp = sequences.slice(0, lastRunIdx + 1); + sequences.length = 0; + for (let i = 0; i < temp.length; i++) { + sequences.push(temp[i]); + } + } + })(); + + if (rushProfile.config.Wps) { + sequences.push(givewps); + } + + console.debug("Rush sequence: " + sequences.map((s) => s.name).join(", ")); + + while (true) { + if (command) { + switch (command) { + case "go": + // End run if entire sequence is done or if Config.Rusher.LastRun is done + if (current >= sequences.length) { + delay(3000); + log("exit"); + console.log("Current sequence length: " + current + " sequence length: " + sequences.length); + + while (Misc.getPlayerCount() > 1) { + delay(1000); + } + + scriptBroadcast("quit"); + + break; + } + + Town.doChores(); + + // TODO: add extra checks before starting to ensure quester is moving along with us + + try { + sequences[current](questerName); + } catch (sequenceError) { + log(sequenceError.message); + log(AutoRush.playersOut); + Town.goToTown(); + } + + current += 1; + command = "go"; + + break; + default: + handleRushCommand(command); + + break; + } + } + + delay(100); + } +} diff --git a/d2bs/kolbot/threads/ToolsThread.js b/d2bs/kolbot/threads/ToolsThread.js new file mode 100644 index 000000000..a41745412 --- /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(); + +// 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(); + + 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 + + " | ÿc4Item Level: ÿc0" + itemToCheck.ilvl + ); + 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/data/.gitkeep b/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..4834d7311 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2483 @@ +{ + "name": "trunk", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "trunk", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@biomejs/biome": "2.1.2", + "@types/node": "^24.0.10", + "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/@biomejs/biome": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.1.2.tgz", + "integrity": "sha512-yq8ZZuKuBVDgAS76LWCfFKHSYIAgqkxVB3mGVVpOe2vSkUTs7xG46zXZeNPRNVjiJuw0SZ3+J2rXiYx0RUpfGg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.1.2", + "@biomejs/cli-darwin-x64": "2.1.2", + "@biomejs/cli-linux-arm64": "2.1.2", + "@biomejs/cli-linux-arm64-musl": "2.1.2", + "@biomejs/cli-linux-x64": "2.1.2", + "@biomejs/cli-linux-x64-musl": "2.1.2", + "@biomejs/cli-win32-arm64": "2.1.2", + "@biomejs/cli-win32-x64": "2.1.2" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.1.2.tgz", + "integrity": "sha512-leFAks64PEIjc7MY/cLjE8u5OcfBKkcDB0szxsWUB4aDfemBep1WVKt0qrEyqZBOW8LPHzrFMyDl3FhuuA0E7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.1.2.tgz", + "integrity": "sha512-Nmmv7wRX5Nj7lGmz0FjnWdflJg4zii8Ivruas6PBKzw5SJX/q+Zh2RfnO+bBnuKLXpj8kiI2x2X12otpH6a32A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.1.2.tgz", + "integrity": "sha512-NWNy2Diocav61HZiv2enTQykbPP/KrA/baS7JsLSojC7Xxh2nl9IczuvE5UID7+ksRy2e7yH7klm/WkA72G1dw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.1.2.tgz", + "integrity": "sha512-qgHvafhjH7Oca114FdOScmIKf1DlXT1LqbOrrbR30kQDLFPEOpBG0uzx6MhmsrmhGiCFCr2obDamu+czk+X0HQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.1.2.tgz", + "integrity": "sha512-Km/UYeVowygTjpX6sGBzlizjakLoMQkxWbruVZSNE6osuSI63i4uCeIL+6q2AJlD3dxoiBJX70dn1enjQnQqwA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.1.2.tgz", + "integrity": "sha512-xlB3mU14ZUa3wzLtXfmk2IMOGL+S0aHFhSix/nssWS/2XlD27q+S6f0dlQ8WOCbYoXcuz8BCM7rCn2lxdTrlQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.1.2.tgz", + "integrity": "sha512-G8KWZli5ASOXA3yUQgx+M4pZRv3ND16h77UsdunUL17uYpcL/UC7RkWTdkfvMQvogVsAuz5JUcBDjgZHXxlKoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.1.2.tgz", + "integrity": "sha512-9zajnk59PMpjBkty3bK2IrjUsUHvqe9HWwyAWQBjGLE7MIBjbX2vwv1XPEhmO2RRuGoTkVx3WCanHrjAytICLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "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/@types/node": { + "version": "24.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", + "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "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.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "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/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "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" + } + } + } + }, + "@biomejs/biome": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.1.2.tgz", + "integrity": "sha512-yq8ZZuKuBVDgAS76LWCfFKHSYIAgqkxVB3mGVVpOe2vSkUTs7xG46zXZeNPRNVjiJuw0SZ3+J2rXiYx0RUpfGg==", + "dev": true, + "requires": { + "@biomejs/cli-darwin-arm64": "2.1.2", + "@biomejs/cli-darwin-x64": "2.1.2", + "@biomejs/cli-linux-arm64": "2.1.2", + "@biomejs/cli-linux-arm64-musl": "2.1.2", + "@biomejs/cli-linux-x64": "2.1.2", + "@biomejs/cli-linux-x64-musl": "2.1.2", + "@biomejs/cli-win32-arm64": "2.1.2", + "@biomejs/cli-win32-x64": "2.1.2" + } + }, + "@biomejs/cli-darwin-arm64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.1.2.tgz", + "integrity": "sha512-leFAks64PEIjc7MY/cLjE8u5OcfBKkcDB0szxsWUB4aDfemBep1WVKt0qrEyqZBOW8LPHzrFMyDl3FhuuA0E7g==", + "dev": true, + "optional": true + }, + "@biomejs/cli-darwin-x64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.1.2.tgz", + "integrity": "sha512-Nmmv7wRX5Nj7lGmz0FjnWdflJg4zii8Ivruas6PBKzw5SJX/q+Zh2RfnO+bBnuKLXpj8kiI2x2X12otpH6a32A==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-arm64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.1.2.tgz", + "integrity": "sha512-NWNy2Diocav61HZiv2enTQykbPP/KrA/baS7JsLSojC7Xxh2nl9IczuvE5UID7+ksRy2e7yH7klm/WkA72G1dw==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-arm64-musl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.1.2.tgz", + "integrity": "sha512-qgHvafhjH7Oca114FdOScmIKf1DlXT1LqbOrrbR30kQDLFPEOpBG0uzx6MhmsrmhGiCFCr2obDamu+czk+X0HQ==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-x64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.1.2.tgz", + "integrity": "sha512-Km/UYeVowygTjpX6sGBzlizjakLoMQkxWbruVZSNE6osuSI63i4uCeIL+6q2AJlD3dxoiBJX70dn1enjQnQqwA==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-x64-musl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.1.2.tgz", + "integrity": "sha512-xlB3mU14ZUa3wzLtXfmk2IMOGL+S0aHFhSix/nssWS/2XlD27q+S6f0dlQ8WOCbYoXcuz8BCM7rCn2lxdTrlQA==", + "dev": true, + "optional": true + }, + "@biomejs/cli-win32-arm64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.1.2.tgz", + "integrity": "sha512-G8KWZli5ASOXA3yUQgx+M4pZRv3ND16h77UsdunUL17uYpcL/UC7RkWTdkfvMQvogVsAuz5JUcBDjgZHXxlKoA==", + "dev": true, + "optional": true + }, + "@biomejs/cli-win32-x64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.1.2.tgz", + "integrity": "sha512-9zajnk59PMpjBkty3bK2IrjUsUHvqe9HWwyAWQBjGLE7MIBjbX2vwv1XPEhmO2RRuGoTkVx3WCanHrjAytICLA==", + "dev": true, + "optional": true + }, + "@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 + }, + "@types/node": { + "version": "24.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz", + "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", + "dev": true, + "requires": { + "undici-types": "~7.8.0" + } + }, + "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.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "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 + }, + "undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "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..8c4798342 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "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": { + "@biomejs/biome": "2.1.2", + "@types/node": "^24.0.10", + "eslint": "^7.32.0", + "typescript": "^4.9.3" + } +} diff --git a/setup.bat b/setup.bat new file mode 100644 index 000000000..3c9fc3fcf --- /dev/null +++ b/setup.bat @@ -0,0 +1,3 @@ +@echo off +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0/+setup/setup.ps1" +pause \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..7faee461d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "ignoreDeprecations": "6.0", + "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/", + ], +} diff --git a/update.bat b/update.bat new file mode 100644 index 000000000..b02edd911 --- /dev/null +++ b/update.bat @@ -0,0 +1,94 @@ +@echo off +echo Updating repository and submodules... + +REM Check if git is available +git --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo ERROR: Git is not installed or not available in PATH + echo Please install Git and ensure it's in your PATH before running this script + echo. + pause + exit /b 1 +) + +echo Git detected, checking for local changes... + +REM Check if there are any local changes +git diff --quiet +set has_changes=%errorlevel% + +git diff --cached --quiet +set has_staged_changes=%errorlevel% + +if %has_changes% neq 0 ( + echo Local changes detected, stashing them... + git stash push -m "Auto-stash before update" + if %errorlevel% neq 0 ( + echo ERROR: Failed to stash local changes + echo Please manually resolve any conflicts and try again + echo. + pause + exit /b 1 + ) + set stashed=1 +) else if %has_staged_changes% neq 0 ( + echo Staged changes detected, stashing them... + git stash push -m "Auto-stash before update" + if %errorlevel% neq 0 ( + echo ERROR: Failed to stash staged changes + echo Please manually resolve any conflicts and try again + echo. + pause + exit /b 1 + ) + set stashed=1 +) else ( + echo No local changes detected + set stashed=0 +) + +echo. +echo Updating repository... + +REM Pull latest changes from remote +git pull +if %errorlevel% neq 0 ( + echo ERROR: Failed to pull updates from remote repository + echo Please check your Git configuration and network connection + echo. + pause + exit /b 1 +) + +echo Repository updated successfully! +echo. + +REM Update all submodules to latest commits +echo Updating submodules... +git submodule update --init --remote --recursive +if %errorlevel% neq 0 ( + echo WARNING: Failed to update submodules + echo Please check your Git configuration and try again + echo. +) else ( + echo Submodules updated successfully! + echo. +) + +REM Restore stashed changes if any were stashed +if %stashed% equ 1 ( + echo Restoring your local changes... + git stash pop + if %errorlevel% neq 0 ( + echo WARNING: Failed to restore stashed changes automatically + echo Your changes are saved in the stash. You can restore them manually with: + echo git stash pop + echo. + ) else ( + echo Local changes restored successfully! + echo. + ) +) + +echo Update complete! +pause \ No newline at end of file