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 deleted file mode 100644 index 40feecf6f..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,135 +0,0 @@ -/* eslint-disable linebreak-style */ -// Standard Formatting rules for Kolbot scripts -// 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"], - } -}; 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..bd61c54c8 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,49 @@ +name: Checks + +on: + pull_request: + +jobs: + checks: + runs-on: ubuntu-latest + + steps: + # submodules: the SoloPlay submodule cannot be checked in isolation (its tsconfig points at + # ../../sdk and ../../libs), so the parent checkout is where it gets validated - against the + # exact commit this repo pins. + - name: Checkout code + uses: actions/checkout@v7 + with: + submodules: recursive + + # Node 24: eslint-plugin-es-x@10 requires ^22.23.0 || ^24.18.0 || >=26.4.0, and + # eslint@10 requires ^20.19.0 || ^22.13.0 || >=24. Anything older cannot run the toolchain. + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: '24' + cache: npm + + - name: Install dependencies + run: npm ci + + # lint:check, never `lint` - that one is `eslint --fix`, which would rewrite files in CI + # and let anything auto-fixable pass silently. + - name: ESLint + run: npm run lint:check + + - name: Typecheck + run: npm run typecheck + + # The no-undef inventory is generated. If someone adds a global without regenerating, + # the committed file goes stale and no-undef quietly stops covering it. + - name: Globals inventory is current + run: npm run globals:check + + - name: Typecheck (SoloPlay submodule) + run: npm run typecheck:soloplay + + # Every runtime .js must belong to a TS program. A file in none gets no typechecking and + # no editor intellisense, and nothing else reports it. + - name: No orphaned files + run: node tools/audit/program-files.mjs --check diff --git a/.gitignore b/.gitignore index 613d92ffe..8cdca7004 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,62 @@ +# 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/mulecondenser/config/MuleCondenserConfig.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 +/d2bs/kolbot/libs/modules/EngineProbe.js +/d2bs/kolbot/libs/modules/external/base85.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..f9f622622 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,31 @@ +{ + "files.associations": { + "*.dbj": "javascript" + }, + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "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", + } + }, + "js/ts.tsdk.path": "node_modules/typescript/lib" +} \ 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..e9cfe8c13 --- /dev/null +++ b/d2bs/kolbot/D2BotAutoRush.dbj @@ -0,0 +1,583 @@ +/** +* @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(); + + /** @returns {boolean} */ + 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); + } + }, + }; +})(); + +/** + * D2BS entry point: initializes the profile, then loops handling in-game status updates and, + * out of game, dispatching the current lobby/menu location to `locationAction`. + * @returns {void} + */ +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..ff5e41930 100644 --- a/d2bs/kolbot/D2BotChannel.dbj +++ b/d2bs/kolbot/D2BotChannel.dbj @@ -3,549 +3,616 @@ * @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(); +/** @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, +}; - DataFile.updateStats("runs", Starter.gameCount); - } +if (DataFile.init()) { + Starter.firstRun = true; +} - D2Bot.updateStatus(Starter.profileInfo.charName + " | Game: " + (me.gamename || "singleplayer") + Starter.timer(Starter.gameStart)); - } +/** + * @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 }); +} - delay(1000); - } +/** + * @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() }); +} - joinInfo.inGame = false; +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; + } + + 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 @stylistic/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, + }; +})(); - locationAction(getLocation()); - delay(1000); - } +/** + * Entry point: overrides Starter.receiveCopyData to report gameReady/gameInfo status on mode 3. + */ +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..9036a45cd --- /dev/null +++ b/d2bs/kolbot/D2BotCharRefresher.dbj @@ -0,0 +1,314 @@ +/** +* @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); + + 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, + }; +})(); + +/** + * Entry point for the CharRefresher session: initializes the D2Bot handshake, then loops + * forever tracking game status and reacting to location changes. + */ +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..f2f4512bf 100644 --- a/d2bs/kolbot/D2BotCleaner.dbj +++ b/d2bs/kolbot/D2BotCleaner.dbj @@ -4,495 +4,393 @@ * @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 = []; +/** + * Removes SoloPlay data/log files for each profile in `AdvancedProfileCleanerConfig`/`profiles`, + * optionally archiving them under `libs/SoloPlay/.soloplay/` first. + * @returns {void} + */ 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); } - + +/** + * Populates `accounts`/`chars` from `AccountsToClean` and `AdvancedCleanerConfig`, stopping the + * script if no accounts were configured. + * @returns {void} + */ 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) { + const { rangeStart, rangeStop, accountPrefix, accountPassword, accountRealm } = AdvancedCleanerConfig; + for (let index = rangeStart; index <= rangeStop; index += 1) { + 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); + } } - + +/** @returns {boolean} False as soon as a character fails to delete, true once all are done. */ 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); + + 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, + }; +})(); +/** + * Entry point: waits for the D2Bot handle and game info, then cleans configured accounts/characters + * by driving `locationAction` forever. + * @returns {void} + */ 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..8082c4f3d 100644 --- a/d2bs/kolbot/D2BotFollow.dbj +++ b/d2bs/kolbot/D2BotFollow.dbj @@ -3,439 +3,398 @@ * @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); - } +/** + * D2BotFollow entry point: waits for game/profile info from the client, then drives Starter's join/create + * loop for the follower character. + * @returns {void} + */ +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..f7099925f 100644 --- a/d2bs/kolbot/D2BotGameAction.dbj +++ b/d2bs/kolbot/D2BotGameAction.dbj @@ -3,401 +3,367 @@ * @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, + }; +})(); +/** + * Entry point: waits for the D2Bot handle and game info, then drives the GameAction loop + * (login/char-select/in-game state machine) until the script is stopped. + */ 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..527fe62b5 100644 --- a/d2bs/kolbot/D2BotLead.dbj +++ b/d2bs/kolbot/D2BotLead.dbj @@ -1,401 +1,138 @@ -/* 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); - } - - ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3); - D2Bot.updateRuns(); - } +const locationAction = (function () { + const { + run, + } = require("./libs/oog/Locations"); - DataFile.updateStats("debugInfo", JSON.stringify({currScript: "none", area: "out of game"})); + return { + run: run, + }; +})(); - 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); - } +/** + * Entry point for the Lead script: forwards copydata/scriptmsg events to Starter, waits for + * game/profile info from D2Bot#, then loops tracking in-game status and dispatching location + * actions between games. Never returns. + */ +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..0aa5879bf 100644 --- a/d2bs/kolbot/D2BotMule.dbj +++ b/d2bs/kolbot/D2BotMule.dbj @@ -3,1010 +3,585 @@ * @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 - */ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // +const { StarterConfig } = require("./libs/systems/automule/config/StarterConfig"); +Object.assign(Starter.Config, StarterConfig); -// 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: "" }; - -/** - * 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; } +/** + * Entry point: drives the OOG mule login/game-join state machine (location handlers) forever, + * coordinating with the in-game mule thread over copydata/scriptmsg. + */ 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 () { + const Controls = require("./libs/modules/Control"); + 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(e.fileName.lastIndexOf("\\") + 1, e.fileName.length) + + " #" + e.lineNumber + ")" + ); + } + + delay(100); + } } diff --git a/d2bs/kolbot/D2BotMuleLog.dbj b/d2bs/kolbot/D2BotMuleLog.dbj index b17c3fa9e..df4112a88 100644 --- a/d2bs/kolbot/D2BotMuleLog.dbj +++ b/d2bs/kolbot/D2BotMuleLog.dbj @@ -3,360 +3,332 @@ * @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)); - } +if (DataFile.init()) { + Starter.firstRun = true; +} - delay(1000); - } +const usingDropper = isIncluded("systems/dropper/DropperSetup.js"); +const accounts = []; +const chars = []; +const parseInfo = function () { + // external dropper plugin: usingDropper is false unless the untracked DropperSetup.js + // exists (include() of a missing file silently returns false) + // eslint-disable-next-line no-undef + 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); + + 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, + }; +})(); - locationAction(getLocation()); - delay(1000); - } +/** + * Entry point: drives the OOG login/game-join state machine (location handlers) and + * launches the in-game MuleLogger heartbeat thread. + */ +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..e7a8ae7c6 100644 --- a/d2bs/kolbot/D2BotPubJoin.dbj +++ b/d2bs/kolbot/D2BotPubJoin.dbj @@ -3,442 +3,483 @@ * @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)); - } - - let nextGame = (gameName || this.randomString(null, true)); - nextGame = incrementString(nextGame); - - DataFile.updateStats("nextGame", nextGame); + /** + * @param {string} text + * @returns {string} `text` with its trailing numeric suffix incremented, zero-padded to the same width + */ + function incrementString (text) { + return text.replace(/(\d*)$/, (_, t) => (+t + 1).toString().padStart(t.length, 0)); + } + + let nextGame = (gameName || Starter.randomString(null, true)); + nextGame = incrementString(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); - } - } +const GameTracker = { + _path: "data/" + me.profile + "/pubjoin.json", + + /** + * Creates the per-profile data folder and pubjoin.json tracker file if they don't already exist. + */ + 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, + }; +})(); - 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); - } +/** + * Entry point: waits for the Starter window handle, then drives the public-game join loop. + */ +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..878ce2b94 100644 --- a/d2bs/kolbot/default.dbj +++ b/d2bs/kolbot/default.dbj @@ -1,270 +1,334 @@ /** * @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"); + +/** + * Entry point for the default game thread: runs a game session (chores, scripts, mule/torch checks). + * @returns {boolean} true when the game session is complete and the thread should exit + */ 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) { + /** + * Wraps the builtin load() with a 500ms delay so a spawned thread has time to initialize. + * @param {...unknown} args forwarded to the original load() + */ + 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(); + + /** + * scriptmsg listener: on "soj" resets the SoJ sale wait counter so it starts counting again. + * @param {unknown} msg + */ + this.scriptEvent = function (msg) { + if (msg === "quit") return; + if (typeof msg === "string" && msg === "soj") { + sojPause = true; + sojCounter = 0; + } + }; + + /** + * copydata listener: handles the "Mule Profile" D2Bot# command (mode 0) and getProfile replies + * (mode 1638, which carry the character's Tag and trigger GameAction.init). + * @param {number} mode + * @param {string} msg + */ + 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..9012835ac 100644 --- a/d2bs/kolbot/libs/OOG.js +++ b/d2bs/kolbot/libs/OOG.js @@ -1,2155 +1,2034 @@ +/* eslint-disable @stylistic/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 ~~~ // + /** + * Scroll the character select list down by sending several DownArrow key presses. + * @returns {void} + */ + scrollDown: function () { + me.blockMouse = true; + for (let i = 0; i < 4; i++) { + sendKey(sdk.keys.code.DownArrow); + } + me.blockMouse = false; + }, + + /** + * Select the given realm/gateway from the main menu, opening the gateway list if needed. + * @param {number} realm - realm index (0 = US West, 1 = US East, 2 = Asia, 3 = Europe) + * @returns {boolean} true once the requested realm is selected, false if the gateway control could not be found after retries + * @throws {Error} if realm is undefined, not a number, or outside the 0-3 range + */ + 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) { + me.blockMouse = false; + + 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; + }, + + /** + * Collect the names of all characters on the current character select screen. + * @returns {string[]} array of character names (empty if not on the character select screen) + */ + getCharacters: function () { + 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 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(5000, sdk.game.locations.CharSelect); + + 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; + } + }, + + /** + * Register/verify an email on the RegisterEmail screen, cycling until reaching character select. + * @param {string} [email] - email local-part; a random string is generated when empty + * @param {string} [domain] - email domain suffix appended to the local-part + * @returns {boolean} true if the email was accepted (reached char select or fresh account), false if not on the RegisterEmail screen or a login error occurred + */ + 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; + }, + + /** + * @typedef {Object} AccountInfo + * @property {string} account + * @property {string} password + * @property {string} realm + */ + + /** + * Drive the menus to create a new battle.net/open battle.net account, cycling until an empty char screen. + * @param {AccountInfo} info - account credentials and realm used to create the account + * @returns {boolean} true once the empty character select screen is reached, false on a login error + */ + 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(); + me.blockMouse = false; + + return false; + default: + break; + } + + delay(delays.random()); + } + + me.blockMouse = false; + + return true; + }, + + /** + * Drive the menus to log into an existing account, cycling until reaching a character select screen. + * @param {AccountInfo} info - account credentials and realm used to log in + * @returns {boolean} true if the account reached a (populated or empty) character select screen, false on connection failure, realm down, timeout, or an unhandled location + */ + 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) { + me.blockMouse = false; + + return false; + } + + delay(100); + } + + delay(1000); + + me.blockMouse = false; + + return getLocation() === sdk.game.locations.CharSelect || getLocation() === sdk.game.locations.CharSelectNoChars; + }, + + /** + * Join the given chat channel from the lobby, opening the chat/channel list as needed. + * @param {string} channel - name of the channel to join + * @returns {boolean} true if the channel was joined before the timeout, false otherwise + */ + 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; + }, + + /** + * Fill out and submit the create-game window with the given name, password and difficulty. + * @param {string} name - game name + * @param {string} pass - game password + * @param {string} diff - difficulty selection ("Normal", "Nightmare", "Highest", or default for Hell) + * @param {number} [delay] - optional pre-create delay in milliseconds + * @returns {void} + */ + 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; + }, + + /** + * Read the join-game list control and return the listed games. + * @returns {{ gameName: string, players: string }[] | false} array of games with name/player counts, or false if the list has no text + */ + 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; + }, + + /** + * Parse the create-game "in line" control to get the current queue position. + * @returns {number} position in line, or 0 when there is no queue + */ + 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 + }, + + /** + * Drive the Other Multiplayer / Open Battle.net / TCP-IP / single-player menus until in game, + * in the lobby, or at the TCP/IP enter-IP prompt. + * @returns {boolean} true if in game (or at the enter-IP prompt), false otherwise. + * Note: the final `getLocation() === [sdk.game.locations.TcpIpEnterIp]` compares against an array + * and is always false, so in practice the result reflects `me.ingame`. + */ + 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 === success + 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, + accountExists: false, + 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, + + /** + * Say a message in chat, but only when chat output is enabled. + * @param {string} string - message to say + * @returns {void} + */ + sayMsg: function (string) { + if (!this.useChat) return; + say(string); + }, + + /** + * Format the elapsed time since the given tick as a " (HH:MM:SS)" string. + * @param {number} tick - starting tick count from getTickCount() + * @returns {string} elapsed time formatted as " (HH:MM:SS)" + */ + timer: function (tick) { + return " (" + new Date(getTickCount() - tick).toISOString().slice(11, -5) + ")"; + }, + + /** + * Wait until leaving the given location, entering a game, or the timeout elapses. + * @param {number} time - maximum time to wait in milliseconds + * @param {number} location - location id (sdk.game.locations.*) to wait out of + * @returns {boolean} true if the current location differs from the given location when the wait ends + */ + locationTimeout: function (time, location) { + let endtime = getTickCount() + time; + + while (!me.ingame && getLocation() === location && endtime > getTickCount()) { + delay(500); + } + + return (getLocation() !== location); + }, + + /** + * Compute the next game name (incrementing/looping the counter) and persist it to the data file. + * @param {{ gameName?: string }} [gameInfo] - optional base game info; a random name is used when gameName is absent + * @returns {void} + */ + 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); + }, + + /** + * Report the run count to D2Bot then log in and exit back to the menu to refresh the profile. + * @returns {void} + */ + updateCount: function () { + D2Bot.updateCount(); + delay(1000); + Controls.BattleNet.click(); + + try { + login(me.profile); + } catch (e) { + return; + } + + delay(1000); + Controls.BottomLeftExit.click(); + }, + + waypointCache: {}, + + /** + * Handle a scriptBroadcast message: cache waypoint data (object messages) or set the relevant + * mule/torch/crafting/pingquit flags and respond to cache/mode requests (string messages). + * @param {object | string} msg - the broadcast message (waypoint-cache object or a known command string) + * @returns {void} + */ + 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; + + 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; + } + }, + + /** + * Generate a random lowercase (optionally alphanumeric) string. + * @param {number} [len] - length of the string; a random length between 5 and 14 is used when falsy + * @param {boolean} [useNumbers] - include digits 0-9 in the character set + * @returns {string} the generated random string + */ + 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 { + /** + * Select the profile's configured difficulty on the single-player difficulty screen, + * falling back to the highest one that is unlocked. + * @returns {boolean} true if the difficulty screen was left before the timeout + */ + 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); + }, + + /** + * Dispatch the current error location to its handler, restarting the client when the location is unhandled. + * @returns {void} + */ + 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(); + } + }, + + /** + * Handle a character select error screen, stopping or switching keys when the CD key is realm disabled. + * @returns {boolean} true if the screen cleared, false if still stuck on the connecting screen + */ + 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; + }, + + /** + * Handle a realm down: exit to the main menu, wait out the configured delay, then switch keys or restart. + * @returns {void} + */ + 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(); + } + }, + + /** + * Report the create-game queue position, abandoning a failed creation or waiting out a queue restriction. + * @returns {void} + */ + 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; + } + } + }, + + /** + * Handle a "game does not exist" result by switching a flagged CD key or waiting out the configured timeout, + * then marking the starter ready for the next game. + * @returns {void} + */ + 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"; + }, + + /** + * Handle a TCP/IP or battle.net unable-to-connect screen, retrying a couple of times before + * waiting out the configured version-error or connect delay. + * @returns {void} + */ + 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; + } + }, + + /** + * Open the create-game window from the lobby, handling a dead hardcore character and a bugged create button. + * @returns {boolean} false if the character is dead or the create-game screen was not reached, true otherwise + */ + 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); + }, + + /** + * Open the join-game window from the lobby, retrying once through the create-game window when the button is bugged. + * @returns {void} + */ + 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; + } + } + }, + + /** + * Log the current profile in, first correcting a character select screen that does not match the profile type. + * @param {boolean} [otherMultiCheck] - hand off to the other multiplayer flow when on the OtherMultiplayer screen + * @returns {boolean} false when exiting a character select screen wrong for this profile type, true otherwise + */ + 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; + }, + + /** + * Pick the other-multiplayer entry matching the profile type (TCP/IP host or join, open battle.net), cancelling + * out of the screen for any other type. + * @returns {void} + */ + 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(); + } + }, + + /** + * Try to clear a stuck character select "connecting" screen by exiting back out of it. + * @returns {boolean} true if not (or no longer) on the connecting screen + */ + 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..0d7b338ab 100644 --- a/d2bs/kolbot/libs/Polyfill.js +++ b/d2bs/kolbot/libs/Polyfill.js @@ -1,670 +1,1491 @@ /** * @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 + }; }; +/** + * Computes an approximate edit-distance style difference count against another string using the LCS graph. + * @param {string} stringB - String to compare against. + * @returns {number} Difference count, or `Infinity` if computing the LCS graph fails. + */ String.prototype.diffCount = function (stringB) { - try { - if (typeof stringB !== "string" || !stringB) { - return this.length; - } + try { + if (typeof stringB !== "string" || !stringB) { + return this.length; + } - if (!this.length) { - return stringB.length; - } + if (!this.length) { + return stringB.length; + } - let graph = this.lcsGraph(stringB); + let graph = this.lcsGraph(stringB); - return (Math.max(graph.a.length, graph.b.length) - graph.graph[graph.a.length - 1][graph.b.length - 1]); - } catch (err) { - print(err.stack); - } + return (Math.max(graph.a.length, graph.b.length) - graph.graph[graph.a.length - 1][graph.b.length - 1]); + } catch (err) { + console.log(err.stack); + } - return Infinity; + return Infinity; }; if (!String.prototype.includes) { - String.prototype.includes = function (search, start) { - "use strict"; - if (typeof start !== "number") { - start = 0; - } - - if (start + search.length > this.length) { - return false; - } else { - return this.indexOf(search, start) !== -1; - } - }; + /** + * @param {string} search + * @param {number} [start] + * @returns {boolean} + */ + 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; + } + }; } +/** + * @param {boolean} [downcase] - When true, lowercases the remainder of the string after the first character. + * @returns {string} + */ 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); +/** + * @param {number} targetLength + * @param {string} [padString] + * @returns {string} + */ +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 +/** + * @param {number} targetLength + * @param {string} [padString] + * @returns {string} + */ +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 - }); -} +/** + * @param {number} count + * @returns {string} + * @throws {TypeError} If called on `null`/`undefined`. + * @throws {RangeError} If `count` is negative, infinite, or the result would overflow max string size. + */ +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) { + /** @returns {string} */ + 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; - }; + /** + * @param {string} prefix + * @returns {boolean} + */ + 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; - }; + /** + * @param {string} search + * @param {number} [this_len] - Position to treat as the end of the string; defaults to the string's length. + * @returns {boolean} + */ + 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) { + /** + * @param {number} pos - Zero-based index; negative counts back from the end. + * @returns {string|undefined} + */ + 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.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); +}; + +/** + * Recursively filters outlier numeric values whose average distance to other elements exceeds + * the group average, giving up after 10 recursive steps. + * @param {number} [step] - Current recursion depth (internal use). + * @returns {Array} + */ +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 + 1); + } + + 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", { + /** + * @param {function(*, number, Array): boolean} predicate + * @param {*} [thisArg] - Value to use as `this` when executing `predicate`. + * @returns {number} Index of the first matching element, or -1 if none match. + */ + 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) { + /** + * @param {*} val - Element to remove (first occurrence only); falsy/undefined values are rejected. + * @returns {Array} This array, mutated in place. + * @throws {Error} If the array is empty or `val` is falsy. + */ + 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); - }; + /** @returns {Array} A new array with all falsy elements (null, undefined, etc.) removed. */ + 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(); - }; + /** + * @param {function(*, number, Array): *} callback + * @returns {Array} Mapped results with null/undefined entries filtered out. + */ + 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))]; - }; + /** @returns {*} A random element, or `null` if the array is empty. */ + 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; - }; + /** + * @param {*} e + * @returns {boolean} + */ + 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]; - }; + /** + * @param {number} pos - Zero-based index; negative counts back from the end. + * @returns {*} + */ + 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)); - }; + /** + * @param {Array} other + * @returns {Array} Elements present in both this array and `other`. + */ + 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)); - }; + /** + * @param {Array} other + * @returns {Array} Elements of this array not present in `other`. + */ + 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))); - }; + /** + * @param {Array} other + * @returns {Array} Elements present in exactly one of the two arrays. + */ + 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; + /** + * Shuffles the array in place using the Fisher-Yates algorithm. + * @returns {Array} This array, mutated in place. + */ + 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; + }; +} - // While there are elements in the array - while (counter > 0) { - // Pick a random index - index = Math.floor(Math.random() * counter); +// 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", { + /** + * @param {function(*, number, Array): boolean} predicate + * @param {*} [thisArg] - Value to use as `this` when executing `predicate`. + * @returns {*} The first matching element, or `undefined` if none match. + */ + value: function (predicate) { + if (this === null) { + throw new TypeError('"this" is null or not defined'); + } + + let o = Object(this); + + let len = o.length >>> 0; + + if (typeof predicate !== "function") { + throw new TypeError("predicate must be a function"); + } + + let thisArg = arguments[1]; - // Decrease counter by 1 - counter -= 1; + let k = 0; - // And swap the last element with it - temp = this[counter]; - this[counter] = this[index]; - this[index] = temp; - } + while (k < len) { + let kValue = o[k]; - return this; - }; + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } + + k++; + } + + return undefined; + }, + configurable: true, + writable: true + }); } -// Trim String -if (!String.prototype.trim) { - String.prototype.trim = function () { - return this.replace(/^\s+|\s+$/g, ""); - }; +// Fill an array with the same value from start to end indexes. +/** + * @param {*} value + * @param {number} [start] + * @param {number} [end] + * @returns {Array} This array, mutated in place. + */ +Array.prototype.fill = function (value, start = 0, end = undefined) { + let stop = end || this.length; + for (let i = start; i < stop; i++) { + this[i] = value; + } + return this; +}; + +if (!Array.prototype.first) { + /** + * @description Return the first element or undefined + * @return {undefined | *} + */ + Array.prototype.first = function () { + return this.length > 0 ? this[0] : undefined; + }; } -// 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"); - } +if (!Array.prototype.last) { + /** + * @description Return the last element or undefined + * @return {undefined | *} + */ + Array.prototype.last = function () { + return this.length > 0 ? this[this.length - 1] : undefined; + }; +} - let to = Object(target); +if (!Array.prototype.flat) { + Object.defineProperty(Array.prototype, "flat", { + /** + * @description Flatten an array with depth parameter. + * @see https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/flat + * @param {number} [depth] - Maximum recursion depth; defaults to 1. + * @return {Array<*>} + */ + 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 + }); +} - for (let index = 1; index < arguments.length; index++) { - let nextSource = arguments[index]; +if (!Array.of) { + Object.defineProperty(Array, "of", { + /** + * @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 + * @param {...*} elements - Elements to include in the new array. + * @return {Array<...args>} + */ + value: function of () { + return Array.prototype.slice.call(arguments); + }, + configurable: true, + writable: true + }); +} - if (nextSource !== null) { - for (let nextKey in nextSource) { - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } +if (!Array.prototype.toReversed) { + /** + * @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} + */ + Array.prototype.toReversed = function () { + return this.slice().reverse(); + }; +} - return to; - }, - writable: true, - configurable: true - }); +if (!Array.prototype.toSorted) { + /** + * 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. + */ + Array.prototype.toSorted = function (compareFunction) { + return this.slice().sort(compareFunction); + }; } -// 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'); - } +if (!Array.prototype.toSpliced) { + /** + * 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. + */ + 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; + }; +} - let o = Object(this); +if (!Array.prototype.with) { + /** + * @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. + */ + 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; + }; +} - let len = o.length >>> 0; +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Object Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Object.assign + * - Object.entries + * - Object.values + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ - if (typeof predicate !== "function") { - throw new TypeError("predicate must be a function"); - } +if (typeof Object.assign !== "function") { + Object.defineProperty(Object, "assign", { + /** + * @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 + * @param {Object} target - Target object. + * @param {...Object} sources - One or more source objects. + * @returns {Object} The target object. + */ + 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 + }); +} - let thisArg = arguments[1]; +if (!Object.values) { + /** + * @param {Object} source + * @returns {Array<*>} The own enumerable property values of `source`. + */ + Object.values = function (source) { + return Object.keys(source) + .map(function (k) { + return source[k]; + }); + }; +} - let k = 0; +if (!Object.entries) { + /** + * @param {Object} source + * @returns {Array>} Array of `[key, value]` pairs for `source`'s own enumerable properties. + */ + Object.entries = function (source) { + return Object.keys(source) + .map(function (k) { + return [k, source[k]]; + }); + }; +} - while (k < len) { - let kValue = o[k]; +if (!Object.hasOwn) { + /** + * @param {Object} obj + * @param {string|symbol} prop + * @returns {boolean} + */ + Object.hasOwn = function (obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); + }; +} - if (predicate.call(thisArg, kValue, k, o)) { - return kValue; - } +// 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")(); - k++; - } +if (!global.hasOwnProperty("require")) { + let cache; + Object.defineProperty(global, "require", { + /** + * Lazily loads and caches require.js on first access. + * @returns {*} The cached `require` implementation. + */ + get: function () { + if (cache) return cache; + !isIncluded("require.js") && include("require.js"); + return cache; // cache is loaded by require.js + }, + /** @param {*} v */ + set: function (v) { + cache = v; + } + }); +} - return undefined; - }, - configurable: true, - writable: true - }); +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({}, { + /** + * Lazily loads `.env.json` into the backing store on first access. + * @param {Object} target + * @param {string|symbol} prop + * @returns {*} The stored value for `prop`, or `undefined`. + */ + 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; + }, + + /** + * @param {Object} target + * @param {string|symbol} prop + * @param {*} value + * @returns {boolean} + */ + set: function (target, prop, value) { + if (!initialized) { + this.get(target, "version"); + } + + envStore[prop] = value; + return true; + }, + + /** + * @param {Object} target + * @param {string|symbol} prop + * @returns {boolean} + */ + has: function (target, prop) { + if (!initialized) { + this.get(target, "version"); + } + + return prop in envStore; + }, + + /** + * @param {Object} target + * @returns {Array} + */ + ownKeys: function (target) { + if (!initialized) { + this.get(target, "version"); + } + + return Object.keys(envStore); + } + }), + writable: false, + configurable: false + }); } -// 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; - } -}; +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Math Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Math.randomIntBetween + * - Math.trunc + * - Math.percentDifference + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ +// Returns a random integer between start and end included. /** - * @description Return the first element or undefined - * @return undefined|* + * @param {number} start + * @param {number} end + * @returns {number} A random integer in the inclusive range [start, end]. */ -if (!Array.prototype.first) { - Array.prototype.first = function () { - return this.length > 0 ? this[0] : undefined; - }; +Math.randomIntBetween = function (start, end) { + let min = Math.ceil(start); + let max = Math.floor(end); + return Math.floor(Math.random() * (max - min + 1)) + min; +}; + +if (!Math.trunc) { + /** + * Polyfill for Math.trunc + * Static method returns the integer part of a number by removing any fractional digits. + * @static + * @param {number} number + * @returns {number} + */ + Math.trunc = function (number) { + return number < 0 ? Math.ceil(number) : Math.floor(number); + }; } /** - * @description Return the last element or undefined - * @return undefined|* + * @param {number} value1 + * @param {number} value2 + * @returns {number} Percent difference between the two values, truncated to an integer. */ -if (!Array.prototype.last) { - Array.prototype.last = function () { - return this.length > 0 ? this[this.length - 1] : undefined; - }; -} +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); +}; /** - * @description Flatten an array with depth parameter. - * @see https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/flat - * @return array + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Map Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Map.prototype.forEach + * - Map.prototype.toString + * - Map.prototype.keys + * - Map.prototype.values + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // */ -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 -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); - } + +if (typeof Map.prototype.forEach !== "function") { + /** + * @param {function(*, *, Map): void} callbackFn + * @param {*} [thisArg] + */ + Map.prototype.forEach = function (callbackFn, thisArg) { + thisArg = thisArg || this; + for (let [key, value] of this.entries()) { + callbackFn.call(thisArg, value, key, this); + } + }; +} + +/** @returns {string} JSON string of the map's entries as a plain object. */ +Map.prototype.toString = function () { + let obj = {}; + for (let [key, value] of this.entries()) { + obj[key] = value; + } + return JSON.stringify(obj); }; -String.prototype.padStart = function padStart(targetLength, padString) { - targetLength = targetLength >> 0; //floor if number or convert non-number to 0; - padString = String(typeof padString !== "undefined" ? padString : " "); - if (this.length > targetLength) { - return String(this); - } else { - targetLength = targetLength - this.length; - if (targetLength > padString.length) { - padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed - } - return padString.slice(0, targetLength) + String(this); - } +/** @returns {Array} */ +Map.prototype.keys = function () { + let keys = []; + + for (let [key, _value] of this.entries()) { + keys.push(key); + } + return keys; }; -String.prototype.repeat = function(count) { - "use strict"; - if (this == null) throw new TypeError("can't convert " + this + " to object"); - let str = "" + this; - count = +count; - // eslint-disable-next-line no-self-compare - if (count !== count) { - count = 0; - } - if (count < 0) throw new RangeError("repeat count must be non-negative"); - if (count === Infinity) throw new RangeError("repeat count must be less than infinity"); - - count = Math.floor(count); - if (str.length === 0 || count === 0) { - return ""; - } - if (str.length * count >= 1 << 28) { - throw new RangeError( - "repeat count must not overflow maximum string size" - ); - } - let rpt = ""; - for (;;) { - if ((count & 1) === 1) { - rpt += str; - } - count >>>= 1; - if (count === 0) { - break; - } - str += str; - } - return rpt; +/** @returns {Array<*>} */ +Map.prototype.values = function () { + let values = []; + + for (let [_key, value] of this.entries()) { + values.push(value); + } + return values; }; -if (!Object.values) { - Object.values = function (source) { - return Object.keys(source).map(function (k) { return source[k]; }); - }; +/** + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Set Polyfills ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + * - Set.prototype.forEach + * - Set.prototype.keys + * - Set.prototype.values + * - Set.prototype.entries + * - Set.prototype.isSuperset + * - Set.prototype.union + * - Set.prototype.intersection + * - Set.prototype.difference + * - Set.prototype.symmetricDifference + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // + */ + +if (typeof Set.prototype.forEach !== "function") { + /** + * @param {function(*, *, Set): void} callbackFn + * @param {*} [thisArg] + */ + 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") { + /** @returns {Array<*>} */ + Set.prototype.keys = function () { + let keys = []; + for (let item of this) { + keys.push(item); + } + return keys; + }; } +if (typeof Set.prototype.values !== "function") { + /** @returns {Array<*>} */ + Set.prototype.values = function () { + let values = []; + for (let item of this) { + values.push(item); + } + return values; + }; +} + +if (typeof Set.prototype.entries !== "function") { + /** @returns {Array>} Array of `[item, item]` pairs. */ + Set.prototype.entries = function () { + let entries = []; + for (let item of this) { + entries.push([item, item]); + } + return entries; + }; +} + +/** + * @param {Set|Iterable} subset + * @returns {boolean} True if every element of `subset` is present in this set. + */ +Set.prototype.isSuperset = function (subset) { + for (let item of subset) { + if (!this.has(item)) { + return false; + } + } + return true; +}; + +/** + * @param {Set|Iterable} setB + * @returns {Set} A new set containing all elements from both sets. + */ +Set.prototype.union = function (setB) { + let union = new Set(this); + for (let item of setB) { + union.add(item); + } + return union; +}; + +/** + * @param {Set|Iterable} setB + * @returns {Set} A new set containing elements present in both sets. + */ +Set.prototype.intersection = function (setB) { + let intersection = new Set(); + for (let item of setB) { + if (this.has(item)) { + intersection.add(item); + } + } + return intersection; +}; + +/** + * @param {Set|Iterable} setB + * @returns {Set} A new set containing elements present in exactly one of the two sets. + */ +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; +}; + +/** + * @param {Set|Iterable} setB + * @returns {Set} A new set containing this set's elements minus those in `setB`. + */ +Set.prototype.difference = function (setB) { + let difference = new Set(this); + for (let item of setB) { + difference.delete(item); + } + return difference; +}; + +/** @returns {string} JSON string of the set's elements. */ +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; + }; + + /** @param {...*} args */ + console.log = function (...args) { + // use call to avoid type errors + print.call(null, args.map(argMap).join(",")); + }; + + console.printDebug = true; + /** + * Logs only when `console.printDebug` is true, prefixed with the caller's file and line. + * @param {...*} args + */ + 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(",")); + } + }; + + /** + * Logs a warning prefixed with the caller's file and line. + * @param {...*} args + */ + 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(",")); + }; + + /** @param {string|Error} [error] */ + 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); + } + }; + + /** + * Logs a stack trace built from `new Error().stack`, formatted innermost-frame-last. + */ + 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); + } + }; + + /** + * @param {boolean} [start] - `true` logs a "Start" prefix, `false` an "End" prefix, any other value a plain prefix. + * @param {string} [msg] + * @param {string} [timer] - Key registered via `console.time`; appends elapsed duration and clears the timer. + */ + 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 = []; + + // Create table headers + if (!columns) { + columns = Object.keys(data[0]); + } + let 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", { + /** @returns {string} Date formatted as "[MM/DD/YYYY]". */ + 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..9a5c09600 --- /dev/null +++ b/d2bs/kolbot/libs/SoloPlay @@ -0,0 +1 @@ +Subproject commit 9a5c096007621f8ab65580f0c9cae89a9c1b115d 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..e5417173f 100644 --- a/d2bs/kolbot/libs/config/Amazon.js +++ b/d2bs/kolbot/libs/config/Amazon.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Amazon config file /* Brief instructions: @@ -15,720 +14,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..51fb96a42 100644 --- a/d2bs/kolbot/libs/config/Assassin.js +++ b/d2bs/kolbot/libs/config/Assassin.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Assassin config file /* Brief instructions: @@ -15,726 +14,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..8ca9c77e9 100644 --- a/d2bs/kolbot/libs/config/Barbarian.js +++ b/d2bs/kolbot/libs/config/Barbarian.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Barbarian config file /* Brief instructions: @@ -15,715 +14,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..1d605a673 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,901 @@ */ 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"); } + +/** @type {AutoBuildTemplate} */ +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..1ce0c3317 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,924 @@ */ 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"); +/** @type {AutoBuildTemplate} */ +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..41f29c10f 100644 --- a/d2bs/kolbot/libs/config/Druid.js +++ b/d2bs/kolbot/libs/config/Druid.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Druid config file /* Brief instructions: @@ -15,719 +14,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..bcba32565 100644 --- a/d2bs/kolbot/libs/config/Necromancer.js +++ b/d2bs/kolbot/libs/config/Necromancer.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Necromancer config file /* Brief instructions: @@ -15,740 +14,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..6b5b0b856 100644 --- a/d2bs/kolbot/libs/config/Paladin.js +++ b/d2bs/kolbot/libs/config/Paladin.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Paladin config file /* Brief instructions: @@ -15,719 +14,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..9777ddc70 100644 --- a/d2bs/kolbot/libs/config/Sorceress.js +++ b/d2bs/kolbot/libs/config/Sorceress.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Sorceress config file /* Brief instructions: @@ -15,721 +14,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..1bb20a530 100644 --- a/d2bs/kolbot/libs/config/_BaseConfigFile.js +++ b/d2bs/kolbot/libs/config/_BaseConfigFile.js @@ -1,898 +1,1032 @@ -/* eslint-disable indent */ /* Brief instructions: * - the role of this file is only to keep the required info for completing the specific character configuration files * - 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..5b99cae05 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attack.js @@ -0,0 +1,2748 @@ +/// +/** + * @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 + /** + * Load the class attack file and prime attack state (primary slot, skills, infinity/auradin flags) + * @param {boolean} [notify] Warn on console when the configured attack skills are unusable + */ + 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; + }, + + getScarinessLevel: (function () { + // Summoner-type monsters (shamans, unravelers, ancients, etc.) used for scariness scoring + const summonerIds = [ + 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 + ]; + /** + * @description Determine scariness of monster for monster sorting + * @param {Monster} unit + * @returns {number} scariness + */ + return function (unit) { + // todo - define summonertype prototype + let scariness = 0; + + // 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 + summonerIds.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 = []; + /** @type {Monster} */ + let monster; + + 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); + } + if (Config.FastFindItem && me.classid === sdk.player.class.Barbarian) { + 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, + /** + * Restrict this clear to monsters standing inside the room currently being explored + * @param {Monster} unit + * @returns {boolean} + */ + 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) { + /** + * Walkable center of a room, falling back to the geometric center when nothing walkable is near + * @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]; + } + + 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) { + /** + * Order rooms by straight-line distance from the room we are standing in + * @param {[number, number, Room]} a + * @param {[number, number, Room]} b + * @returns {number} comparator: negative if a is closer, positive if b is closer, 0 if equal + */ + function RoomSort (a, b) { + return getDistance(myRoom[0], myRoom[1], a[0], a[1]) - getDistance(myRoom[0], myRoom[1], b[0], b[1]); + } + + /** + * Order rooms by walking distance from the room we are standing in + * @param {[number, number, Room]} a + * @param {[number, number, Room]} b + * @returns {number} comparator: negative if a is closer, positive if b is closer, 0 if equal + */ + 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; + }, + + sortMonsters: (function () { + // Monsters sorted to the front of the kill order (summoners, oblivion knights, etc.) + 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 + ]; + /** + * @description Sort monsters based on distance, spectype and classId (summoners are attacked first) + * @param {Monster} unitA + * @param {Monster} unitB + * @returns {number} comparator: negative if unitA should be attacked first, positive if unitB, 0 if equal + * @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 + */ + return 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; + + 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); + }; + })(), + + validSpot: (function () { + const nonFloorAreas = [ + sdk.areas.ArcaneSanctuary, sdk.areas.RiverofFlame, sdk.areas.ChaosSanctuary, + sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit + ]; + /** + * @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 + */ + return 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); + + // 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 {PathNode} 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 count = 999; + + let monList = this.buildMonsterList(); + monList.sort(Sort.units); + + if (this.getMonsterCount(me.x, me.y, 15, monList) === 0) { + return new PathNode(me.x, me.y); + } + + 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 new PathNode(grid[index].x, grid[index].y); + } + + return false; + }, + + /** + * Move to the least crowded spot around a unit + * @param {Unit} unit + * @param {number} distance Max offset from the unit the spot may be + * @param {number} spread Grid step used when sampling candidate spots + * @param {number} range Radius each candidate spot is scored for nearby monsters + * @returns {boolean} Successfully moved to a safe spot + */ + 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 () { + const ignoredIds = [sdk.monsters.Diablo]; // why is diablo ignored? + /** + * Count attackable monsters within range of a spot, heavily penalizing spots standing in fire + * @param {number} x + * @param {number} y + * @param {number} range + * @param {Monster[]} list Pre-built monster list to check against + * @returns {number} + */ + return function (x, y, range, list) { + let count = 0; + + for (let i = 0; i < list.length; i += 1) { + if (ignoredIds.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; + }; + })(), + + /** + * Build evenly spaced coords over a rectangle, keeping only those with known collision data + * @param {number} xmin + * @param {number} xmax + * @param {number} ymin + * @param {number} ymax + * @param {number} spread Distance between sampled coords + * @returns {(PathNode & { coll: number })[]} + * @throws {Error} If the rectangle is inverted or spread is less than 1 + */ + 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; + }, + + getSkillElement: (function () { + // Indexed by the "etype" column of skills.txt + const elements = ["physical", "fire", "lightning", "magic", "cold", "poison", "none"]; + /** + * @description Get element by skill number + * @param {number} skillId + * @returns {DamageType | "none" | false} + */ + return function (skillId) { + 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; + } + }, + + /** + * Resistance reduction our Lower Resist curse applies, 0 when we can't cast it + * @returns {number} Percent of resistance lowered + */ + 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; + }, + + /** + * Resistance reduction our Conviction aura applies, treating an equipped Infinity as level 12 + * @returns {number} Percent of resistance lowered + */ + 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; + }, + + getIntoPosition: (function () { + const angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 135, -135, 180]; + /** + * 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 + */ + return 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 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 : []; + }, + + whirlwind: (function () { + const baseAngles = [180, 175, -175, 170, -170, 165, -165, 150, -150, 135, -135, 45, -45, 90, -90]; + /** + * @param {Monster} unit + * @returns {boolean} + */ + return function (unit) { + if (!unit.attackable) return true; + + const angles = unit.isSpecial ? [120].concat(baseAngles) : baseAngles; + + 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..41789170c --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Amazon.js @@ -0,0 +1,279 @@ +/** +* @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; + }, + + /** + * Runs post-attack upkeep: handles precast skills, visits town to restock ammo if repair is needed, and resets the light fury tick. + */ + 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..a22ebaed4 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Assassin.js @@ -0,0 +1,298 @@ +/** +* @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; + }, + + /** @returns {void} */ + 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..aafdf80ad --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Barbarian.js @@ -0,0 +1,303 @@ +/** +* @filename Barbarian.js +* @author kolton, theBGuy +* @desc Barbarian attack sequence +* +*/ + +(function (module) { + const councilClassIds = [sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3]; + const waterWatcherIds = [sdk.monsters.WaterWatcherHead, sdk.monsters.WaterWatcherLimb]; + // States that make a corpse unhorkable + const unhorkableStates = [ + sdk.states.FrozenSolid, + sdk.states.Revive, + sdk.states.Redeemed, + sdk.states.CorpseNoDraw, + sdk.states.Shatter, + sdk.states.RestInPeace, + sdk.states.CorpseNoSelect + ]; + + /** + * @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; + } + if (councilClassIds.indexOf(unit.classid) === -1 && unit.spectype === sdk.monsters.spectype.All) { + // why ignore all normal monsters? + return false; + } + + 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; + } + + return (unit.distance <= 25 + && !checkCollision(me, unit, sdk.collision.Ranged) + && unhorkableStates.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..4b47d90df --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Druid.js @@ -0,0 +1,215 @@ +/** +* @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; + }, + + /** + * Re-applies precast buffs once an attack sequence has finished. + */ + 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..9a6856ce3 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Necromancer.js @@ -0,0 +1,579 @@ +/** +* @filename Necromancer.js +* @author kolton, theBGuy +* @desc Necromancer attack sequence +* +*/ + +(function (module) { + // Base ids that should never be revived + const noReviveBaseIds = [312, 571]; + // States that make a corpse unusable + const badCorpseStates = [ + sdk.states.FrozenSolid, sdk.states.Revive, sdk.states.Redeemed, + sdk.states.CorpseNoDraw, sdk.states.Shatter, sdk.states.RestInPeace, sdk.states.CorpseNoSelect + ]; + + module.exports = { + novaTick: 0, + maxSkeletons: 0, + maxMages: 0, + maxRevives: 0, + + /** + * Recomputes maxSkeletons/maxMages/maxRevives from Config, resolving "max" to the skill's cap. + * @returns {void} + */ + 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] = [-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) { + let 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; + }, + + /** @returns {void} */ + 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"); + + if (revive + && (unit.isSpecial || noReviveBaseIds.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) + && badCorpseStates.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..ee3b2e215 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Paladin.js @@ -0,0 +1,415 @@ +/** +* @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; + 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; + }, + + /** @returns {void} */ + 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).length > 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..38db23bdf --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Sorceress.js @@ -0,0 +1,271 @@ +/** +* @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; + }, + + /** @returns {void} */ + 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; + 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; + } + + 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 = !me.skillDelay; + } + + // 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..264a906a4 --- /dev/null +++ b/d2bs/kolbot/libs/core/Attacks/Wereform.js @@ -0,0 +1,199 @@ +/** +* @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); + }, + + /** + * Re-applies precast buffs (e.g. rebuffing shapeshift form) after an attack sequence ends. + */ + 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..85f64b529 --- /dev/null +++ b/d2bs/kolbot/libs/core/Auto/AutoBuild.js @@ -0,0 +1,131 @@ +/** +* @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"); + +/** @type {AutoBuildInstance} */ +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; + + /** + * Reapplies build template Update() functions from the last-applied level up 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. + * @returns {void} + */ + 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); + } + } + + /** + * @returns {string} the configured build template name + * @throws {Error} if Config.AutoBuild.Template is falsy + */ + 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; + } + + /** @returns {string} lowercased filename of the currently running script */ + function getCurrentScript () { + return getScript(true).name.toLowerCase(); + } + + /** @returns {string} per-character, per-day log file path under logs/ */ + function getLogFilename () { + let d = new Date(); + let dateString = d.getMonth() + "_" + d.getDate() + "_" + d.getFullYear(); + return ("logs/AutoBuild." + me.realm + "." + me.charname + "." + dateString + ".log"); + } + + /** @returns {string} lowercased include path of the class/template build file */ + function getTemplateFilename () { + let build = getBuildType(); + let template = "config/Builds/" + sdk.player.class.nameOf(me.classid) + "." + build + ".js"; + return template.toLowerCase(); + } + + /** + * Includes the character's build template, wires up the level-up listener (all scripts + * except autobuildthread.js), loads autobuildthread.js from default.dbj if needed, and + * resynchronizes Config with all past AutoBuild updates. + * @throws {Error} if the build template fails to include + */ + 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(); + } + + /** + * scriptmsg listener: reapplies config updates when a "level up" event is received. + * @param {{event?: string}} obj + */ + function levelUpHandler (obj) { + if (typeof obj === "object" && obj.hasOwnProperty("event") && obj.event === "level up") { + applyConfigUpdates(); + } + } + + /** @param {string} message appended (with trailing newline) to the AutoBuild log file */ + function log (message) { FileTools.appendText(getLogFilename(), message + "\n"); } + + // Only print to console from autobuildthread.js, + // but log from all scripts + /** + * Prints and/or logs the given arguments joined with a space and prefixed "AutoBuild:". + */ + 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..c18682af5 --- /dev/null +++ b/d2bs/kolbot/libs/core/Auto/AutoSkill.js @@ -0,0 +1,180 @@ +/** +* @filename AutoSkill.js +* @author Original work by Nad42, edited by IMBA +* @desc Automatically allocate skill points and its pre-requisites if necessary +* +*/ + +/** @type {AutoSkill} */ +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 + ]; + */ + + /** + * @param {number} skillid + * @returns {number | false} The first unmet prerequisite skill id, or false if all are met. + */ + 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; + }; + + /** + * @param {number} skillid + * @param {number} count + * @returns {boolean} + */ + 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; + }; + + /** + * @param {AutoSkillBuildEntry[]} inputArray + * @returns {number | false} Id of the next skill/prerequisite to allocate, or false if none. + */ + 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; + }; + + /** @returns {boolean} True once a point was spent (or false on timeout). */ + 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; + + /** + * @param {AutoSkillBuildEntry[]} skillBuildOrder + * @param {number} [save] + * @returns {boolean} False if `skillBuildOrder` is empty, true once allocation finishes/stalls. + */ + 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..df2a980a3 --- /dev/null +++ b/d2bs/kolbot/libs/core/Auto/AutoStat.js @@ -0,0 +1,787 @@ +/** +* @filename AutoStat.js +* @author IMBA +* @desc Automatically allocate stat points +* +*/ + +/** @type {AutoStat} */ +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"] + ]; + */ + + /** @returns {number} effective block chance percent, casting Holy Shield first if it would raise it */ + 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 + /** + * @param {ItemUnit} unit + * @param {number} type sdk.stats id + * @param {number} stats expected stat value to confirm against the item's description text + * @returns {boolean} + */ + 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; + }; + + /** + * @param {ItemUnit} item + * @returns {boolean} + */ + 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 + /** + * @param {number} type sdk.stats id + * @returns {number} bonus points of `type` granted by equipped set items, or 0 for energy/vitality + */ + 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 + /** + * @param {number} type sdk.stats id + * @returns {number} stat value excluding bonuses from items and set bonuses + */ + 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)); + }; + + /** @returns {number} dexterity points still needed to reach the configured block chance */ + 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; + }; + + /** + * @param {number} type sdk.stats id + * @param {number | false} [goal] target stat point count; falsy spends up to 99 at once + * @returns {boolean} true once the expected stat points were spent within the 3s wait + */ + 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; + }; + + /** + * Spends the next stat point(s) per statBuildOrder. + * @returns {boolean} result of the useStats call for the matched entry, or false if none matched + */ + 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; + + /** + * @param {AutoStatBuildEntry[]} statBuildOrder + * @param {number} [save] stat points to leave unspent + * @param {number} [block] desired block chance for "block" dexterity entries (ignored in classic) + * @param {boolean} [bulkStat] spend multiple points per packet (true) or one at a time (false) + * @returns {boolean} false if statBuildOrder is empty, true once allocation loop finishes/stalls + */ + 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..f5470110a --- /dev/null +++ b/d2bs/kolbot/libs/core/ClassAttack.js @@ -0,0 +1,26 @@ +/** +* @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/ +/** @type {IClassAttack} */ +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..725a7cf75 --- /dev/null +++ b/d2bs/kolbot/libs/core/CollMap.js @@ -0,0 +1,299 @@ +/** +* @filename CollMap.js +* @author kolton +* @desc manipulate map collision data +* +*/ + +/** @type {CollMapInstance} */ +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; + }; + + /** + * Clears the cached room and map collision data. + */ + 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..5aee094c5 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common.js @@ -0,0 +1,27 @@ +/** +* @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/ +/** @type {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..a962c9ca8 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Ancients.js @@ -0,0 +1,174 @@ +/** +* @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; + + /** @returns {boolean} false if any non-parented ancient (Attack.canAttack) is unreachable, true otherwise */ + 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; + }; + + /** + * Activates the Ancients' Altar and waits for the ancients to spawn. + * @returns {boolean} true once the altar is active and Talic has spawned, false if the altar wasn't found + */ + 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; + }; + + /** @returns {boolean} true if all three ancient statues are active */ + 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; + }; + + /** + * Sweeps the summit's corner spots clearing monsters, unless the statues are already all active. + * @returns {boolean|undefined} true if statues were already active (corners skipped); otherwise + * undefined after visiting the corners + */ + 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; + }; + + /** + * Repeatedly clears the ancients (and their surrounding corners) until all three statues are active. + * @param {boolean} [checkQuest] when true, also stop early once the Rite of Passage quest completes + */ + 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++; + } + }; + + /** + * Restocks town supplies and portals to Arreat Summit ahead of an Ancients attempt. + */ + 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(); + }; + + /** + * Touches the altar and re-preps/portals until the ancients are attackable, then kills them. + * @param {boolean} [preTasks] when true, run `ancientsPrep` (rate-limited to once per minute) + * instead of just making a new portal between retries + * @param {boolean} [checkQuest] forwarded to `killAncients` to allow early exit on quest completion + * @throws {Error} if the ancients remain unattackable after 10 retries + */ + 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..439ed7748 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Baal.js @@ -0,0 +1,367 @@ +/** +* @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 }, + }; + + /** + * Moves to the throne corner farthest from any living, non-summoned Hydra and waits for it to die. + * @returns {boolean} always true + */ + 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; + }; + + /** + * Checks for the current throne-room wave's signature monster near the throne. + * @param {boolean} [clear] when true and only trash is present, engage it via Attack.clear + * @returns {number | boolean} 1-5 for the matched wave (fallen/shaman, mummy/mage, council, venom + * lord, Lister), or false when no wave monster is present + */ + 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; + }; + + /** + * Clears remaining throne-room monsters (dolls first if configured), then sweeps preset nodes. + * @returns {boolean} true when ThroneBaal is already gone or the sweep completes + */ + 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; + }; + + /** + * Fires a class-specific ranged/AoE attack at the throne center to pre-damage upcoming spawns. + * @returns {boolean} true if an attack was cast, false if no class-specific action applied + */ + 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; + }; + + /** + * Positions in the throne room and clears each wave in sequence until Lister's wave is done. + * @returns {boolean} false if stuck in the throne for over 30 minutes, true otherwise + */ + 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: + Attack.clearClassids(sdk.monsters.ListerTheTormenter, sdk.monsters.Minion1, sdk.monsters.Minion2); + + 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; + }; + + /** + * Clears the throne room, portals to the Worldstone Chamber, and kills/damages Baal. + * @param {number} [hurtPercent] if > 0, only damage Baal to this HP percent instead of killing him + * @returns {boolean} true once Baal is killed/hurt as requested, false if not yet in the chamber + */ + 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..aa81fcdf1 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Cain.js @@ -0,0 +1,144 @@ +/** +* @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; + }, + + /** + * Drives the full Search for Cain quest: Inifuss scroll and key, activating the Cairn Stones, + * opening the portal to Tristram, and freeing Cain from the gibbet. + * @returns {boolean} + */ + 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..0fad7432e --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Cows.js @@ -0,0 +1,203 @@ +/** +* @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; + + /** + * Builds the list of walkable Moo Moo Farm rooms, excluding the Cow King's room and its + * neighbors, and caches the excluded rooms in `this._badRooms`. + * @returns {[number, number, Room][]} [centerX, centerY, room] tuples for each safe room + */ + 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? + /** + * Walks the safe rooms in nearest-unseen order, clearing monsters in each while avoiding + * paths through the Cow King's room. + * @returns {boolean} false if a room failed to clear + */ + 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 @stylistic/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(); + 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..e8d416ca8 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Diablo.js @@ -0,0 +1,899 @@ +/** +* @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"); + } + }, + + /** + * Registers the game-packet listener that detects Diablo's spawn cue. + */ + addLightsEventListener: function () { + addEventListener("gamepacket", _Diablo.diabloLightsEvent); + }, + + /** + * Unregisters the game-packet listener registered by {@link addLightsEventListener}. + */ + removeLightsEventListener: function () { + removeEventListener("gamepacket", _Diablo.diabloLightsEvent); + }, + + /** + * Builds a poll callback that skips town chores and throws (after invoking onSpawn) once Diablo spawns. + * @param {null | (() => void)} onSpawn + * @returns {() => boolean} Poll function that throws once Diablo spawns; returns false once done. + */ + diaSpawnWatcher: ( + 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; + } + } + }, + + /** + * Attacks any monsters lingering near previously-cleared path nodes, then returns to the start position. + * @returns {boolean} Always true. + */ + 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; + /** @type {Record<1 | 2 | 3 | "vizier" | "seis" | "infector", (openSeal?: boolean) => boolean>} */ + 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 - " + e.message); + } + } + + 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, + /** @returns {boolean} True once de Seis is in range or dead. */ + 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 - " + e.message); + } + } + + 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, + /** @returns {boolean} True once Infector is in range or dead. */ + 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)) { + // e.message is provably identical to this literal (guarded above) - chaining would duplicate + 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; + }, + + /** @returns {boolean} */ + 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; + }, + + /** + * Positions for and waits for Diablo to spawn after the seals are opened. + * @returns {boolean} True once Diablo has spawned. + * @throws {Error} If the portal to Chaos Sanctuary can't be taken, or Diablo doesn't spawn in time. + */ + 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..6df441d7f --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Leecher.js @@ -0,0 +1,53 @@ +/** +* @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 + ], + + /** + * Polled every 3 seconds; throws if the game is empty or the leader has moved on to a new script. + * @returns {boolean} false to signal the caller to kill the tracking process, true to keep polling. + */ + 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..3d1aad985 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Smith.js @@ -0,0 +1,29 @@ +/** +* @filename Smith.js +* @author theBGuy +* @desc Complete smith quest +* +*/ + +(function (module) { + /** + * Kills The Smith, loots and picks up the Horadric Malus, then turns it in to Charsi. + * @returns {boolean} true if the Tools of the Trade quest reached ReqComplete afterward + */ + 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..edef0f565 --- /dev/null +++ b/d2bs/kolbot/libs/core/Common/Tools.js @@ -0,0 +1,377 @@ +/** +* @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; + }, + + /** + * Rebuilds {@link Config.QuitList} from the `name` field of each profile's existing save data. + */ + 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; + }, + + /** + * Stops every script listed in this.stopScripts, blocking until each has fully stopped. + * @returns {boolean} + */ + 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; + }, + + /** + * Logs experience/run duration, stops default scripts, and quits the game. + * @param {boolean} [chickenExit] + */ + 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; + }, + + /** @returns {boolean} true if a revived Tomb Viper owned by another player is present */ + 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; + }, + + /** @returns {Monster|false} this player's own Iron Golem, or false if none found */ + 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; + }, + + /** @returns {string} " " of the nearest preset unit in the current area, or "" if none */ + 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..7974ad7d0 --- /dev/null +++ b/d2bs/kolbot/libs/core/Config.js @@ -0,0 +1,841 @@ +/** +* @filename Config.js +* @author kolton, theBGuy +* @desc config loading and default config values storage +* +*/ + +/** @type {Scripts} */ +const Scripts = {}; + +/** @type {IConfig} */ +let Config = { + /** + * Locates and includes the character's config file (custom/realm/char/profile/class formats, in that + * priority order), falling back to the class default; throws if no config file can be found or loaded. + * @param {boolean} [notify=true] + * @returns {void} + */ + 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." + (e1 && e1.message ? " - " + e1.message : "")); + } + } 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." + (e && e.message ? " - " + e.message : "")); + } + } + + try { + LoadConfig.call(); + Config.Loaded = true; + } catch (e2) { + if (notify) { + console.error(e2); + + // e2 is surfaced via console.error immediately above; the throw is a clean domain message + 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 + /** @param {string} what */ + 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..9bd40e1e3 --- /dev/null +++ b/d2bs/kolbot/libs/core/Cubing.js @@ -0,0 +1,1569 @@ +/** +* @filename Cubing.js +* @author kolton, theBGuy +* @desc transmute Horadric Cube recipes +* +*/ + +/** @type {Roll} */ +const Roll = { + All: 0, + Eth: 1, + NonEth: 2 +}; + +/** + * @type {IRecipe} + * @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, +}); + +/** @type {ICubing} */ +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 + ], + }, + + /** + * Initialize the cubing system - validate configured recipes, then build the recipe/gem/ingredient lists + * @returns {void} + */ + 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; + + continue; + } + } + + 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(); + }, + + /** + * Build the list of perfect gems (Cubing.gemList) that are not already consumed by another recipe's ingredients + * @returns {boolean} Always true + */ + 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] + * + * + * Parse Config.Recipes into normalized recipeObj entries stored on Cubing.recipes + * @returns {void} + * + * @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: [], + + /** + * Scan storage and populate Cubing.validIngredients (items we have) and Cubing.neededIngredients (items to pick up), + * enabling recipes we have the base item for and queueing sub-recipes to upgrade gems + * @returns {void} + */ + 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); + + // 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 + /** + * Remove the auto-generated flawless-gem sub-recipes (those with a MainRecipe property) from Cubing.recipes + * @returns {void} + */ + 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; + } + } + }, + + /** + * Refresh the ingredient/recipe state by clearing sub-recipes and rebuilding the valid/needed ingredient lists + * @returns {void} + */ + 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; + }, + + /** + * Run all currently-completable cube recipes: move ingredients into the cube, transmute, and handle the results. + * Recurses while more recipes remain completable. + * @returns {boolean} True if cubing completed (or nothing to do), false on failure (no cube, stash/cube couldn't open, items couldn't be moved) + */ + 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; + }, + + /** + * If an item is on the cursor, stash/store it in inventory or drop it to clear the cursor + * @returns {boolean} True if the cursor is clear (or was successfully cleared), false if the item could not be placed or dropped + */ + 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; + }, + + /** + * Open the Horadric Cube UI, opening the stash first if the cube is stashed + * @returns {boolean} True if the cube UI is open, false if the cube couldn't be found or opened + */ + 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(); + }, + + /** + * Close the Horadric Cube UI + * @param {boolean} [closeToStash] - If true, click the close button (returns to stash) instead of cancelling + * @returns {boolean} True if the cube UI is closed, false if it couldn't be closed + */ + 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(); + }, + + /** + * Move every item out of the cube into stash or inventory, then close the cube + * @returns {boolean} True if the cube is empty (or nothing to move), false if the cube is missing or items couldn't be moved out + */ + 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; + }, + + /** + * Combine rejuvenation potions into full rejuvenation potions (3 per group) via the cube, restoring the results to their original locations + * @returns {void} + */ + 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(); + }, + + /** + * Determine whether the given Ral/Ort rune should be kept because it's needed to cube-repair equipped items + * @todo Add chipped/flawed gems for recharging a item + * @param {ItemUnit} item - Rune + * @returns {boolean} True if the rune is needed for repairs (and we don't already have enough), false otherwise + */ + 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.repairIngred(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; + }, + + /** + * Cube-repair all equipped items below Config.RepairPercent durability, worst durability first + * @returns {boolean} True if repairs ran (or completed), false if cube repair is disabled or no cube is available + */ + 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..962d04d1c --- /dev/null +++ b/d2bs/kolbot/libs/core/Experience.js @@ -0,0 +1,142 @@ +/* eslint-disable @stylistic/max-len */ +/** +* @filename Experience.js +* @author kolton +* @desc Experience library +* +*/ + +/** @type {Experience} */ +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..a374785f7 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/AreaData.js @@ -0,0 +1,341 @@ +/** +* @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..6642da4cc --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/GameData.js @@ -0,0 +1,227 @@ +/* eslint-disable @stylistic/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], + /** + * Effective level of a monster. On nightmare/hell the value comes from the + * area level; on normal it comes from the monster's own data. + * @param {number} monsterID - monster classid (index into MonsterData) + * @param {number} areaID - area id (index into AreaData) + * @returns {number} the monster's effective level + */ + 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; + }, + /** + * Base experience awarded for killing a monster, before party/level modifiers. + * @param {number} monsterID - monster classid (index into MonsterData) + * @param {number} areaID - area id (index into AreaData) + * @returns {number} raw monster experience adjusted by its experience modifier + */ + monsterExp: function (monsterID, areaID) { + return Experience.monsterExp[this.monsterLevel(monsterID, areaID)][me.diff] * MonsterData[monsterID].ExperienceModifier / 100; + }, + /** + * Representative level of an area. On nightmare/hell this is the fixed area + * level; on normal it is the rarity-weighted average level of its monsters. + * @param {number} areaID - area id (index into AreaData) + * @returns {number} the area's level (rounded on normal difficulty) + */ + 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); + }, + /** + * Damage types the area's monsters (and their minions) are immune to. + * @param {number} areaID - area id (index into AreaData) + * @returns {string[]} names of resistances at or above 100 (e.g. "Fire", "Cold") + */ + 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); + }, + /** + * Experience multiplier from the character-level vs monster-level gap, + * including the high-level (69+) experience penalty. + * @param {number} clvl - character level + * @param {number} mlvl - monster level + * @returns {number} experience scaling factor (fraction of base exp) + */ + 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; + }, + /** + * Experience multiplier from the number of players in the game. + * @param {number} [count] - player count; when falsy it is derived from the current party + * @returns {number} multiplayer experience scaling factor ((count + 1) / 2) + */ + multiplayerModifier: function (count) { + if (!count) { + let party = getParty(me); + + if (!party) { + return 1; + } + + count = 1; + + while (party.getNext()) { + count++; + } + } + + return (count + 1) / 2; + }, + /** + * A player's share of party experience: their level over the party's total level. + * @param {string|number} playerID - player name or gid to look up + * @returns {number} the player's fraction of total party levels (1 if not in a party) + */ + partyModifier: function (playerID) { + let party = getParty(me), partyid, 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; + }, + /** + * Actual experience a specific player receives for a kill, after applying + * level, multiplayer, and party-share modifiers. + * @param {string|number} playerID - player name or gid receiving the exp + * @param {number} monsterID - monster classid (index into MonsterData) + * @param {number} areaID - area id (index into AreaData) + * @returns {number} experience granted to the player (0 if not in a party) + */ + killExp: function (playerID, monsterID, areaID) { + let exp = this.monsterExp(monsterID, areaID), party = getParty(me), partyid, 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); + }, + /** + * Average total party experience gained per kill in an area, used to compare + * leeching/leveling spots. + * @param {number} areaID - area id (index into AreaData) + * @param {string|number|null} [exclude] - player name/gid to exclude from the party entirely + * @param {boolean} [onlytown] - only count party members currently in a town area + * @param {string|number|null} [ignore] - player name/gid whose own exp is not added to the pool + * @returns {number} average total party experience per kill (0 if not in a party) + */ + areaPartyExp: function (areaID, exclude = null, onlytown = true, ignore = null) { // amount of total party exp gained per kill on average + let party = getParty(me), partyid, 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..506db909f --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/MonsterData.js @@ -0,0 +1,151 @@ +/** +* @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 : 0), + 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]), + }); + } + + /** + * Find a monster's data entry by (partial) name. + * @param {string} whatToFind - monster name to match against locale and internal names + * @returns {MonsterObj | undefined} best matching entry, or undefined for invalid input + */ + MonsterData.findByName = function (whatToFind) { + if (!whatToFind || typeof whatToFind !== "string") { + return undefined; + } + + const searchTerm = whatToFind.toLowerCase().trim(); + + // First attempt: exact or contains matches, preferring shorter (more specific) names + const directMatches = MonsterData.filter(function (mon) { + if (!mon || !mon.LocaleString) return false; + const localeLower = mon.LocaleString.toLowerCase(); + const internalLower = mon.InternalName ? mon.InternalName.toLowerCase() : ""; + return localeLower === searchTerm || internalLower === searchTerm + || localeLower.includes(searchTerm) || internalLower.includes(searchTerm); + }); + + if (directMatches.length > 0) { + directMatches.sort(function (a, b) { + return a.LocaleString.length - b.LocaleString.length; + }); + return directMatches.first(); + } + + // Fallback: fuzzy match over the full table + 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..d57a58e08 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/NTItemAlias.js @@ -0,0 +1,2184 @@ +/* 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; + +/** @type {Record} */ +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..d6ae73f87 --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/QuestData.js @@ -0,0 +1,180 @@ +/** +* @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..859a29aff --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/ShrineData.js @@ -0,0 +1,80 @@ +/** +* @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) { + if (!_shrines.has(shrineType)) return 0; + return _shrines.get(shrineType).duration || 0; + }, + + /** @param {number} shrineType */ + getRegenTime: function (shrineType) { + if (!_shrines.has(shrineType)) return Infinity; + 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..35b492c3d --- /dev/null +++ b/d2bs/kolbot/libs/core/GameData/SkillData.js @@ -0,0 +1,1583 @@ +/** +* @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) { + 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) { + let decoyCost = Math.max(19.75 - (0.75 * skillLvl), 1); + // don't memoize before the skill is learned - the level-0 value would poison the cache + return skillLvl > 0 ? (this._manaCost = decoyCost) : decoyCost; + } + 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 skillLvl > 0 ? (this._manaCost = ret) : ret; + }; + + /** + * Get the effective range of the skill, optionally using the pvp range variant + * @this Skill + * @param {boolean} [pvpRange] - whether to use the pvp range for skills that define one + * @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; + }; + + /** + * Reset the cached mana cost, damage, and skill-point state so they are recomputed on next access + * @this Skill + * @returns {void} + */ + 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; + }, + + /** + * @param {number} bodyLoc + * @returns {{ classid: number, tier: number }} classid/tier of the item in that slot, + * or `{ classid: -1, tier: -1 }` if nothing is equipped there + */ + 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; + }, + + /** + * Equips any higher-tier storage items over what's currently worn. + * @returns {boolean} true if the item should be kept+logged, false if not + */ + autoEquip: function () { + if (!Config.AutoEquip) return true; + + /** + * Comparator: equippable items sort before non-equippable ones. + * @param {ItemUnit} a + * @param {ItemUnit} b + * @returns {number} + */ + 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..4866ea951 --- /dev/null +++ b/d2bs/kolbot/libs/core/Loader.js @@ -0,0 +1,482 @@ +/** +* @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; +} + +/** @type {Loader} */ +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(), + + /** + * Populates fileList from libs/scripts/ and runs loadScripts(). + */ + init: function () { + this.getScripts(); + this.loadScripts(); + }, + + /** + * Populates fileList with the basenames (no extension) of every .js file in libs/scripts/. + */ + 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."); + }, + + /** + * @param {object} from + * @param {object} to - each own property of `from` is deep-cloned onto this object + */ + copy: function (from, to) { + for (let i in from) { + if (from.hasOwnProperty(i)) { + to[i] = this.clone(from[i]); + } + } + }, + + /** + * Runs every enabled script in Scripts, in order, restoring Config between reconfigured scripts. + */ + 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(); + + /** @returns {string} log line prefix listing the main script and any nested tempList scripts */ + 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..c5bc7254e --- /dev/null +++ b/d2bs/kolbot/libs/core/Me.js @@ -0,0 +1,1573 @@ +/** +* @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; +}; + +/** + * Find all items matching classid, mode, and (optionally) location. + * @param {number} [id] + * @param {number} [mode] + * @param {number | boolean} [loc] + * @returns {ItemUnit[]} + */ +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; +}; + +/** + * Waits for the game to be ready, then repeatedly cancels any open UI flag (inventory, shop, etc.). + * @returns {void} + */ +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; +}; + +/** + * Calculates the number of frames needed to cast a given skill at a given FCR for a given char. + * @param {number} skillId + * @param {number} [fcr] + * @param {number} [charClass] + * @returns {number} + */ +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) ? "wolf" : me.getState(sdk.states.Bear) ? "bear" : "human") + : "normal"]; + return Math.ceil( + 256 * baseCastRate / Math.floor(animationSpeed * (100 + effectiveFCR) / 100) - (isLightning ? 0 : 1) + ); +}; + +/** + * Calculates the duration in seconds needed to cast a given skill at a given FCR for a given char. + * @param {number} skillId + * @param {number} [fcr] + * @param {number} [charClass] + * @returns {number} + */ +me.castingDuration = function (skillId, fcr = me.FCR, charClass = me.classid) { + return (me.castingFrames(skillId, fcr, charClass) / 25); +}; + +/** + * @param {number} [weaponLoc] + * @returns {number} quantity stat of the item equipped at weaponLoc, or 0 if none is equipped + */ +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; +}; + +/** + * Removes belt potions that don't belong in their configured column, plus any stamina/antidote/thawing + * potions, moving them to storage or drinking them if there's no room. + * @returns {boolean} + */ +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; +}; + +/** + * Moves matching potions from inventory into empty belt columns to top up the belt. + * @param {number} [beltSize] + * @returns {boolean} + */ +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; +}; + +/** + * Checks whether any configured belt column (hp/mp) is missing its potion type. + * @returns {boolean} + */ +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; +}; + +/** + * Same column-coverage check as needPotions, but first tops up the belt from inventory potions + * (via cleanUpInvoPotions) before checking. + * @returns {boolean} + */ +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; +}; + +/** + * Checks whether total inventory HP/MP potion counts are below the configured buffer thresholds. + * @returns {boolean} + */ +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; +}; + +/** @returns {string[]} repair actions needed, any of "buyQuiver" (out of/low on ammo) or "repair" */ +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; +}; + +/** @returns {ItemUnit[]} unidentified items in inventory */ +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); +}; + +/** + * @returns {number} quantity of Key items owned in inventory; returns the sentinel 12 (always "enough") + * when key buying doesn't apply - chests disabled, assassin, can't afford a key, or no inventory space + */ +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; +}; + +/** + * Identifies unidentified items in the field using an ID tome, then drops or keeps each per the + * pickit result. + * @returns {boolean} false if there's nothing to identify or the ID tome lacks enough charges + */ +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; +}; + +/** @returns {boolean} */ +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: { + /** @returns {number} */ + get: function () { + return Math.floor((4 * (1 / me.hpmax * me.hp)) + 1); + }, + configurable: true + }, + maxgold: { + /** + * max capacity (cLvl * 10000) + * @returns {number} + */ + get: function () { + return me.getStat(sdk.stats.Level) * 10000; + }, + }, + inShop: { + /** @returns {boolean} */ + get: function () { + if (getUIFlag(sdk.uiflags.Shop)) return true; + if (!Config.PacketShopping) return false; + let npc = getInteractedNPC(); + return !!(npc && npc.itemcount > 0); + } + }, + walking: { + /** @returns {boolean} */ + get: function () { + return me.runwalk === sdk.player.move.Walk; + } + }, + running: { + /** @returns {boolean} */ + get: function () { + return me.runwalk === sdk.player.move.Run; + } + }, + deadOrInSequence: { + /** @returns {boolean} */ + get: function () { + return me.dead || me.mode === sdk.player.mode.SkillActionSequence; + } + }, + moving: { + /** @returns {boolean} */ + get: function () { + return [sdk.player.mode.Walking, sdk.player.mode.Running, sdk.player.mode.WalkingInTown].includes(me.mode); + } + }, + staminaPercent: { + /** @returns {number} */ + get: function () { + return Math.round((me.stamina / me.staminamax) * 100); + } + }, + staminaDrainPerSec: { + /** @returns {number} */ + 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: { + /** @returns {number} */ + get: function () { + return me.stamina / me.staminaDrainPerSec; + } + }, + staminaMaxDuration: { + /** @returns {number} */ + get: function () { + return me.staminamax / me.staminaDrainPerSec; + } + }, + FCR: { + /** @returns {number} */ + get: function () { + return me.getStat(sdk.stats.FCR) - (!!Config ? Config.FCR : 0); + } + }, + FHR: { + /** @returns {number} */ + get: function () { + return me.getStat(sdk.stats.FHR) - (!!Config ? Config.FHR : 0); + } + }, + FBR: { + /** @returns {number} */ + get: function () { + return me.getStat(sdk.stats.FBR) - (!!Config ? Config.FBR : 0); + } + }, + IAS: { + /** @returns {number} */ + get: function () { + return me.getStat(sdk.stats.IAS) - (!!Config ? Config.IAS : 0); + } + }, + shapeshifted: { + /** @returns {boolean} */ + get: function () { + return me.getState(sdk.states.Wolf) || me.getState(sdk.states.Bear) || me.getState(sdk.states.Delerium); + } + }, + mpPercent: { + /** @returns {number} */ + get: function () { + return Math.round(me.mp * 100 / me.mpmax); + } + }, + skillDelay: { + /** @returns {boolean} */ + get: function () { + return me.getState(sdk.states.SkillDelay); + } + }, + _shitList: { + value: new Set(), + writable: true, + enumerable: true, + configurable: true + }, + shitList: { + /** @returns {Set} */ + 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: { + /** @returns {boolean} */ + get: function () { + return me.gametype === sdk.game.gametype.Classic; + } + }, + expansion: { + /** @returns {boolean} */ + get: function () { + return me.gametype === sdk.game.gametype.Expansion; + } + }, + softcore: { + /** @returns {boolean} */ + get: function () { + return me.playertype === false; + } + }, + hardcore: { + /** @returns {boolean} */ + get: function () { + return me.playertype === true; + } + }, + diedHardcore: { + /** + * Hardcore character has permanently died. Also readable out of game + * (char select/lobby), where the dead char greys out join/create. + * @returns {boolean} + */ + get: function () { + return me.hardcore && !!(me.charflags & sdk.game.charflags.Dead); + } + }, + normal: { + /** @returns {boolean} */ + get: function () { + return me.diff === sdk.difficulty.Normal; + } + }, + nightmare: { + /** @returns {boolean} */ + get: function () { + return me.diff === sdk.difficulty.Nightmare; + } + }, + hell: { + /** @returns {boolean} */ + get: function () { + return me.diff === sdk.difficulty.Hell; + } + }, + amazon: { + /** @returns {boolean} */ + get: function () { + return me.classid === sdk.player.class.Amazon; + } + }, + sorceress: { + /** @returns {boolean} */ + get: function () { + return me.classid === sdk.player.class.Sorceress; + } + }, + necromancer: { + /** @returns {boolean} */ + get: function () { + return me.classid === sdk.player.class.Necromancer; + } + }, + paladin: { + /** @returns {boolean} */ + get: function () { + return me.classid === sdk.player.class.Paladin; + } + }, + barbarian: { + /** @returns {boolean} */ + get: function () { + return me.classid === sdk.player.class.Barbarian; + } + }, + druid: { + /** @returns {boolean} */ + get: function () { + return me.classid === sdk.player.class.Druid; + } + }, + assassin: { + /** @returns {boolean} */ + get: function () { + return me.classid === sdk.player.class.Assassin; + } + }, +}); + +/** + * Quest items + */ +Object.defineProperties(me, { + wirtsleg: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.WirtsLeg); + } + }, + cube: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.Cube); + } + }, + shaft: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.ShaftoftheHoradricStaff); + } + }, + amulet: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.ViperAmulet); + } + }, + staff: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.HoradricStaff); + } + }, + completestaff: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.HoradricStaff); + } + }, + eye: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.items.quest.KhalimsEye); + } + }, + brain: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.KhalimsBrain); + } + }, + heart: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.KhalimsHeart); + } + }, + khalimswill: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.KhalimsWill); + } + }, + khalimsflail: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.KhalimsFlail); + } + }, + malahspotion: { + /** @returns {ItemUnit | false} */ + get: function () { + return me.getItem(sdk.quest.item.MalahsPotion); + } + }, + scrollofresistance: { + /** @returns {ItemUnit | false} */ + 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", { + /** @returns {boolean[]} */ + 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); + }; + + /** + * @param {number} questId + * @param {number} state + * @returns {boolean} + */ + me.checkQuest = function (questId, state) { + const quest = QuestData.get(questId); + if (!quest) return false; + return quest.checkState(state); + }; + + Object.defineProperties(me, { + highestAct: { + /** @returns {number} highest act reached (1-5) */ + 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: { + /** @returns {number | undefined} highest completed quest id, walking backwards from Respec */ + 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: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.DenofEvil).complete(); + } + }, + bloodraven: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.SistersBurialGrounds).complete(); + } + }, + smith: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.ToolsoftheTrade).complete(); + } + }, + imbue: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.ToolsoftheTrade).checkState(sdk.quest.states.ReqComplete, true); + } + }, + cain: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.TheSearchForCain).complete(); + } + }, + tristram: { + /** @returns {boolean} */ + 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: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.ForgottenTower).complete(); + } + }, + andariel: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.AbleToGotoActII).complete(); + } + }, + radament: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.RadamentsLair).complete(); + } + }, + horadricstaff: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.TheHoradricStaff).complete(); + } + }, + summoner: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.TheSummoner).complete(); + } + }, + duriel: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.AbleToGotoActIII).complete(); + } + }, + goldenbird: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.TheGoldenBird).complete(); + } + }, + lamessen: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.LamEsensTome).complete(); + } + }, + gidbinn: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.BladeoftheOldReligion).complete(); + } + }, + travincal: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.KhalimsWill).complete(); + } + }, + blackendTemple: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.TheBlackenedTemple).complete(); + } + }, + mephisto: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.AbleToGotoActIV).complete(); + } + }, + izual: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.TheFallenAngel).complete(); + } + }, + hellforge: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.HellsForge).complete(); + } + }, + diablo: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.TerrorsEnd).complete(); + } + }, + shenk: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.SiegeOnHarrogath).complete(true); + } + }, + larzuk: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.SiegeOnHarrogath).checkState(sdk.quest.states.ReqComplete, true); + } + }, + savebarby: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.RescueonMountArreat).complete(); + } + }, + barbrescue: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.RescueonMountArreat).complete(); + } + }, + anya: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.PrisonofIce).complete(); + } + }, + ancients: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.RiteofPassage).complete(); + } + }, + baal: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.EyeofDestruction).complete(); + } + }, + // Misc + cows: { + /** @returns {number} quest state value for the cow-portal quest state */ + get: function () { + return me.getQuest(sdk.quest.id.TheSearchForCain, 10); + } + }, + respec: { + /** @returns {boolean} */ + get: function () { + return QuestData.get(sdk.quest.id.Respec).complete(); + } + }, + diffCompleted: { + /** @returns {boolean} */ + 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..ca1af7c76 --- /dev/null +++ b/d2bs/kolbot/libs/core/Misc.js @@ -0,0 +1,1237 @@ +/** +* @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 +* +*/ + +/** @type {Misc} */ +const Misc = (function () { + const ShrineData = require("./GameData/ShrineData"); + const chestsTypes = [ + "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" + ]; + + 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; + + const useAllTypes = Config.OpenChests.Types.some(function (el) { + return el.toLowerCase() === "all"; + }); + const containers = new Set( + useAllTypes ? + chestsTypes + : Config.OpenChests.Types.map(function (el) { + return el.toLowerCase(); + }) + ); + + /** @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.has(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); + }; + + /** @returns {number} milliseconds left before the tracked shrine's state effect expires */ + this.remaining = function () { + return this.duration - (getTickCount() - this.tick); + }; + + /** @returns {boolean} true if the character currently has the tracked shrine's state active */ + this.isMyCurrentState = function () { + if (this.state <= 0) return false; + return me.getState(this.state); + }; + }, + + /** @type {Set} */ + _shrinerIgnore: new Set(), + + /** + * TODO: Handle stateful shrines + * TODO: Track last shrine used - should tier based on shrine type + * @param {ObjectUnit} shrine + * @returns {boolean} + */ + _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: + if (Config.DebugMode.Shrines) { + console.debug( + "Skill shrine. Dist: " + walkDistance + + " Last shrine state: " + Misc.lastShrine.state + + " isMyCurrentState: " + Misc.lastShrine.isMyCurrentState() + ); + } + 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]) { + // TODO: weigh the improvement — currently takes any better-resist shrine; + // ideally only take it if the resist gap is meaningful (>=5%) or the detour is short + + 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; + }, + + /** @returns {boolean} true if HP/MP/stamina are low or (when configured) an ailment state is active */ + _wantWell: function () { + if (me.hpPercent < (Config.AutoShriner ? 75 : Config.UseWells.HpPercent)) return true; + if (me.mpPercent < (Config.AutoShriner ? 75 : Config.UseWells.MpPercent)) return true; + if (me.staminaPercent < (Config.AutoShriner ? 50 : Config.UseWells.StaminaPercent)) return true; + return (Config.AutoShriner || Config.UseWells.StatusEffects) && [ + sdk.states.Frozen, + sdk.states.Poison, + sdk.states.AmplifyDamage, + sdk.states.Decrepify + ].some(function (state) { + return me.getState(state); + }); + }, + + /** + * @param {number[]} ignore + * @param {number} range + * @returns {boolean} + */ + shriner: function (ignore = [], range = 50) { + if (!Config.AutoShriner) return false; + + let shrineList = []; + let shrine = Game.getObject(); + + 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) ? Misc._wantShrine(shrine) : Misc._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]); + } + } + + /** + * 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") + && Misc._wantWell() + ) + ) && (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, + /** @returns {boolean} true once positioned at the expected shrine coordinates */ + 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..d25fb1473 --- /dev/null +++ b/d2bs/kolbot/libs/core/NPC.js @@ -0,0 +1,68 @@ +/** +* @filename NPC.js +* @author kolton, theBGuy +* @desc Handle NPC object +* +*/ + +/** @type {NPCList} */ +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..59a3df826 --- /dev/null +++ b/d2bs/kolbot/libs/core/NTItemParser.js @@ -0,0 +1,835 @@ +/* eslint-disable @stylistic/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 = []; + +/** + * Parses a single pickit line and registers it in the checklists if valid. + * @param {string} itemString - one pickit line, in NTIP syntax. + * @param {string} [filename="kolbot"] - source label recorded for error messages. + * @returns {boolean} always true, regardless of whether the line parsed successfully. + */ +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; +}; + +/** + * Reads and parses a .nip file, registering every valid line in the checklists. + * @param {string} filepath + * @param {boolean} [notify] - log load status / errors to console when true. + * @returns {boolean} false if the file doesn't exist or couldn't be opened, true otherwise. + */ +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 ((fileError instanceof ScriptError)) { + throw fileError; + } + if (notify) { + Misc.errorReport("ÿc1Failed to load NIP: ÿc0" + filename); + } + } + + if (!nipfile) { + return false; + } + + let lines = nipfile.readAllLines(); + nipfile.close(); + + for (let i = 0; i < lines.length; i++) { + const info = { + line: i + 1, + file: filename, + string: lines[i] + }; + + let line = NTIP.ParseLineInt(lines[i], info); + + if (line) { + entries++; + + NTIP_CheckList.push(line); + + if (!lines[i].toLowerCase().match("tier")) { + NTIP_CheckListNoTier.push(line); + } + + stringArray.push(info); + } + } + + if (notify) { + console.log( + "ÿc4Loaded NIP: ÿc2" + filename + "ÿc4. Lines: ÿc2" + lines.length + + "ÿc4. Valid entries: ÿc2" + entries + + ". ÿc4Time: ÿc2" + (getTickCount() - tick) + " ms" + ); + } + + return true; +}; + +/** + * Counts stash/inventory items matching the given filters. + * @param {?((item: ItemUnit) => boolean)} item_type - filter on item type/class, or null to match any. + * @param {?((item: ItemUnit) => boolean)} item_stats - filter on item stats, or null to match any. + * @returns {number} matching item count; 0 if items couldn't be enumerated. + */ +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; +}; + +/** + * Empties the compiled checklists and the raw line-string cache. + * @returns {void} + */ +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 ((pickError instanceof ScriptError)) { + throw pickError; + } + showConsole(); + + if (!entryList) { + Misc.errorReport("ÿc1Pickit error! Line # ÿc2" + stringArray[i].line + " ÿc1Entry: ÿc0" + stringArray[i].string + " (" + stringArray[i].file + ") Error message: " + pickError.message + " Trigger item: " + item.fname.split("\n").reverse().join(" ")); + + NTIP_CheckList.splice(i, 1); // Remove the element from the list + } else { + Misc.errorReport("ÿc1Pickit error in runeword config!"); + } + + result = 0; + } + } + + if (verbose) { + switch (result) { + case -1: + break; + case 1: + rval.line = stringArray[i].file + " #" + stringArray[i].line; + + break; + default: + rval.line = null; + + break; + } + + rval.result = result; + + return rval; + } + + return result; +}; + +/** + * @param {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 ((pickError instanceof ScriptError)) { + throw 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; + } + } + + 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], +]); + +/** + * Compiles one raw pickit line into a [propertyMatcher, statMatcher, options] tuple of runtime functions. + * @param {string} input - raw pickit line text (property expr # stat expr). + * @param {{line: number, file: string, string: string}} info - source metadata used in thrown error messages. + * @returns {[((item: ItemUnit) => boolean)|undefined, ((item: ItemUnit) => number)|undefined, Object]|false|null} + * the compiled tuple, or false/null if the line failed to parse. + */ +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..404b4e24d --- /dev/null +++ b/d2bs/kolbot/libs/core/Packet.js @@ -0,0 +1,645 @@ +/** +* @filename Packet.js +* @author kolton, theBGuy +* @desc handle packet based functions +* +*/ + +/** @type {Packet} */ +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; + }, + + /** + * @param {number} hand - sdk.skills.hand.Left or sdk.skills.hand.Right + * @param {number} wX - world x coordinate + * @param {number} wY - world y coordinate + */ + 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(); + }, + + /** + * Casts a skill and continually resends the held-skill packet until duration elapses. + * @param {number} hand - sdk.skills.hand.Left or sdk.skills.hand.Right + * @param {number} wX - world x coordinate + * @param {number} wY - world y coordinate + * @param {number} [duration] - milliseconds to keep the skill held + */ + 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; + }, + + /** @returns {void} */ + 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 + /** + * Registers a "gamepacket" listener that only invokes `callback` for the given packet type(s). + * @param {number|number[]} packetType + * @param {(bytes: ArrayBufferLike) => boolean} callback + * @returns {((bytes: ArrayBufferLike) => boolean)|null} The registered callback, or null if packetType was invalid. + */ + 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; + }, + + /** + * @param {(bytes: ArrayBufferLike) => boolean} callback + * @returns {void} + */ + 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..09e179086 --- /dev/null +++ b/d2bs/kolbot/libs/core/Pather.js @@ -0,0 +1,2457 @@ +/** +* @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])); +}; + +/** + * Sum of segment distances along the walk path from me to this node. + * @returns {number} walk distance, 0 if already at the node, Infinity if no path exists + */ +PathNode.prototype.getWalkDistance = function () { + const path = getPath(me.area, me.x, me.y, this.x, this.y, 0, Pather.walkDistance); + if (!path) return Infinity; + return path + .map(function (e, i, s) { + return i && getDistance(s[i - 1], e) || 0; + }) + .reduce(function (acc, cur) { + return acc + cur; + }, 0); +}; + +/** @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 + */ +/** @type {NodeAction} */ +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); + } + } +}; + +/** @type {PathDebug} */ +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)); + }, + + /** + * @returns {boolean} true when teleport is usable, not restricted by Config.NoTele, and there's enough + * mana for more than 2 casts. + */ + 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) { + useTeleport && Config.TeleSwitch && me.switchWeapons(primarySlot); + PathDebug.removeHooks(PATH_DEBUG_ID); + 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) { + useTeleport && Config.TeleSwitch && me.switchWeapons(primarySlot); + PathDebug.removeHooks(PATH_DEBUG_ID); + 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; + }, + + kickBarrels: (function () { + // Small breakable objects kickBarrels clears out of the way - anything small and annoying really + const barrelNames = [ + "ratnest", "goo pile", "barrel", "basket", + "largeurn", "jar3", "jar2", "jar1", + "urn", "jug", "barrel wilderness", "cocoon" + ]; + /** + * 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 + */ + return function (x, y) { + if (me.inTown) return false; + + (typeof x !== "number" || typeof y !== "number") && ({ x, y } = me); + + let barrels = getUnits(sdk.unittype.Object) + .filter(function (el) { + return (el.name && el.mode === sdk.objects.mode.Inactive + && barrelNames.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, xMin, xMax, me.y, yMin, yMax, 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); + const path = getPath(area, x, y, xx, yy, reductionType, radius); + if (!path) return Infinity; + // distance between node x and x-1 + return path + .map(function (e, i, s) { + return i && getDistance(s[i - 1], e) || 0; + }) + .reduce(function (acc, cur) { + return acc + cur; + }, 0); + }, +}; + +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 {typeof globalThis} global + * @param {(id: number) => boolean} original + */ +(function (global, original) { + global._getWaypoint = original; + + /** + * Overrides the engine's getWaypoint to cache results in me.waypoints and broadcast cache updates, + * avoiding redundant native lookups. + * @param {number} id + * @param {boolean} [noCache=false] - bypass the cache and call the original engine function directly. + * @returns {boolean} + */ + global.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..cd7b4b8ad --- /dev/null +++ b/d2bs/kolbot/libs/core/Pickit.js @@ -0,0 +1,840 @@ +/** +* @filename Pickit.js +* @author kolton, theBGuy +* @desc handle item pickup +* +*/ + +/** @type {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)); + }); + } + }, + + /** + * itemlog event handler: tracks newly-seen ground item gids for later pickup. + * @param {number} [gid] + * @param {number} [mode] + * @param {number} [code] + * @param {number} [global] + * @returns {void} + */ + // 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); + }, + + /** @returns {boolean} true if all 4 belt columns have a potion in their first row */ + 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, + }, + + pickItem: (function () { + // UI windows that block picking an item up + const cancelFlags = [ + sdk.uiflags.Inventory, sdk.uiflags.NPCMenu, + sdk.uiflags.Waypoint, sdk.uiflags.Shop, + sdk.uiflags.Stash, sdk.uiflags.Cube + ]; + /** + * @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 + */ + return 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", { + /** + * @this {ItemStats} + * @returns {boolean} true if within telekinesis range (5-20) and not blocked by wall/ranged collision + */ + 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; + + 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..a70302084 --- /dev/null +++ b/d2bs/kolbot/libs/core/Precast.js @@ -0,0 +1,667 @@ +/** +* @filename Precast.js +* @author noah-, kolton, theBGuy +* @desc handle player prebuff sequence +* +*/ + +/** @type {Precast} */ +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; + } + /** @returns {boolean} true if this skill can currently be used */ + PrecastSkill.prototype.canUse = function () { + return Skill.canUse(this.skillId); + }; + /** @returns {number} percent of buff duration remaining, clamped to 0-100 */ + PrecastSkill.prototype.remaining = function () { + if (!this.duration) { + this.duration = Skill.getDuration(this.skillId); + // stateful buffs with no timed duration never "run out" - the state check governs recasts + if (!this.duration) return 100; + } + const pRemaining = 100 * (1 - (getTickCount() - this.lastCast) / this.duration); + return Math.max(0, Math.min(100, pRemaining)); + }; + /** + * @param {number} [percent] threshold percent remaining below which a recast is needed + * @returns {boolean} + */ + PrecastSkill.prototype.needSoon = function (percent = 25) { + return this.remaining() < percent; + }; + /** + * @param {boolean} [force] treat as needing cast regardless of state/duration + * @param {number} [percent] threshold percent remaining passed to {@link needSoon} + * @returns {boolean} + */ + PrecastSkill.prototype.needToCast = function (force = false, percent = 25) { + if (!this.canUse()) return false; + return force || !me.getState(this.state) || this.needSoon(percent); + }; + /** + * Records the current tick as the last cast time. + */ + 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; + + /** @returns {number} percent of max Bone/Cyclone Armor absorb currently remaining */ + PrecastArmorSkill.prototype.remaining = function () { + return this.max > 0 + ? Math.round(me.getStat(sdk.stats.SkillBoneArmor) * 100 / this.max) + : 0; + }; + /** + * Records the current tick and current armor max as the cast baseline. + */ + 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, + ]), + + /** @returns {boolean} true once haveCTA has been resolved (cached after the first successful check) */ + 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]; + }, + + /** + * @param {number} skillId + * @param {number|Unit} [x] x coordinate, or a Unit to target + * @param {number} [y] y coordinate (ignored when x is a Unit) + * @param {boolean} [allowSwitch] allow switching to the weapon slot with the better skill roll + * @returns {boolean} false on failure, otherwise the skill's state (or true if stateless) + */ + 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); + } + }, + + /** + * Repeatedly casts a summon skill at random nearby spots until the minion count cap is reached. + * @param {number} skillId + * @param {number} minionType + * @returns {boolean} + */ + 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(); + } + + /** @returns {boolean} true if this unit's Enchant is due for a recast (10s before it expires) */ + ChantTracker.prototype.reChant = function () { + return getTickCount() - this.lastChant >= chantDuration - Time.seconds(10); + }; + + /** + * Records the current tick as the last chant time. + */ + ChantTracker.prototype.update = function () { + this.lastChant = getTickCount(); + }; + + /** @type {Map} */ + const chantList = new Map(); + + return function () { + chantDuration = Skill.getDuration(sdk.skills.Enchant); + const slot = me.weaponswitch; + const chanted = []; + + me.switchWeapons(Precast.getBetterSlot(sdk.skills.Enchant)); + + // Player + let unit = Game.getPlayer(); + + if (unit) { + do { + if (unit.dead) continue; + if (!Misc.inMyParty(unit.name) || unit.distance > 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 = false, forceBo; + + // 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; + }, + + /** @returns {boolean} true if any out-of-town-only precast skill (warcries or CTA) is available */ + needOutOfTownCast: function () { + return Skill.canUse(sdk.skills.Shout) || Skill.canUse(sdk.skills.BattleOrders) || Precast.checkCTA(); + }, + + /** + * @param {boolean} [force] force re-cast of all precast skills + * @param {number} [goToWhenDone] area id to return to when done; defaults to the current area + * @returns {boolean} true if we ended up back at the target area + */ + 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..ae28d4d34 --- /dev/null +++ b/d2bs/kolbot/libs/core/Prototypes.js @@ -0,0 +1,2858 @@ +/** +* @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; + /** + * Overrides the native getUnit, nulling out the phantom act 1/2 "Dummy" monster references + * a native engine bug returns when scanning by classid/name. + * @param {...(number|string)} args - forwarded verbatim to the native getUnit + * @returns {Unit|null} + */ + 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: { + /** @returns {boolean} */ + get: function () { + return (this.spectype & sdk.monsters.spectype.Champion) > 0; + }, + }, + isUnique: { + /** @returns {boolean} */ + get: function () { + return (this.spectype & sdk.monsters.spectype.Unique) > 0; + }, + }, + isMinion: { + /** @returns {boolean} */ + get: function () { + return (this.spectype & sdk.monsters.spectype.Minion) > 0; + }, + }, + isSuperUnique: { + /** @returns {boolean} */ + get: function () { + return (this.spectype & (sdk.monsters.spectype.Super | sdk.monsters.spectype.Unique)) > 0; + }, + }, + isSpecial: { + /** @returns {boolean} */ + get: function () { + return (this.isChampion || this.isUnique || this.isSuperUnique); + }, + }, + isPlayer: { + /** @returns {boolean} */ + get: function () { + return this.type === sdk.unittype.Player; + }, + }, + isMonster: { + /** @returns {boolean} */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + return [ + sdk.monsters.Tentacle1, sdk.monsters.Tentacle2, + sdk.monsters.Tentacle3, sdk.monsters.Tentacle4, sdk.monsters.Tentacle5 + ].includes(this.classid); + }, + }, + isShaman: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + return getBaseStat("monstats", this.classid, "MonType") === sdk.monsters.type.Unraveler; + }, + }, + isFallen: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** @returns {boolean} */ + get: function () { + return this.getState(sdk.states.FrozenSolid); + }, + }, + isChilled: { + /** @returns {boolean} */ + get: function () { + return this.getState(sdk.states.Frozen); + }, + }, + extraStrong: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ExtraStrong); + }, + }, + extraFast: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ExtraFast); + }, + }, + cursed: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.Cursed); + }, + }, + magicResistant: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.MagicResistant); + }, + }, + fireEnchanted: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.FireEnchanted); + }, + }, + lightningEnchanted: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.LightningEnchanted); + }, + }, + coldEnchanted: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ColdEnchanted); + }, + }, + manaBurn: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.ManaBurn); + }, + }, + teleportation: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.Teleportation); + }, + }, + spectralHit: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.SpectralHit); + }, + }, + stoneSkin: { + /** + * @this {Monster} + * @returns {boolean} + */ + get: function () { + if (!this.isMonster) return false; + return this.getEnchant(sdk.enchant.StoneSkin); + }, + }, + multiShot: { + /** + * @this {Monster} + * @returns {boolean} + */ + 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: { + /** @returns {number} fire resist stat, adjusted for this-is-`me` shrine buffs */ + 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: { + /** @returns {number} cold resist stat, adjusted for this-is-`me` shrine/potion buffs */ + 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: { + /** @returns {number} lightning resist stat, adjusted for this-is-`me` shrine buffs */ + 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: { + /** @returns {number} poison resist stat, adjusted for this-is-`me` shrine/potion buffs */ + 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: { + /** @returns {number} */ + get: function () { + return Math.round(this.hp * 100 / this.hpmax); + } + }, + attacking: { + /** + * @this {Player | Monster} + * @throws {Error} if used on a unit type other than Player or Monster + * @returns {boolean} + */ + 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: { + /** + * @this {Player | Monster} + * @throws {Error} if used on a unit type other than Player or Monster + * @returns {boolean} + */ + 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: { + /** + * @this {Player | Monster} + * @returns {boolean} + */ + 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: { + /** + * @this {Player} + * @throws {Error} if used on a unit type other than Player + * @returns {boolean} + */ + 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: { + /** + * @this {ItemUnit} + * @throws {Error} if used on a non-item unit (siblings in this block return false/undefined instead) + * @returns {number} 100 for stackable/charge items with no durability track + */ + 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); +}; + +/** + * @this {ItemUnit} + * @param {boolean} [shiftBuy] - shift-click buy (buys full stack for stackables) + * @param {boolean} [gamble] - buy through the gambling window instead of a normal shop + * @returns {boolean} + */ +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) +/** + * @this {ItemUnit} + * @returns {boolean} + */ +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; +}; + +/** + * @this {ItemUnit} + * @returns {boolean} + */ +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); + } +}; + + +/** + * @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 : []; +}; + +/** + * @description Return the items of a unit + * @param args + * @returns {ItemUnit[]} + */ +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; +}; + +/** + * @this {ItemUnit} + * @param {number|string} id - prefix num, or prefix name (whitespace/case-insensitive) + * @returns {boolean} + */ +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; +}; + +/** + * @this {ItemUnit} + * @param {number|string} id - suffix num, or suffix name (whitespace/case-insensitive) + * @returns {boolean} + */ +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; +}; + +/** + * @this {ItemUnit} + * @param {number} id + * @param {number} [subid] + * @returns {number} + */ +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; +*/ + +/** + * @this {ItemUnit} + * @returns {number} the NTIP-style color id for this magic/rare/set/unique item, or -1 if unmapped + */ +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.level - a.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, + /** @returns {void} */ + rollback: () => currentEquiped.forEach(item => item.equip()) // Note; rollback only works if you had other items equipped before. + }; +}; + +Unit.prototype.getBodyLoc = (function () { + // Body location -> item types that can be equipped there + const bodyLocTypes = 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]], + ]); + /** + * @this {ItemUnit} + * @returns {number[]} + */ + return function () { + let bodyLoc = []; + + for (let [key, value] of bodyLocTypes) { + if (value.includes(this.itemType)) { + bodyLoc.push(key); + } + } + + return bodyLoc; + }; +})(); + +/** + * @param {number} type - one of sdk.stats.FireResist/ColdResist/PoisonResist/LightningResist + * @param {number} [difficulty] - 0 (normal) to 2 (hell); clamped to that range + * @returns {number} resistance value, or -1 if `type` isn't a resistance stat + */ +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: { + /** @returns {number} distance from `me` to this unit/coord/preset-unit, or NaN if not in-game */ + 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, + /** + * @param {{range?: number, coll?: number, type?: number, ignoreClassids?: number[]}} [givenSettings] + * @returns {number} count of attackable monsters near this unit/coord matching the given filters + */ + 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; + } + }); +} + +/** + * @this {Monster} + * @param {...number} enchants + * @returns {boolean} + */ +Unit.prototype.hasEnchant = function (...enchants) { + if (!this.isMonster) return false; + for (let enchant of enchants) { + if (this.getEnchant(enchant)) return true; + } + return false; +}; + +/** + * Switches to the main weapon set (if checking `me`) before testing, so results always reflect + * the main-hand loadout. + * @returns {boolean} + */ +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; +}); + +/** + * @param {number} [range] + * @param {number} [coll] - collision flags to also filter by, or 0 to skip collision checks + * @param {number} [type] - spectype flags; when set and `noSpecialMobs`, special monsters are excluded + * @param {boolean} [noSpecialMobs] + * @returns {number} + */ +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; +}; + +/** + * @param {{range?: number, count?: number, coll?: number, spectype?: number}} [givenSettings] + * @returns {boolean} true once `settings.count` matching monsters are found (short-circuits the scan) + */ +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, + /** + * Generic property/method accessor: looks up `this[args[0]]` and, if it's a function, calls it + * with the remaining args; otherwise returns the property value (or {} if undefined). + * @param {...unknown} args - [key, ...callArgs] + * @returns {unknown} + */ + 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]] + : {}; + } +}); + +/** + * @this {PresetUnit} + * @returns {{id: number, area: number, x: number, y: number}} absolute world coords + * (roomx/roomy converted to tile units), unlike this.x/this.y which are room-relative + */ +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, + }; +}; + +/** + * @this {ObjectUnit} + * @returns {boolean} + */ +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; +}; + +/** + * @this {ObjectUnit | Tile} + * @param {number} [targetArea] - expected destination area; used for quest-completion gating + * and to detect success when reached + * @throws {Error} if a portal/lever/stairs requires an incomplete quest + * @returns {boolean} + */ +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..16b016fd5 --- /dev/null +++ b/d2bs/kolbot/libs/core/Runewords.js @@ -0,0 +1,385 @@ +/** +* @filename Runewords.js +* @author kolton, theBGuy +* @desc make and reroll runewords +* +*/ + +const Runeword = require("./GameData/RuneData"); + +/** @type {IRunewords} */ +const Runewords = { + needList: [], + pickitEntries: [], + validGids: [], + + /** + * Parses KeepRunewords pickit entries and resolves Config.Runewords base entries to classids. + */ + 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); + + 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); + + 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 + + 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; + }, + + /** + * Finds or buys a Scroll of Town Portal for rerolling runewords. + * @returns {ItemUnit|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); + }, + + /** + * Sockets bases with runes for every completable runeword, then rerolls bad ones. + * @returns {boolean} + */ + 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; + }, + + /** + * Cubes a Hel rune, bad-rolled base, and a Town Portal scroll to reroll each unwanted runeword base. + * @returns {boolean} + */ + 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..e0f861ca3 --- /dev/null +++ b/d2bs/kolbot/libs/core/Skill.js @@ -0,0 +1,797 @@ +/** +* @filename Skill.js +* @author theBGuy +* @credit kolton +* @desc Skill library +* +*/ + +(function () { + const _SkillData = require("./GameData/SkillData"); + // Skills usable in both wereforms + const wereFormSharedSkills = 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 wolfOnlySkills = new Set([sdk.skills.FeralRage, sdk.skills.Rabies, sdk.skills.Fury]); + const bearOnlySkills = new Set([sdk.skills.Maul, sdk.skills.ShockWave]); + + /** + * @todo Move some of the precast functions here + * @type {Skill} + */ + 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); + }, + + /** + * @param {number} [classid] + * @returns {[number, number]} The skill id range `[min, max]` for the given class. + */ + 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 + /** @returns {void} */ + 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; + + let wolfForm = me.getState(sdk.states.Wearwolf); + if (wolfForm) return wereFormSharedSkills.has(skillId) || wolfOnlySkills.has(skillId); + + let bearForm = me.getState(sdk.states.Wearbear); + if (bearForm) return wereFormSharedSkills.has(skillId) || bearOnlySkills.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 + /** + * @param {number} skillId + * @param {number} [hand] + * @param {ItemUnit} [item] + * @returns {boolean} + */ + 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 + /** + * @param {number | string} mode + * @returns {boolean} + */ + 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 + /** @returns {boolean} */ + 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); + } + } + }, + + /** @returns {boolean} True if the character currently has Telekinesis available. */ + 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..2f54f4168 --- /dev/null +++ b/d2bs/kolbot/libs/core/Storage.js @@ -0,0 +1,750 @@ +/* eslint-disable @stylistic/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 + " Error: " + e2.message); + } + + return false; + }; + + /** + * Clears the container's buffer and item list. @returns {boolean} always true + */ + 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 or already occupied move on + if (Config.Inventory[x][y] === 0 || this.buffer[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 || this.buffer[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("Storage.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("Storage.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("Storage.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! + 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); + if (!tmpLocation) return false; // no spot to move the blocking item — can't make room + // 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; + } + }; + + /** + * Logs the container's occupancy grid (when >60% full) and used-space percent to console. + */ + 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"); + }; + + /** @returns {number} percent of the container's total space currently occupied */ + 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; + } + }; + + /** + * @deprecated + * @returns {string} source representation of the container's buffer + */ + Container.prototype.toSource = function () { + return this.buffer.toSource(); + }; + + /** @type {storage} Storage */ + const Storage = new function () { + /** + * Builds all storage containers (Inventory, TradeScreen, Stash, Belt, Cube) and loads them. + */ + 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(); + }; + + /** @returns {1 | 2 | 3 | 4} belt column count based on the equipped belt's item code */ + 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 + }; + + /** + * Resets and re-marks every container from the character's current item list. @returns {boolean} + */ + 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..03ccbd994 --- /dev/null +++ b/d2bs/kolbot/libs/core/Town.js @@ -0,0 +1,2495 @@ +/** +* @filename Town.js +* @author kolton, theBGuy +* @desc do town chores like buying, selling and gambling +* +*/ + +/** @type {Town} */ +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); + }, + + /** + * @description Run the full set of town chores (heal, id, clear/sort inventory, buy pots/keys/scrolls, shop, gamble, repair, revive merc, cube, runewords, stash) + * @param {boolean} [repair=false] Force a repair even if items aren't below the repair threshold + * @returns {boolean} True once chores complete + */ + 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) { + choices.add(npcs.Shop); + } 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 + * @returns {boolean} True if we didn't need healing or successfully interacted with the healer + */ + heal: function () { + if (!me.needHealing()) return true; + return !!(Town.initNPC("Heal", "heal")); + }, + + /** + * @description Buy potions to fill the belt and any configured HP/MP buffer + * @returns {boolean} True if belt/buffer requirements were met (or nothing needed), false if we couldn't buy (no gold or failed to reach an NPC) + */ + 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; + }, + + /** + * @description Buy scrolls (and the tome itself if missing) to top up the given TP/ID tome to a full stack + * @param {number} classid - TomeofTownPortal or TomeofIdentify classid + * @returns {boolean} True if the tome is/was filled, false if we lacked gold or couldn't buy the tome/scrolls + */ + 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 ((e1 instanceof ScriptError)) { + throw e1; + } + console.log(e1); + // Couldn't buy the tome, don't spam the scrolls + return false; + } + } else { + return false; + } + } + + const scrollID = classid === sdk.items.TomeofTownPortal + ? sdk.items.ScrollofTownPortal + : sdk.items.ScrollofIdentify; + let scroll = npc.getItem(scrollID); + if (!scroll) return false; + + try { + scroll.buy(true); + } catch (e2) { + 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); + }, + + /** + * @description Identify unid items in the inventory (via Cain, tome, or bought scrolls) and sell/keep them per pickit + * @returns {boolean} True if identification was performed or nothing needed identifying, false if there were no items to process + */ + 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; + }, + + /** + * @description Use Cain to identify unid items when the CainID config thresholds (gold, min unids, no kept unids) are met + * @returns {boolean} True if Cain was used or no unids remained, false if CainID is disabled/unaffordable or thresholds weren't met + */ + 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; + }, + + /** + * @description MiniShopBot: scan the currently interacted NPC's wares and buy any pickit-wanted items that fit and we can afford + * @returns {boolean} True if scanning ran (or MiniShopBot is disabled), false if there's no NPC/no items to scan + */ + 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(), + + /** + * @description Gamble the configured item classids while above the gold-stop threshold, keeping/selling results per pickit + * @returns {boolean} True if gambling completed or nothing was needed, false if we couldn't reach the gamble NPC or ran out of inventory space + */ + 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; + }, + + /** + * @description Check whether gambling should start (enabled and gold at/above the start threshold) + * @returns {boolean} + */ + needGamble: function () { + return Config.Gamble && me.gold >= Config.GambleGoldStart; + }, + + /** + * @description Find the most recently gambled item (an inventory item whose gid isn't in the tracked list yet) + * @param {number[]} list - gids of items already accounted for + * @returns {ItemUnit | false} The newly gambled item, or false if none found + */ + 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; + }, + + /** + * @description Buy a quantity of a specific potion type (thawing/antidote/etc.) from the act's pot dealer, optionally drinking them + * @param {number} quantity + * @param {number | string} type + * @param {boolean} [drink=false] + * @param {boolean} [force=false] + * @param {Unit} [npc=null] + * @returns {boolean} True if pots were bought (or the resistance/skill check made buying unnecessary), false on invalid args or NPC/trade failure + */ + 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 + }; + }, + + /** + * @description Buy a stack of keys if we have fewer than 6, avoiding Hratli in act 3 + * @returns {boolean} True if keys were bought or already stocked, false if we couldn't reach the NPC or find keys to buy + */ + 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); + }, + + /** + * @description Repair gear (via cube first, else the act's repair NPC) and re-buy a spent quiver if needed + * @param {boolean} [force=false] Force a repair even if no item is below the repair threshold + * @returns {boolean} True if repairs were done or none were needed, false if we couldn't reach the repair NPC + */ + 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); + }, + + /** + * @description Revive the merc at the act's merc NPC and (optionally) recast BO on it via MercWatch + * @returns {boolean} True if no merc was needed or the merc was successfully revived, false if we couldn't reach the NPC, lacked gold, or revival failed + */ + 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(); + }, + + /** + * @description Move to and open the stash (via telekinesis or click), retrying up to 5 times + * @returns {boolean} True if the stash UI opened, false if it couldn't be opened + */ + 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; + }, + + /** + * @description Walk to and loot our own corpse(s) to recover dropped items, re-initializing skills afterward + * @returns {boolean} True if there was no corpse or looting finished, false if we died again while retrieving it + */ + 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(); + }, + + /** + * @description Consolidate loose TP/ID scrolls back into their tomes, then sell or drop the leftovers + * @returns {boolean} True if scrolls were processed, false if there were no loose scrolls in the inventory + */ + 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; + }, + + /** + * @description Tidy the belt/inventory: return pots to the belt, drink specials, and sell or drop unwanted leftover items + * @returns {boolean} Always true once the clean-up pass completes + */ + 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.length && 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; + }, + + /** + * @description Initialize the current act's town spot coordinates (act 1 is derived from the town fire preset) + * @returns {boolean} True if spots are initialized, false if the act 1 town-fire preset couldn't be found + */ + 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; + /** @returns {boolean} True once the target NPC is within 5 units, stopping the move early. */ + 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 - " + e.message); + } + } + + 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 - " + e.message); + } + } + + 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; + } + /** + * Stub matching Unit.prototype.getFlag's shape - gems have no flags, so this always passes. + * @param {number} flag unused + * @returns {boolean} always true + */ + // 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 - " + e.message); + } + } + + 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..c5107557c --- /dev/null +++ b/d2bs/kolbot/libs/core/Util.js @@ -0,0 +1,717 @@ +/** + * @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 () { + /** @param {string} [message] */ + 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 = { + /** + * Overloaded distance helper - accepts (unit), (x, y), (unitA, unitB), (unit, x, y), + * or (x1, y1, x2, y2); any other arity or mismatched arg types returns Infinity. + * @param {...(number|Unit)} args + * @returns {number} + */ + 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 = { + /** + * Comparator: sorts units by distance from the player. + * @param {Unit} a + * @param {Unit} b + * @returns {number} + */ + 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)); + }, + + /** + * Comparator: sorts preset units by distance from the player, using preset room-relative + * x/y calculations. + * @param {PresetUnit} a + * @param {PresetUnit} b + * @returns {number} + */ + 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); + }, + + /** + * Comparator: sorts [x, y] coordinate pairs by distance from the player. + * @param {[number, number]} a + * @param {[number, number]} b + * @returns {number} + */ + points: function (a, b) { + return getDistance(me, a[0], a[1]) - getDistance(me, b[0], b[1]); + }, + + /** + * Comparator: sorts numbers in ascending order. + * @param {number} a + * @param {number} b + * @returns {number} + */ + numbers: function (a, b) { + return a - b; + } + }; + + const Messaging = { + /** + * @param {string} name - script filename + * @param {*} msg + * @returns {boolean} true if a running script was found and sent the message + */ + sendToScript: function (name, msg) { + let script = getScript(name); + + if (script && script.running) { + script.send(msg); + + return true; + } + + return false; + }, + + /** + * Sends a copydata message to another profile, optionally waiting for a matching response. + * @param {string} profileName + * @param {number} mode + * @param {*} message + * @param {boolean} [getResponse] + * @returns {boolean|*} false on failure/timeout, true if sent with no response requested, + * or the parsed response object when getResponse is true + */ + sendToProfile: function (profileName, mode, message, getResponse = false) { + let response; + + /** + * @param {number} mode2 + * @param {string} msg + * @returns {boolean} + */ + 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..14a54a2fe --- /dev/null +++ b/d2bs/kolbot/libs/globals.js @@ -0,0 +1,373 @@ +/** +* @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", { + /** @param {number} ms */ + 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", { + /** @param {number} ms */ + 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.d.ts b/d2bs/kolbot/libs/manualplay/MapMode.d.ts new file mode 100644 index 000000000..bb86dbac1 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/MapMode.d.ts @@ -0,0 +1,12 @@ +export {}; + +declare global { + // Value shape of the global `MapMode` const in MapMode.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface MapMode { + mapHelperFilePath: string; + + include(): void; + generalSettings(): void; + } +} diff --git a/d2bs/kolbot/libs/manualplay/MapMode.js b/d2bs/kolbot/libs/manualplay/MapMode.js index 33f7103e4..8f2ca6c58 100644 --- a/d2bs/kolbot/libs/manualplay/MapMode.js +++ b/d2bs/kolbot/libs/manualplay/MapMode.js @@ -5,62 +5,65 @@ * */ +/** @type {MapMode} */ 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", + /** @returns {void} */ + 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]; + /** @returns {void} */ + 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..808d3b04c 100644 --- a/d2bs/kolbot/libs/manualplay/config/Amazon.js +++ b/d2bs/kolbot/libs/manualplay/config/Amazon.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Amazon config file /* Brief instructions: @@ -18,194 +17,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..60d5ee3f7 100644 --- a/d2bs/kolbot/libs/manualplay/config/Assassin.js +++ b/d2bs/kolbot/libs/manualplay/config/Assassin.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Assassin config file /* Brief instructions: @@ -18,203 +17,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..95cf49d9d 100644 --- a/d2bs/kolbot/libs/manualplay/config/Barbarian.js +++ b/d2bs/kolbot/libs/manualplay/config/Barbarian.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Barbarian config file /* Brief instructions: @@ -18,192 +17,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..d1acec994 100644 --- a/d2bs/kolbot/libs/manualplay/config/Druid.js +++ b/d2bs/kolbot/libs/manualplay/config/Druid.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Druid config file /* Brief instructions: @@ -18,196 +17,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..0c35c7952 100644 --- a/d2bs/kolbot/libs/manualplay/config/Necromancer.js +++ b/d2bs/kolbot/libs/manualplay/config/Necromancer.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Necromancer config file /* Brief instructions: @@ -18,217 +17,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..4ed082911 100644 --- a/d2bs/kolbot/libs/manualplay/config/Paladin.js +++ b/d2bs/kolbot/libs/manualplay/config/Paladin.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Paladin config file /* Brief instructions: @@ -18,196 +17,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..479904b71 100644 --- a/d2bs/kolbot/libs/manualplay/config/Sorceress.js +++ b/d2bs/kolbot/libs/manualplay/config/Sorceress.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ // Sorceress config file /* Brief instructions: @@ -18,195 +17,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.d.ts b/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.d.ts new file mode 100644 index 000000000..0e254245e --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.d.ts @@ -0,0 +1,63 @@ +export {}; + +declare global { + /** + * An action to perform when a hook is triggered, e.g. open a chest/portal or use/take a portal. + */ + interface ActionHookAction { + do: string; + id?: number; + } + + /** + * An entry in the ActionHooks `hooks`/`portals`/`frame` arrays. + */ + interface ActionHookEntry { + name: string; + type?: string; + dest?: number | { x: number; y: number }; + action?: ActionHookAction; + hook: Hook; + } + + // Value shape of the global `ActionHooks` const in ActionHooks.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface ActionHooks { + enabled: boolean; + hooks: ActionHookEntry[]; + portals: ActionHookEntry[]; + frame: ActionHookEntry[]; + action: number | null; + currArea: number; + prevAreas: number[]; + + /** + * Record a key press to be processed on the next `checkAction` call. + */ + event(keycode: number): void; + + /** + * Process the last recorded key press, dispatching the matching hook or action. + */ + checkAction(): void; + + /** + * Poll for area changes and uber-portal availability, refreshing hooks as needed. + */ + check(): void; + + /** + * Y coordinate for the next hook, stacked above the previously added ones. + */ + yHookLoc(): number; + + /** + * Build the hook set (side areas, POIs, waypoint, prev/next area) for the given area. + */ + add(area: number): void; + + getHook(name: string): ActionHookEntry | false; + getPortalHook(name: string): ActionHookEntry | false; + flush(): void; + } +} diff --git a/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js b/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js index a5938161b..62ffb12dc 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ActionHooks.js @@ -1,780 +1,1083 @@ /** -* @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 + * + */ + +/** @type {ActionHooks} */ +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 uberPortalConfigs = [ + { 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 } + ]; + + 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 openPortals = uberPortalConfigs.filter(function (config) { + return !!Pather.getPortal(config.area); + }); + + if (!openPortals.length) { + return; + } + + // draw the box/frame once, when the first portal appears + if (!ActionHooks.frame.some(function (f) { return f.name === "portalbox"; })) { + 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), + }); + } + + // add a hook only for portals we haven't hooked yet (fixed key/offset per type) + openPortals.forEach(function (config) { + if (ActionHooks.getPortalHook(config.name)) return; + 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; + }, + + /** + * Process the last recorded key press, dispatching the matching hook or action. + */ + 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; + } + }, + + /** + * Poll for area changes and uber-portal availability, refreshing hooks as needed. + */ + 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; + } + + addUberPortals(); // poll - crafted portals appear without an area change + }, + + /** + * Y coordinate for the next hook, stacked above the previously added ones. + * @returns {number} + */ + 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.d.ts b/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.d.ts new file mode 100644 index 000000000..c3c27ea0d --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.d.ts @@ -0,0 +1,40 @@ +export {}; + +declare global { + /** + * Line/Text/Line triple produced by ItemHooks.newHook() for a single ground-item marker. + */ + interface ItemHookParts { + itemLoc: Line[]; + itemName: Text[]; + vector: Line[]; + } + + /** + * An entry in ItemHooks.hooks tracking a single ground-item marker. + */ + interface ItemHookEntry { + item: ItemUnit; + area: number; + hook: Line[]; + name: Text[]; + vector: Line[]; + } + + // Value shape of the global `ItemHooks` const in ItemHooks.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface ItemHooks { + enabled: boolean; + pickitEnabled: boolean; + hooks: ItemHookEntry[]; + + check(): void; + update(): void; + getName(item: ItemUnit): string; + newHook(item: ItemUnit): ItemHookParts; + add(item: ItemUnit): void; + getHook(item: ItemUnit): Line[] | false; + remove(item: ItemUnit): boolean; + flush(): void; + } +} diff --git a/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js b/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js index 78cba8200..063fe7d94 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ItemHooks.js @@ -1,3 +1,4 @@ +/* eslint-disable @stylistic/max-len */ /** * @filename ItemHooks.js * @author theBGuy @@ -6,398 +7,461 @@ */ // 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" }; +/** @type {ItemHooks} */ +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: [], + + /** + * Syncs on-screen item hooks with ground items in the current area, adding hooks for newly + * visible items and removing stale ones (item picked up, moved out of area, or no longer + * eligible under the current pickit filter). + */ + 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(); + } + } + }, + + /** + * Re-anchors every hook's vector line to the player's current position. + */ + 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; + }, + + /** + * Removes every active item hook's line/text overlays and clears the hooks array. + */ + 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.d.ts b/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.d.ts new file mode 100644 index 000000000..f3148e439 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.d.ts @@ -0,0 +1,22 @@ +export {}; + +declare global { + /** + * A per-monster hook entry (instance of the closure-private MonsterHook constructor in MonsterHooks.js). + */ + interface MonsterHookEntry { + unit: Monster; + hook: Text; + update(): void; + } + + // Value shape of the global `MonsterHooks` const in MonsterHooks.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface MonsterHooks { + hooks: { [gid: number]: MonsterHookEntry }; + enabled: boolean; + check(): void; + remove(unit: Monster): boolean; + flush(): void; + } +} diff --git a/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js b/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js index 3a35e33c0..330a9f7b4 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/MonsterHooks.js @@ -6,102 +6,119 @@ * */ -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(); - } - } -}; +/** @type {MonsterHooks} */ +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); + } + + /** + * Syncs the hook's marker position with its unit; removes the hook if the unit is gone or unattackable. + * @returns {void} + */ + 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, + + /** @returns {void} */ + 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 + * @returns {boolean} + */ + remove: function (unit) { + if (this.hooks.hasOwnProperty(unit.gid)) { + this.hooks[unit.gid].hook.remove(); + delete this.hooks[unit.gid]; + + return true; + } + + return false; + }, + + /** @returns {void} */ + 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.d.ts b/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.d.ts new file mode 100644 index 000000000..ca2d3da08 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.d.ts @@ -0,0 +1,25 @@ +export {}; + +declare global { + /** + * An entry in ShrineHooks.hooks tracking a single inactive-shrine marker. + */ + interface ShrineHookEntry { + shrine: ObjectUnit; + hook: Text | null; + } + + // Value shape of the global `ShrineHooks` const in ShrineHooks.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface ShrineHooks { + enabled: boolean; + hooks: ShrineHookEntry[]; + shrines: Map; + check(): void; + newHook(shrine: ObjectUnit): Text | null; + add(shrine: ObjectUnit): void; + getHook(shrine: ObjectUnit): Text | null | false; + remove(shrine: ObjectUnit): boolean; + flush(): void; + } +} diff --git a/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js b/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js index 826f0cac2..125c47a94 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/ShrineHooks.js @@ -5,111 +5,122 @@ * */ +/** @type {ShrineHooks} */ 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"] + ]), + + /** + * Prunes stale hooks then adds/removes hooks for inactive/active shrines in the current area. + */ + 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; + }, + + /** + * Removes all shrine hooks. + */ + 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..78722addf --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.d.ts @@ -0,0 +1,72 @@ +/** + * An entry in the hook arrays + */ +export interface HookEntry { + name: string; + hook: Hook; + dest?: number; + type?: string; +} + +declare global { + // Value shape of the global `TextHooks` const in TextHooks.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + // Ambient value declaration (Skill/Attack/Misc precedent): the checker leaves some + // @type-bound js consts unresolved or any (cause undiagnosed); the ambient const + // restores resolution and empirically does not collide with the js declaration. + const TextHooks: TextHooks; + + interface TextHooks { + events: typeof import("../../modules/Events"); + enabled: boolean; + displayTitle: boolean; + displaySettings: boolean; + frameworkDisplayed: boolean; + frameYSizeScale: number; + frameYLocScale: number; + settingsModifer: number; + dashBoardWidthScale: number; + statusFrameYSize: number; + qolFrameYSize: number; + /** Set on area change (in the `qolBoard` special case); absent before the first area change. */ + lastAct?: 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..3e40e4a9b 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/TextHooks.js @@ -1,313 +1,464 @@ /** -* @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 + * + */ + +/** @type {TextHooks} */ +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")} HooksModule */ + + /** + * @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 = { + /** @returns {HookEntry} */ + credits: function () { + return HookFactory.createHooks.text({ + name: "credits", + text: "MM by theBGuy", + x: 0, + y: 600 + Hooks.resfix.y, + }); + }, + /** @returns {HookEntry} */ + title: function () { + return HookFactory.createHooks.text({ + name: "title", + text: ":: Running Map-Mode, enter .help in chat to see more commands ::", + x: 0, + y: 13, + }); + }, + /** @returns {HookEntry} */ + 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, + }); + }, + /** @returns {HookEntry} */ + 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, + }); + }, + /** @returns {HookEntry} */ + 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; + }, + /** @returns {HookEntry} */ + 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, + }); + }, + /** @returns {HookEntry} */ + 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, + }); + }, + /** @returns {(HookEntry & { dest: number, type: string })|null} */ + 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; + }, + /** @returns {(HookEntry & { dest: number, type: string })|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; + }, + /** @returns {HookEntry} */ + 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; + }, + + /** + * Removes the status/dashboard/qol hooks; also clears the framework hooks if Hooks is disabled. + */ + 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.d.ts b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.d.ts new file mode 100644 index 000000000..01c2339d8 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.d.ts @@ -0,0 +1,45 @@ +export {}; + +declare global { + /** + * A point of interest returned by `VectorHooks.getPOI()`: quest chest, boss, or other notable spot. + */ + interface VectorHooksPOI { + name: string; + x: number; + y: number; + action?: { do: string; id?: number }; + } + + // Value shape of the global `VectorHooks` const in VectorHooks.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface VectorHooks { + enabled: boolean; + currArea: number; + lastLoc: { x: number; y: number }; + names: Text[]; + hooks: Line[]; + + /** + * Redraw the exit vectors/labels on area change, or reposition them as the player moves. + */ + check(): void; + + /** + * Reposition all vector lines to originate from the player's current location. + */ + update(): void; + + flush(): void; + + /** + * Find the current area's waypoint object, if any. + */ + getWP(): { id: number; area: number; x: number; y: number } | false; + + /** + * Find the current area's point of interest (quest chest, boss, etc.), if any. + */ + getPOI(): VectorHooksPOI | false; + } +} diff --git a/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js index 18a745450..c4679e1d2 100644 --- a/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js +++ b/d2bs/kolbot/libs/manualplay/hooks/VectorHooks.js @@ -5,397 +5,580 @@ * */ -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; - } -}; +/** @type {VectorHooks} */ +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 unit + * @description This function takes a preset unit (like a monster or object), a name, and an optional action, + * and returns a POI object with the unit's coordinates and the provided name and action. If the unit is not found, it returns false. + * @param {PresetUnit} unit + * @param {string} name + * @param {*} action + * @returns {{ name: string, x: number, y: number, action?: * }} + */ + function createPOIFromPresetUnit(unit, name, action = null) { + const coords = unit.realCoords(); + const poi = { + name: name, + x: coords.x, + y: coords.y + }; + + if (action) { + poi.action = action; + } + + return poi; + } + + /** + * 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; + + return createPOIFromPresetUnit(unit, name, action); + } + + /** + * 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; + + return createPOIFromPresetUnit(unit, name, action); + } + + /** + * 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; + + return createPOIFromPresetUnit(unit, name, action); + } + + /** + * 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 = { + /** @returns {VectorHooksPOI|false} POI for this area's small sparkly (super) chest, if present. */ + superChest: function() { + return createPOIFromPreset( + sdk.objects.SmallSparklyChest, + "SuperChest", + { do: "openChest", id: sdk.objects.SmallSparklyChest } + ); + }, + + /** @returns {VectorHooksPOI|false} POI for this area's large sparkly chest, if present. */ + largeSparklyChest: function() { + return createPOIFromPreset( + sdk.objects.LargeSparklyChest, + "SuperChest", + { do: "openChest", id: sdk.objects.LargeSparklyChest } + ); + }, + + /** @returns {VectorHooksPOI|false} POI for the Tal Rasha tomb orifice, or its super chest fallback. */ + talRashaTomb: function() { + let poi = createPOIFromPreset(sdk.quest.chest.HoradricStaffHolder, "Orifice"); + if (poi) return poi; + + return createPOIFromPreset(sdk.objects.SmallSparklyChest, "SuperChest"); + }, + + /** @returns {VectorHooksPOI|false} POI for this area's red portal to Hell, if present. */ + 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: [], + + /** + * Redraw the exit vectors/labels on area change, or reposition them as the player moves. + */ + 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(); + } + }, + + /** + * Reposition all vector lines to originate from the player's current location. + */ + 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; + } + }, + + /** + * Removes all vector lines and area-name labels, and resets currArea so check() rebuilds them. + * @returns {void} + */ + flush: function () { + while (this.hooks.length) { + this.hooks.pop().remove(); + } + + while (this.names.length) { + this.names.pop().remove(); + } + + VectorHooks.currArea = 0; + }, + + /** + * Find the current area's waypoint object, if any. + * @returns {{ id: number, area: number, x: number, y: number }|false} + */ + 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; + }, + + /** + * Find the current area's point of interest (quest chest, boss, etc.), if any. + * @returns {VectorHooksPOI|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..90300f575 100644 --- a/d2bs/kolbot/libs/manualplay/libs/ConfigOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/ConfigOverrides.js @@ -5,109 +5,115 @@ * */ -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) { + /** + * Loads the manualplay character config (falling back to the class default) before delegating + * to the original `Config.init` for the shared setup and AutoBuild initialization. + * @param {boolean} notify + * @returns {void} + */ + 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." + (e1 && e1.message ? " - " + e1.message : "")); + } + } 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); + + // e2 is surfaced via console.error immediately above; the throw is a clean domain 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; + /** + * @param {string} what + * @returns {void} + */ + 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.d.ts b/d2bs/kolbot/libs/manualplay/libs/Hooks.d.ts new file mode 100644 index 000000000..d42a9f844 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/libs/Hooks.d.ts @@ -0,0 +1,20 @@ +export {}; + +declare global { + // Value shape of the global `Hooks` const in Hooks.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface Hooks { + dashBoard: { x: number, y: number }; + portalBoard: { x: number, y: number }; + qolBoard: { x: number, y: number }; + resfix: { x: number, y: number }; + saidMessage: boolean; + userAddon: boolean; + enabled: boolean; + flushed: boolean | number; + + init(): void; + update(): void; + flush(flag: boolean | number): boolean; + } +} diff --git a/d2bs/kolbot/libs/manualplay/libs/Hooks.js b/d2bs/kolbot/libs/manualplay/libs/Hooks.js new file mode 100644 index 000000000..7d79d4ee9 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/libs/Hooks.js @@ -0,0 +1,102 @@ +/** @typedef {import('./hooks/TextHooks')} TextHooksModule */ + +/** @type {Hooks} */ +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, + + /** + * Includes every hook module file under libs/manualplay/hooks/ that isn't already included. + * @returns {void} + */ + 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); + } + } + }); + }, + + /** + * Blocks until the game is ready, then drives a check pass across all hook modules. + * @returns {void} + */ + 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; + }, + + /** + * Flushes hook module UI state. `true` flushes everything and disables hooks; a uiflags value + * flushes only the modules relevant to that flag (or all of them for unrecognized flags). + * @param {boolean | number} flag + * @returns {boolean} true if already flushed to this flag or once the flush completes + */ + 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.d.ts b/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.d.ts new file mode 100644 index 000000000..f5cb664e0 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.d.ts @@ -0,0 +1,15 @@ +export {}; +declare global { + // Manualplay's additions to the global Pather. Declared HERE, not in sdk/types/Pather.d.ts: + // override-layer members stay with their layer so the base interface only describes what the + // base implementation provides. + interface Pather { + /** + * Early-abort flag for the manualplay move overrides: raised by {@link Pather.stopEvent} and + * cleared by the move that observes it. + */ + stop: boolean; + /** @param key forwarded by the `keyup` event; Numpad9 while not idle raises {@link Pather.stop}. */ + stopEvent(key: number): void; + } +} diff --git a/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js b/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js index 179b72ff7..6695fe772 100644 --- a/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/PatherOverrides.js @@ -1,3 +1,4 @@ +/* eslint-disable @stylistic/max-len */ /** * @filename PatherOverides.js * @author theBGuy @@ -5,402 +6,435 @@ * */ -includeIfNotIncluded("common/Pather.js"); +includeIfNotIncluded("core/Pather.js"); Pather.stop = false; +/** + * "keyup" listener that requests an early stop of the current move when Numpad9 is pressed. + * @param {number} key + */ Pather.stopEvent = function (key) { - key === sdk.keys.Numpad9 && !me.idle && (Pather.stop = true); + key === sdk.keys.Numpad9 && !me.idle && (Pather.stop = true); }; +/** + * @param {number} act + * @returns {boolean} true once the character has reached the requested act + */ 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; }; +/** + * @param {number} area + * @param {boolean} [clearPath] + * @returns {boolean} + */ 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; }; +/** + * @param {number} [x] + * @param {number} [y] + * @param {number | undefined} [minDist] + * @returns {boolean} + */ 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; }; +/** + * @param {number} x + * @param {number} y + * @param {number} [maxRange] + * @returns {boolean} + */ 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; }; +/** + * @param {number} x + * @param {number} y + * @param {number | undefined} [retry] + * @param {boolean | undefined} [clearPath] + * @param {boolean | undefined} [pop] + * @returns {boolean} + */ 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); + // defaults must be applied BEFORE clearSettings is built from clearPath - computing them + // after left the AttackSkill guard unreachable and walkers unconditionally clearing + retry === undefined && (retry = useTeleport ? 3 : 15); + // walking characters need to clear in front of them - but only when an attack skill is assigned + clearPath === undefined && (clearPath = Config.AttackSkill.some(skillId => skillId > 0) && !useTeleport ? true : false); + pop === undefined && (pop = false); + let clearSettings = { + clearPath: !!clearPath, + range: 10, + specType: (typeof clearPath === "number" ? clearPath : 0), + }; + 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.d.ts b/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.d.ts new file mode 100644 index 000000000..9f85d2300 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/libs/PickitOverrides.d.ts @@ -0,0 +1,9 @@ +export {}; +declare global { + // Manualplay's addition to the global Pickit - kept out of sdk/types/Pickit.d.ts so the base + // interface only describes what the base implementation provides. + interface Pickit { + /** Stripped-down pickup loop for map mode; `range` is required (no default in the impl). */ + basicPickItems(range: number): boolean; + } +} 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..3643aa117 100644 --- a/d2bs/kolbot/libs/manualplay/libs/TownOverrides.js +++ b/d2bs/kolbot/libs/manualplay/libs/TownOverrides.js @@ -5,44 +5,52 @@ * */ -includeIfNotIncluded("common/Town.js"); +includeIfNotIncluded("core/Town.js"); +/** + * Stashes eligible inventory items (gems/runes/uniques first), then stashes gold if enabled. + * @param {boolean} [stashGold] + * @returns {boolean} + */ 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..2f8fe7dcd --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/main.js @@ -0,0 +1,322 @@ +/** +* @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')} TextHooksModule */ + +function main () { + D2Bot.init(); // Get D2Bot# handle + D2Bot.ingame(); + + (function (global, original) { + /** + * @param {...string} args - Forwarded to the original `load`. + * @returns {void} + */ + 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"); + + /** @returns {boolean} Always true; keeps the background process re-queued each low-prio check. */ + 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..8d9764913 --- /dev/null +++ b/d2bs/kolbot/libs/manualplay/modules/HelpMenu.js @@ -0,0 +1,234 @@ +/** + * @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)); + }, + + /** + * Builds and draws the chat/key-command help box (hooks + boxes) at the configured screen position. + * @returns {void} + */ + 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; + }, + + /** + * Removes all help-box hooks and boxes drawn by showMenu. + * @returns {void} + */ + hideMenu: function () { + this.cleared = true; + + while (this.hooks.length > 0) { + this.hooks.shift().remove(); + } + + while (this.box.length > 0) { + this.box.shift().remove(); + } + + return; + }, + + /** + * Comparator sorting hooks by vertical proximity to the clicked y-coordinate (HelpMenu.actionY). + * @param {Text} h1 + * @param {Text} h2 + * @returns {number} + */ + sortHooks: function (h1, h2) { + return Math.abs(h1.y - HelpMenu.actionY) - Math.abs(h2.y - HelpMenu.actionY); + } + }; + + /** + * Drains queued menu clicks, resolving each to the nearest clickable hook and echoing its help text + * overhead; clicking the "X" hook closes the menu. + * @returns {boolean} true once the action queue is drained (or the menu was closed). + */ + 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..7ef788b1a 100644 --- a/d2bs/kolbot/libs/manualplay/threads/MapHelper.js +++ b/d2bs/kolbot/libs/manualplay/threads/MapHelper.js @@ -2,419 +2,647 @@ * @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; + + /** + * Ensures the inventory holds a Tome of Town Portal stocked with scrolls, buying from the shop NPC as needed. + * @returns {void} + */ + 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..783268cd7 100644 --- a/d2bs/kolbot/libs/manualplay/threads/MapToolsThread.js +++ b/d2bs/kolbot/libs/manualplay/threads/MapToolsThread.js @@ -1,3 +1,4 @@ +/* eslint-disable @stylistic/max-len */ /** * @filename MapToolsThread.js * @author theBGuy @@ -6,285 +7,296 @@ * */ 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; +/** + * Entry point: runs the manual-play toolsthread loop - potion/chicken monitoring, ping/viper checks, + * key/game/script event handling - until a quit condition is met. + * @returns {boolean} true once the loop exits and cleanup has run + */ +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..4fa187ebf 100644 --- a/d2bs/kolbot/libs/manualplay/threads/PickThread.js +++ b/d2bs/kolbot/libs/manualplay/threads/PickThread.js @@ -5,51 +5,55 @@ * */ 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); - } +/** + * Entry point for the pick-only manual-play thread: initializes core systems, then loops + * Pickit.fastPick() while no blocking UI (inventory, stash, etc.) is open. Never returns. + */ +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..463f59226 --- /dev/null +++ b/d2bs/kolbot/libs/modules/AsyncEvents.js @@ -0,0 +1,75 @@ +/** + * @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 = []; + + /** + * @param {string} [name] - event name to match; falsy matches every trigger + * @param {(...args: unknown[]) => void} callback + */ + function Hook(name, callback) { + this.name = name; + this.callback = callback; + this.id = self.hooks.push(this) - 1; + this.__callback = callback; // used for once + } + + /** + * @param {string | ((...args: unknown[]) => void)} name - event name, or the callback itself + * when called with a single argument + * @param {(...args: unknown[]) => void} [callback] + * @returns {Hook} + */ + this.on = function (name, callback) { + if (callback === undefined && typeof name === "function") [callback, name] = [name, callback]; + return new Hook(name, callback); + }; + + /** + * Runs every matching hook's callback on the Worker queue (deferred, not synchronous). + * @param {string} name + * @param {...unknown} args + */ + 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 + + /** + * @param {string | ((...args: unknown[]) => void)} name - event name, or the callback itself + * when called with a single argument + * @param {(...args: unknown[]) => void} [callback] + */ + 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; + }; + + /** + * @param {string} name + * @param {(...args: unknown[]) => void} 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..37bca69b4 100644 --- a/d2bs/kolbot/libs/modules/Control.js +++ b/d2bs/kolbot/libs/modules/Control.js @@ -3,193 +3,326 @@ * @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, { + /** + * Resolves, in order: live D2 control properties, bound ControlAction passthrough + * methods (click/setText/getText), then the target's own fields. + * @param {Control} target + * @param {string | symbol} p + * @returns {unknown} shape depends on which of the above resolves p + */ + 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..9e0c282f5 100644 --- a/d2bs/kolbot/libs/modules/Deltas.js +++ b/d2bs/kolbot/libs/modules/Deltas.js @@ -4,36 +4,52 @@ * */ (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])) || []; + /** + * Registers a new value to watch. + * @param {() => *} checkerFn - reads the current value + * @param {(oldValue: *, newValue: *) => *} callback - invoked when the value changes + * @returns {number} the new length of the tracked values array + */ + this.track = function (checkerFn, callback) { + return this.values.push({ fn: checkerFn, callback: callback, value: checkerFn() }); + }; + /** + * Polls every tracked value and fires its callback if it changed since the last check. + */ + 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; + /** + * Stops the background worker from polling this delta's tracked values. + */ + 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..db7238c7a --- /dev/null +++ b/d2bs/kolbot/libs/modules/Graph.js @@ -0,0 +1,707 @@ +/** + * @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 = {}; + }; + + /** + * Marks this vertex's room as explored. + */ + 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 (key in this.cache) { + 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; + }; + + /** + * Sum of segment distances along a path. + * @param {PathNode[] | false} path + * @returns {number} total distance, Infinity if the path is missing/empty + */ + const pathLength = function (path) { + if (!path || !path.length) { + return Infinity; + } + return 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); + }; + + /** + * @param {"walk" | "teleport"} mode + * @returns {number} + */ + Vertex.prototype.pathDistance = function(mode = "walk") { + const key = mode + "PathDistance"; + if (key in this.cache) { + return this.cache[key]; + } + let distance = pathLength(this.path(mode)); + 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] && other.id in this.cache[key]) { + 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] && other.id in this.cache[key]) { + return this.cache[key][other.id]; + } + let distance = pathLength(this.pathTo(other, mode)); + 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 nearby = room.getNearby(); + if (!nearby) { + return []; + } + const self = this; + return nearby + .compactMap(function (r) { + return self.vertexForRoom(r); + }); + //.sort((a, b) => a.adjustedPathDistance - b.adjustedPathDistance); + }; + } + + /** + * @todo unimplemented placeholder for a custom exploration strategy + * @param {Graph} graph + * @param {(vertex: Vertex) => void} explore + */ + // 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..f7730018f 100644 --- a/d2bs/kolbot/libs/modules/HTTP.js +++ b/d2bs/kolbot/libs/modules/HTTP.js @@ -5,48 +5,60 @@ (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..42d8e51fd --- /dev/null +++ b/d2bs/kolbot/libs/modules/LazyLoader.js @@ -0,0 +1,205 @@ +/** + * @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) { + /** + * Derives the relative path from LazyLoader.js's directory to the caller's directory, by walking the + * call stack for the first frame outside this file. Used to resolve caller-relative ("./") module paths. + * @returns {string} the relative path (e.g. "../scripts"), or "" if it couldn't be determined. + */ + 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(); + + /** + * Resolves and requires a module by name, caching the result; a supplied customHandler bypasses + * requiring and is cached/returned as-is. + * @param {string|number} moduleName + * @param {*} [customHandler] + * @returns {*} the loaded module (or customHandler, if given). + */ + 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, + /** @returns {*} the lazily-loaded module. */ + 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..4804af7ce --- /dev/null +++ b/d2bs/kolbot/libs/modules/LocalChat.js @@ -0,0 +1,89 @@ +/** + * @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); + } + }; + + /** + * Enables local chat and (re)wires the `say`/event hooks for the current mode. + * @param {boolean} [cycle] - when true, advances Config.LocalChat.Mode to the next mode (0-2) first + */ + 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..26d541ba3 100644 --- a/d2bs/kolbot/libs/modules/Messaging.js +++ b/d2bs/kolbot/libs/modules/Messaging.js @@ -5,34 +5,61 @@ (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)); + + /** + * Drains the "scriptmsg" work bench, emitting each queued message's keys as events. + * @returns {boolean} Always true, so the background worker keeps looping. + */ + 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 = { + /** + * Register a listener that fires every time the named message event is emitted. + * @param {string} name - Event name to listen for (may be omitted to match all events, in which case pass the callback first). + * @param {function} callback - Handler invoked with the emitted message payload. + * @returns {Hook} The created hook (has `name`, `callback`, `id` and `__callback` properties). + */ + on: myEvents.on, + /** + * Remove every listener previously registered for the given callback. + * @param {string} name - Event name the listener was registered under (unused for matching; callback identity is used). + * @param {function} callback - The same callback reference that was passed to `on`/`once`. + * @returns {void} + */ + off: myEvents.off, + /** + * Register a listener that fires only once for the named message event, then removes itself. + * @param {string} name - Event name to listen for (may be omitted to match all events, in which case pass the callback first). + * @param {function} callback - Handler invoked with the emitted message payload. + * @returns {void} + */ + once: myEvents.once, + /** + * Broadcast a message to all other running scripts/threads. + * @param {string | object} what - Payload to broadcast; objects are keyed by event name for the receiving `on` listeners. + * @returns {void} + */ + send: what => scriptBroadcast(what) + }; })(module, require); diff --git a/d2bs/kolbot/libs/modules/Override.d.ts b/d2bs/kolbot/libs/modules/Override.d.ts new file mode 100644 index 000000000..62f0bbd19 --- /dev/null +++ b/d2bs/kolbot/libs/modules/Override.d.ts @@ -0,0 +1,28 @@ +// Hand-written types for the tsc-emitted UMD in Override.js: its wrapper does +// `var v = factory(require, exports); if (v !== undefined) module.exports = v;` and the +// factory returns nothing, so TS types the whole module `void` and `.Override` becomes +// unresolvable at require() sites. A sibling .d.ts wins CJS resolution and restores typing. + +type OverriddenFn = (...args: unknown[]) => unknown; + +/** + * Replaces `target[key]` with a wrapper that invokes `method`, passing the original + * function (bound to `this`) as the first argument, followed by the call's own arguments. + */ +export class Override { + constructor( + target: T, + /** The original member function itself, or its key on `target`. */ + original: OverriddenFn | string, + method: (original: OverriddenFn | undefined, ...args: unknown[]) => unknown, + ); + target: T; + original: OverriddenFn | undefined; + key: string | undefined; + method: (original: OverriddenFn | undefined, ...args: unknown[]) => unknown; + /** Installs the wrapper onto `target[key]`. */ + apply(): void; + /** Restores the original member (or deletes the key when there was none). */ + rollback(): void; + static all: Override[]; +} diff --git a/d2bs/kolbot/libs/modules/Override.js b/d2bs/kolbot/libs/modules/Override.js index 030b35042..23cda9358 100644 --- a/d2bs/kolbot/libs/modules/Override.js +++ b/d2bs/kolbot/libs/modules/Override.js @@ -1,59 +1,72 @@ +/* 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 () { + /** + * @param {object} target object whose method is being overridden + * @param {string | Function} original method name on `target`, or the current function reference + * to look up its key on `target` + * @param {Function} method replacement implementation; called as `method(original, ...args)` + */ + 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); + } + /** + * Installs the override, replacing `target[key]` with a wrapper that forwards to `method`. + */ + 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)); + }; + }; + /** + * Restores `target[key]` to its original value, or deletes it if there was no original. + */ + 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/Override.ts b/d2bs/kolbot/libs/modules/Override.ts deleted file mode 100644 index 75a425147..000000000 --- a/d2bs/kolbot/libs/modules/Override.ts +++ /dev/null @@ -1,39 +0,0 @@ -export class Override any)> { - - public readonly target: T; - public readonly key: string|number|symbol; - public readonly original?: M; - public readonly method; - - static readonly all: Override any>[] = []; - - constructor(target: T, original: M|keyof T, method: (this: T, original: M, ...args: any) => any) { - this.target = target; - if (typeof original !== 'string') { - this.original = original as M; - this.key = Object.keys(target).find(key => target[key] === original); - } else { - this.original = undefined; - this.key = original as keyof T; - } - this.method = method; - - Override.all.push(this); - } - - apply() { - const {target, key, method, original} = this; - target[key] = function (...args) { - return method.apply(this, [original && original.bind(this), ...args]); - }; - } - - rollback() { - const {target, key, original} = this; - if (original) { - target[key] = original; - } else { - delete target[key]; - } - } -} \ No newline at end of file diff --git a/d2bs/kolbot/libs/modules/Promise.js b/d2bs/kolbot/libs/modules/Promise.js index 61aa0282a..5e5ac1e0a 100644 --- a/d2bs/kolbot/libs/modules/Promise.js +++ b/d2bs/kolbot/libs/modules/Promise.js @@ -5,95 +5,113 @@ */ (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 + /** @returns {Promise|unknown} the resolved value once stopped, otherwise this Promise instance */ + this.valueOf = () => self.stopped ? self.value : self; + + /** + * @param {function(unknown): unknown} handler + * @returns {Promise} this Promise, for chaining + */ + this.then = function (handler) { + typeof self._after !== "undefined" && (self._after = []); + self._after.push(handler); + + return self; + }; + + /** + * @param {function(unknown): unknown} handler + * @returns {Promise} this Promise, for chaining + */ + this.catch = function (handler) { + typeof self._catchers !== "undefined" && (self._catchers = []); + self._catchers.push(handler); + + return self; + }; + + /** + * @param {function(unknown): unknown} handler + * @returns {Promise} this Promise, for chaining + */ + 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(); + } + }; + + /** + * @description resolves once every promise in the array has stopped (settled), with the array itself. + * @param {Promise[]} promises + * @returns {Promise} + */ + 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..9d9d77e79 100644 --- a/d2bs/kolbot/libs/modules/Socket.js +++ b/d2bs/kolbot/libs/modules/Socket.js @@ -4,54 +4,67 @@ */ +/** @param {typeof Socket} buildinSock the engine Socket class, captured before this wrapper shadows the name */ (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; + /** + * Opens the underlying engine socket and marks the connection as active. + * @returns {Socket | false} This wrapper instance once connected, or false if the engine + * refused to open the socket (missing args or a non-whitelisted host). + */ + 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); + }; + + /** + * Drains any pending data off the socket and emits a "data" event; closes on read failure. + */ + 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); + }; + + /** + * Sends data on the socket; closes the socket on a send failure. + * @param {string} 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..7858035db 100644 --- a/d2bs/kolbot/libs/modules/Team.js +++ b/d2bs/kolbot/libs/modules/Team.js @@ -5,164 +5,244 @@ */ !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()); +/** + * @param {boolean} threadType + * @param {typeof globalThis} global + */ +(function (threadType, global) { + const others = []; + + const myEvents = new (require("./AsyncEvents")); + const Worker = require("Worker"); + const Messaging = require("Messaging"); + /** + * Default copydata channel/mode used by every Team message when a caller does + * not supply an explicit `mode`. The `mode` argument on send/broadcast selects + * which copydata channel the payload is delivered on: subscribers registered + * via `Team.on(mode, cb)` only receive emits whose numeric mode matches, so + * `mode` acts as a private routing key. A falsy `mode` (0, undefined) falls + * back to this default channel. + * @type {number} + */ + const defaultCopyDataMode = 0xC0FFFEE; + + const Team = { + /** + * Register a listener for a Team event. The event name may be a copydata + * `mode` (number) or a data-item key (string); when only a callback is + * passed it listens to all events. + * @param {string | number | function} name - Event name/mode, or the callback if listening to everything. + * @param {function} [callback] - Handler invoked with the emitted data. + * @returns {{ name: (string | number), callback: function, id: number, __callback: function }} The created hook. + */ + on: myEvents.on, + /** + * Remove every listener previously registered with the given callback. + * @param {string | number} name - Event name/mode (unused for matching, kept for API symmetry). + * @param {function} callback - The original callback to unregister. + * @returns {void} + */ + off: myEvents.off, + /** + * Register a one-shot listener that is removed after its first invocation. + * @param {string | number | function} name - Event name/mode, or the callback if listening to everything. + * @param {function} [callback] - Handler invoked once with the emitted data. + * @returns {void} + */ + once: myEvents.once, + /** + * Send a message to a single profile over copydata. Stamps the payload with + * this client's profile name before sending. + * @param {string} who - Target profile/window title to deliver to. + * @param {object} what - Payload object; mutated to include `profile`. + * @param {number} [mode] - Copydata channel to send on; defaults to defaultCopyDataMode when falsy. + * @returns {boolean} Result of the underlying sendCopyData call. + */ + send: function (who, what, mode = defaultCopyDataMode) { + what.profile = me.windowtitle; + return sendCopyData(null, who, mode || defaultCopyDataMode, JSON.stringify(what)); + }, + /** + * Send a message to every known profile (all clients tracked in `others`). + * Stamps the payload with this client's profile name before sending. + * @param {object} what - Payload object; mutated to include `profile`. + * @param {number} [mode] - Copydata channel to send on; defaults to defaultCopyDataMode when falsy. + * @returns {void} + */ + broadcast: (what, mode) => { + what.profile = me.windowtitle; + return others + .forEach(other => sendCopyData(null, other.profile, mode || defaultCopyDataMode, JSON.stringify(what))); + }, + /** + * Send a message only to known profiles that share the current game party. + * Stamps the payload with this client's profile name before sending. + * @param {object} what - Payload object; mutated to include `profile`. + * @param {number} [mode] - Copydata channel to send on; defaults to defaultCopyDataMode when falsy. + * @returns {void} + */ + 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 + /** + * Background worker tick: every ~3.5s refreshes other profiles' data from disk, and always + * drains the copydata workBench, forwarding parsed emits to the Team message thread. + * @returns {boolean} always true, to keep the background worker looping + */ + 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 + global["main"] = function () { + while (!quiting) delay(3); + //@ts-ignore + getScript(true).stop(); + }; + } else { + /** + * Export the Team API to consumers running outside the copydata thread. + * The event methods (on/off/once) stay wired to the local AsyncEvents + * instance, while the messaging methods (send/broadcast/broadcastInGame) + * are replaced with RPC proxies that forward the call to the Team thread + * via Messaging.send. Also subscribes to inbound emits and re-fires them + * on the local event bus, keyed by copydata `mode` and by data-item key. + * @param {{ exports: object }} module - The CommonJS module wrapper. + * @returns {void} + */ + (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 + /** + * Convenience reply sent back to the emitting profile via the local Team send. + * @param {object} what - Payload object; mutated to include `profile`. + * @param {number} [mode] - Copydata channel to send on. + * @returns {boolean} Result of the underlying sendCopyData call. + */ + 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..d1f7c5d4e --- /dev/null +++ b/d2bs/kolbot/libs/modules/UnitInfo.js @@ -0,0 +1,260 @@ +/** +* @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; + }; + + /** + * Removes all hooks drawn for the current unit and clears tracking state. + */ + 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..15b1fab45 100644 --- a/d2bs/kolbot/libs/modules/Worker.js +++ b/d2bs/kolbot/libs/modules/Worker.js @@ -3,104 +3,177 @@ * @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; + + /** + * @param {Function|Array<*>} newWork - a callback (invoked with the Worker) or nested array of work + * @returns {number} the new length of the work queue + */ + this.push = function (newWork) { + return work.push(newWork); + }; + + /** + * @param {Function|Array<*>} newWork - a callback (invoked with the Worker) or nested array of work + * @returns {number} the new length of the low-priority work queue + */ + 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"); + } + }; + + /** + * Drains and runs all queued high-priority work; no-op while {@link this.workDisabled}. + */ + this.check = function () { + return checker(work); + }; + + /** + * Drains and runs all queued low-priority work; no-op while {@link this.workDisabled}. + */ + this.checkLowPrio = function () { + return checker(workLowPrio); + }; + + /** + * Runs a work item: invokes a function with the Worker, or recurses into an array of items. + * @param {Function|Array<*>} what + * @returns {unknown} the callback's return value when `what` is a function; otherwise undefined/false + */ + 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: {} }, { + /** + * Registers a named background process and immediately queues it on the low-prio queue; + * the process re-queues itself each low-prio check as long as `callback` keeps returning truthy. + * @param {{ processes: Object }} target + * @param {string} name + * @param {function(): boolean} callback + * @returns {boolean} + */ + 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; + }, + /** + * @param {{ processes: Object }} target + * @param {string} name + * @returns {boolean} + */ + deleteProperty: function (target, name) { + if (target.processes.hasOwnProperty(name)) { + target.processes[name].running = false; + delete target.processes[name]; + } + return true; + } + }); + + /** @param {string} name */ + 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 + /** + * @param {number} [amount] + * @returns {boolean} always true + */ + 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..0d5d7a8bd --- /dev/null +++ b/d2bs/kolbot/libs/modules/external/base64.js @@ -0,0 +1,58 @@ +/** + * 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..282852e8a 100644 --- a/d2bs/kolbot/libs/modules/sdk.js +++ b/d2bs/kolbot/libs/modules/sdk.js @@ -3,4880 +3,5580 @@ * @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: { + /** + * @param {number} difficulty sdk.difficulty.* value + * @returns {string | false} difficulty name, or false if out of range + */ + 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, -(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 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, - difficulty: { - Normal: 0, - Nightmare: 1, - Hell: 2, - Difficulties: ["Normal", "Nightmare", "Hell"], + // 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, - nameOf: function (difficulty) { - if (difficulty === undefined || typeof difficulty !== "number") return false; - if (difficulty < 0 || difficulty > 2) return false; - return ["Normal", "Nightmare", "Hell"][difficulty]; - } - }, + // 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, - party: { - NoParty: 65535 - }, + // Act 4 + PandemoniumFortress: 103, + OuterSteppes: 104, + PlainsofDespair: 105, + CityoftheDamned: 106, + RiverofFlame: 107, + ChaosSanctuary: 108, - 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, - }, + // 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, - 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, - }, + // Ubers + MatronsDen: 133, + ForgottenSands: 134, + FurnaceofPain: 135, + UberTristram: 136, - areas: { - Towns: [1, 40, 75, 103, 109], - None: 0, + /** + * @param {number} area sdk.areas.* area id + * @returns {number} act number (1-5) containing the area + */ + 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; + } + }, - // 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, + /** + * @param {number} area sdk.areas.* area id + * @returns {number} sdk.areas.* id of the town belonging to that area's act + */ + 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 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, + /** + * @param {number} act act number (1-5) + * @returns {number} sdk.areas.* id of that act's town + */ + 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 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, + 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 4 - PandemoniumFortress: 103, - OuterSteppes: 104, - PlainsofDespair: 105, - CityoftheDamned: 106, - RiverofFlame: 107, - ChaosSanctuary: 108, + // 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 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, + // 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, - // Ubers - MatronsDen: 133, - ForgottenSands: 134, - FurnaceofPain: 135, - UberTristram: 136, + // 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, - 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; - } - }, + // 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, - 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; - } - }, + // 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, - 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; - } - } - }, + // General stuff + IdentifyScroll: 217, + BookofIdentify: 218, + TownPortalScroll: 219, + BookofTownPortal: 220, - 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, + // 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, - // 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, + // 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 - 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, + // Sorc + Fire: 8, + Lightning: 9, + Cold: 10, - // 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, + // Necro + Curses: 16, + PoisonandBone: 17, + NecroSummoning: 18, - // 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, + // Pala + PalaCombat: 24, + Offensive: 25, + Defensive: 26, - // 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, + // Barb + BarbCombat: 32, + Masteries: 33, + Warcries: 34, - // General stuff - IdentifyScroll: 217, - BookofIdentify: 218, - TownPortalScroll: 219, - BookofTownPortal: 220, + // Druid + DruidSummon: 40, + ShapeShifting: 41, + Elemental: 42, - // 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 + Traps: 48, + ShadowDisciplines: 49, + MartialArts: 50, + } + }, + skillTabs: undefined, - // 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, + 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, - // Sorc - Fire: 8, - Lightning: 9, - Cold: 10, + // act 2 + ViperAmuletChest: 149, + HoradricStaffHolder: 152, + HoradricCubeChest: 354, + HoradricScrollChest: 355, + ShaftoftheHoradricStaffChest: 356, + Journal: 357, - // Necro - Curses: 16, - PoisonandBone: 17, - NecroSummoning: 18, + // act 3 + ForestAltar: 81, + LamEsensTomeHolder: 193, + GidbinnAltar: 252, + KhalimsHeartChest: 405, + KhalimsBrainChest: 406, + KhalimsEyeChest: 407, - // Pala - PalaCombat: 24, - Offensive: 25, - Defensive: 26, + // act 4 + HellForge: 376, - // Barb - BarbCombat: 32, - Masteries: 33, - Warcries: 34, + // 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, + } + }, - // Druid - DruidSummon: 40, - ShapeShifting: 41, - Elemental: 42, + // 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 + }, - // Assa - Traps: 48, - ShadowDisciplines: 49, - MartialArts: 50, - } - }, - skillTabs: undefined, + 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, + }, - 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, + // 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 + }, - // act 2 - ViperAmuletChest: 149, - HoradricStaffHolder: 152, - HoradricCubeChest: 354, - HoradricScrollChest: 355, - ShaftoftheHoradricStaffChest: 356, - Journal: 357, + // 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 3 - ForestAltar: 81, - LamEsensTomeHolder: 193, - GidbinnAltar: 252, - KhalimsHeartChest: 405, - KhalimsBrainChest: 406, - KhalimsEyeChest: 407, + 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 4 - HellForge: 376, + // unit stats + stats: { + StunLength: 66, + VelocityPercent: 67, + OtherAnimrate: 69, + HpRegen: 74, - // 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, - } - }, + LastBlockFrame: 95, + State: 98, + MonsterPlayerCount: 100, - // 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 - }, + CurseResistance: 109, + IronMaidenLevel: 129, + LifeTapLevel: 130, - 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, - }, + Alignment: 172, + Target0: 173, + Target1: 174, + GoldLost: 175, + MinimumRequiredLevel: 176, + ConversionLevel: 176, + ConversionMaxHp: 177, + UnitDooverlay: 178, + AttackVsMontype: 179, + DamageVsMontype: 180, - // 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 - }, + 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, - // 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, - }, + ModifierListSkill: 350, + ModifierListLevel: 351, - 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, - }, + LastSentHpPct: 352, + SourceUnitType: 353, + SourceUnitId: 354, - // unit stats - stats: { - StunLength: 66, - VelocityPercent: 67, - OtherAnimrate: 69, - HpRegen: 74, + 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, - LastBlockFrame: 95, - State: 98, - MonsterPlayerCount: 100, + 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, - CurseResistance: 109, - IronMaidenLevel: 129, - LifeTapLevel: 130, + // doesn't exist but define for prototypes + AllRes: 555, + }, - Alignment: 172, - Target0: 173, - Target1: 174, - GoldLost: 175, - MinimumRequiredLevel: 176, - ConversionLevel: 176, - ConversionMaxHp: 177, - UnitDooverlay: 178, - AttackVsMontype: 179, - DamageVsMontype: 180, + // unit info + unittype: { + Player: 0, + NPC: 1, + Monster: 1, + Object: 2, + Missile: 3, + Item: 4, + Stairs: 5, // ToDo: might be more as stairs + }, - 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, + 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, - ModifierListSkill: 350, - ModifierListLevel: 351, + /** @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]; + } + } + }, - LastSentHpPct: 352, - SourceUnitType: 353, - SourceUnitId: 354, + 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 + }, - 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, + 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, + }, - 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, + objects: { + mode: { + Inactive: 0, + Interacted: 1, + Active: 2, + }, - // doesn't exist but define for prototypes - AllRes: 555, - }, + 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 + ], - // unit info - unittype: { - Player: 0, - NPC: 1, - Monster: 1, - Object: 2, - Missile: 3, - Item: 4, - Stairs: 5, // ToDo: might be more as stairs - }, + // 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, - 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 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, - 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 3 + A3Waypoint: 237, + ForestAltar: 81, + LamEsensTome: 193, + SewerStairsA3: 366, + SewerLever: 367, + DuranceEntryStairs: 386, + RedPortalToAct4: 342, + CompellingOrb: 404, - 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 4 + A4Waypoint: 398, + SealGlow: 131, + DiabloStar: 255, + DiabloSealInfector: 392, + DiabloSealInfector2: 393, + DiabloSealSeis: 394, + DiabloSealVizier: 396, + DiabloSealVizier2: 395, + RedPortalToAct5: 566, // The one of tyreal - 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, - }, + // 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, - objects: { - mode: { - Inactive: 0, - Interacted: 1, - Active: 2, - }, + FrigidHighlandsChest: 455, + IcyCellarChest: 397, - // act1 - A1TownFire: 39, - A1Waypoint: 119, - StoneAlpha: 17, - StoneBeta: 18, - StoneGamma: 19, - StoneDelta: 20, - StoneLambda: 21, - StoneTheta: 22, - CainsJail: 26, - InifussTree: 30, - Malus: 108, + SmallSparklyChest: 397, + LargeSparklyChest: 455, + SuperChest: 580, - // 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, + // misc + BubblingPoolofBlood: 82, + HornShrine: 83, + Stash: 267, + BluePortal: 59, + RedPortal: 60, + Smoke: 401, + Well: 132, + Wells: [130, 132, 322], + }, - // act 3 - A3Waypoint: 237, - ForestAltar: 81, - LamEsensTome: 193, - SewerStairsA3: 366, - SewerLever: 367, - DuranceEntryStairs: 386, - RedPortalToAct4: 342, - CompellingOrb: 404, + 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 4 - A4Waypoint: 398, - SealGlow: 131, - DiabloStar: 255, - DiabloSealInfector: 392, - DiabloSealInfector2: 393, - DiabloSealSeis: 394, - DiabloSealVizier: 396, - DiabloSealVizier2: 395, - RedPortalToAct5: 566, // The one of tyreal + // 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 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 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, - FrigidHighlandsChest: 455, - IcyCellarChest: 397, + // 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, - SmallSparklyChest: 397, - LargeSparklyChest: 455, - SuperChest: 580, + // 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, - // misc - BubblingPoolofBlood: 82, - HornShrine: 83, - Stash: 267, - BluePortal: 59, - RedPortal: 60, - Smoke: 401, - }, + // 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, - 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, + // 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, - // 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, + // 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 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, + // Misc? + Youngdiablo: 368, + Left: 525, + Life: 426, + Effect: 574, + Pet: 414, + Prince: 249, + POW: 534, + Right: 524, + Sage: 424, + Town: 514, + Cow: 179, + }, - // 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, + 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, + }, - // 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, + 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 + }, - // 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, + Rogue: 271, + Guard: 338, + IronWolf: 359, + A5Barb: 561, + }, - // 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, + missiles: { + DiabloLightning: 172, + FissureCrack1: 462, + FissureCrack2: 463, + }, - // 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, + storage: { + Equipped: 1, + Belt: 2, + Inventory: 3, + TradeWindow: 5, + Cube: 6, + Stash: 7, + }, - // Misc? - Youngdiablo: 368, - Left: 525, - Life: 426, - Effect: 574, - Pet: 414, - Prince: 249, - POW: 534, - Right: 524, - Sage: 424, - Town: 514, - Cow: 179, - }, + node: { + NotOnPlayer: 0, + Storage: 1, + Belt: 2, + Equipped: 3, + Cursor: 4, + }, - 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, - }, + // 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 + }, - 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 - }, + 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, - ClayGolem: 289, - Dopplezon: 356, - Valkyrie: 357, - FireGolem: 292, - IronGolem: 291, - NecroMage: 364, - NecroSkeleton: 363, - Poppy: 425, - Wolverine: 423, - }, + // 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, - 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 - }, + 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, + }, + }, + }, - Rogue: 271, - Guard: 338, - IronWolf: 359, - A5Barb: 561, - }, + // 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, - storage: { - Equipped: 1, - Belt: 2, - Inventory: 3, - TradeWindow: 5, - Cube: 6, - Stash: 7, - }, + // 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, - node: { - NotOnPlayer: 0, - Storage: 1, - Belt: 2, - Equipped: 3, - Cursor: 4, - }, + // 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, - // 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 - }, + // Uniques + // Quest/Misc + KeyofTerror: 11146, + KeyofHate: 11147, + KeyofDestruction: 11148, + DiablosHorn: 11149, + BaalsEye: 11150, + MephistosBrain: 11151, + StandardofHeroes: 11152, + HellfireTorch: 11153, + Annihilus: 21743, - 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, + // 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, - // 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, + // 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 + }, - 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, - }, - }, - }, + 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, + }, - // 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, + 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, - // 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, + // 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, - // 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 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, - // Uniques - // Quest/Misc - KeyofTerror: 11146, - KeyofHate: 11147, - KeyofDestruction: 11148, - DiablosHorn: 11149, - BaalsEye: 11150, - MephistosBrain: 11151, - StandardofHeroes: 11152, - HellfireTorch: 11153, - Annihilus: 21743, + // Act 4 + PandemoniumFortress: 790, + OuterSteppes: 792, + PlainsofDespair: 793, + CityoftheDamned: 794, + RiverofFlame: 791, + ChaosSanctuary: 789, - // 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 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, - // 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 - }, + // Ubers + MatronsDen: 5389, + ForgottenSands: 5389, + FurnaceofPain: 5389, + UberTristram: 5018, + }, + }, - 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, - }, - }, + 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, - }, + // me.charflags bits (D2ClientSaveFlags) - same layout as the .d2s status byte and the realm statstring flags + charflags: { + Init: 0x01, + Hardcore: 0x04, + Dead: 0x08, + Expansion: 0x20, + Ladder: 0x40, + }, - // 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..ad1e09ed5 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/Advertise.js @@ -0,0 +1,46 @@ +/** +* @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 + /** @returns {boolean} always true; says the advertise message to chat once the interval elapses */ + 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"); + } +// eslint-disable-next-line no-undef -- typeof-guarded probe: short-circuit means the bare Worker is never evaluated +})(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..aefd89c6f --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/AntiIdle.js @@ -0,0 +1,36 @@ +/** +* @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"); + } +// eslint-disable-next-line no-undef -- typeof-guarded probe: short-circuit means the bare Worker is never evaluated +})(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..a820578cf --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/Guard.js @@ -0,0 +1,110 @@ +/** + * @param {boolean} thread + * @param {typeof globalThis} global + */ +(function (module, require, thread, global) { + "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); + /** + * Refreshes this line's text and visibility from the callback / current UI state. + */ + 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); + } + + /** + * Reparses the latest received stack trace and refreshes each hook line with it. + * @returns {boolean} always true (worker callback convention to keep re-queueing) + */ + 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 + global["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..3e4eabf23 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/SimpleParty.js @@ -0,0 +1,246 @@ +(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 = {}; + + /** + * @returns {number} The partyid with the most members among visible party entries (excluding NO_PARTY + * when any real party exists), or -1 on error / if no entries are found. + */ + SimpleParty.biggestPartyId = function () { + let uniqueParties = []; + try { + // Or add it and return the value + for (let party = getParty(); party.getNext();) { + // Find this party + let partyEntry = uniqueParties.find(u => u.partyid === party.partyid); + if (!partyEntry) { + // Or create an instance of it + uniqueParties.push({ + partyid: party.partyid, + used: 0 + }); + partyEntry = uniqueParties[uniqueParties.length - 1]; + } + // Once we have the party object, increase field used + partyEntry.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; + } + }; + + /** + * Deterministically picks one name (by md5 hash order) among the acceptable, non-self invites so + * that all bots converge on accepting the same invitation first. + * @returns {string|undefined} + */ + 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(); + }; + + /** @returns {Party|undefined} The first other party member sharing our partyid, if any. */ + 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; + }; + + /** + * @param {string} name + * @returns {boolean} True if a matching, invitable party entry was found and clicked. + */ + 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..44f82b486 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/TownChicken.js @@ -0,0 +1,407 @@ +/** +* @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 - " + e.message); + } + } + + 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 - " + e.message); + } + } + Config.PublicMode && Pather.makePortal(); + console.log("ÿc8End ÿc0:: ÿc8visitTown - currentArea: " + getAreaName(me.area)); + + return me.area === preArea; + }; + + let townCheck = false; + + /** + * Stubbed override of Misc.townCheck for this thread; town-visit triggering is driven by the + * `townCheck` closure flag instead. + * @returns {boolean} always false. + */ + Misc.townCheck = function () { + return false; + }; + + let waitTick = getTickCount(); + let potTick = getTickCount(); + let _recursion = false; + + // Start + /** + * Background worker tick: chickens to town when HP/MP drops below the configured thresholds, needed + * potions/keys are missing (with Config.TownCheck), or `townCheck` was externally flagged. + * @returns {boolean} false only when visitTown throws and the profile is being quit. + */ + 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"); + } +// eslint-disable-next-line no-undef -- typeof-guarded probe: short-circuit means the bare Worker is never evaluated +})(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..4aae23cd8 --- /dev/null +++ b/d2bs/kolbot/libs/modules/workers/WpWatcher.js @@ -0,0 +1,45 @@ +/** +* @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 + /** + * Waits for the waypoint UI to open, caches all waypoints once, and broadcasts them to other scripts. + * @returns {boolean} always true, to keep the background worker looping + */ + 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"); + } +// eslint-disable-next-line no-undef -- typeof-guarded probe: short-circuit means the bare Worker is never evaluated +})(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..c7099fc4a --- /dev/null +++ b/d2bs/kolbot/libs/oog/D2Bot.js @@ -0,0 +1,330 @@ +/** +* @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: "", + + /** + * Picks up the D2Bot# window handle from the profile's datafile and the current script's + * entry (.dbj) filename. + * @returns {number} The D2Bot# window handle (0 if not yet known). + */ + 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; + }, + + /** + * @param {number | string} handle + * @param {number} mode + * @param {string} msg + */ + sendMessage: function (handle, mode, msg) { + sendCopyData(null, handle, mode, msg); + }, + + /** + * @param {string} msg + * @param {number} [color] + * @param {string} [tooltip] + * @param {string} [trigger] + */ + 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(); + }, + + /** @param {D2BotItemLogPayload} itemObj */ + printToItemLog: function (itemObj) { + new CopyData().data("printToItemLog", [itemObj]).send(); + }, + + /** @param {D2BotItemLogPayload} itemObj */ + uploadItem: function (itemObj) { + new CopyData().data("uploadItem", [itemObj]).send(); + }, + + /** + * @param {string} filename + * @param {string} msg + */ + writeToFile: function (filename, msg) { + new CopyData().data("writeToFile", [filename, msg]).send(); + }, + + /** + * @param {string} ircProfile + * @param {string} recepient + * @param {string} msg + */ + postToIRC: function (ircProfile, recepient, msg) { + new CopyData().data("postToIRC", [ircProfile, recepient, msg]).send(); + }, + + /** @param {boolean} mode */ + ircEvent: function (mode) { + new CopyData().data("ircEvent", [mode ? "true" : "false"]).send(); + }, + + /** @param {string} msg */ + notify: function (msg) { + new CopyData().data("notify", [msg]).send(); + }, + + /** @param {D2BotItemLogPayload} itemObj */ + saveItem: function (itemObj) { + new CopyData().data("saveItem", [itemObj]).send(); + }, + + /** @param {string} msg */ + updateStatus: function (msg) { + new CopyData().data("updateStatus", [msg]).send(); + }, + + /** + * Signals D2Bot# to refresh the runs counter display for this profile. + */ + updateRuns: function () { + new CopyData().data("updateRuns", []).send(); + }, + + /** + * Signals D2Bot# to refresh the chickens counter display for this profile. + */ + updateChickens: function () { + new CopyData().data("updateChickens", []).send(); + }, + + /** + * Signals D2Bot# to refresh the deaths counter display for this profile. + */ + updateDeaths: function () { + new CopyData().data("updateDeaths", []).send(); + }, + + /** + * Requests the current game info snapshot from D2Bot#. + */ + requestGameInfo: function () { + new CopyData().data("requestGameInfo", []).send(); + }, + + /** @param {boolean} [keySwap] */ + restart: function (keySwap) { + new CopyData().data( + "restartProfile", + arguments.length > 0 ? [me.profile, keySwap] : [me.profile] + ).send(); + }, + + /** + * Notifies D2Bot# that the CD key is already in use by another instance. + */ + CDKeyInUse: function () { + new CopyData().data("CDKeyInUse", []).send(); + }, + + /** + * Notifies D2Bot# that the CD key has been disabled. + */ + CDKeyDisabled: function () { + new CopyData().data("CDKeyDisabled", []).send(); + }, + + /** + * Notifies D2Bot# of a realm-down condition ("CD Key Realm Down") for the current CD key. + */ + CDKeyRD: function () { + new CopyData().data("CDKeyRD", []).send(); + }, + + /** + * profile is meant to be the profile name, but several call sites pass the release flag + * positionally instead (D2Bot.stop(true)). + * @param {string | boolean} [profile] + * @param {boolean} [release] + */ + stop: function (profile, release) { + !profile && (profile = me.profile); + + new CopyData().data("stop", [profile, release ? "True" : "False"]).send(); + }, + + /** @param {string} profile */ + start: function (profile) { + new CopyData().data("start", [profile]).send(); + }, + + /** @param {string} profile */ + startSchedule: function (profile) { + new CopyData().data("startSchedule", [profile]).send(); + }, + + /** @param {string} profile */ + stopSchedule: function (profile) { + new CopyData().data("stopSchedule", [profile]).send(); + }, + + /** + * Signals D2Bot# to increment the run count for the current profile. + */ + updateCount: function () { + new CopyData().data("updateCount", ["1"]).send(); + }, + + /** + * @param {string} msg + * @param {number} mode + */ + shoutGlobal: function (msg, mode) { + new CopyData().data("shoutGlobal", [msg, mode]).send(); + }, + + /** + * Sends a heartbeat ping to D2Bot# on the dedicated 0xbbbb mode. + */ + heartBeat: function () { + new CopyData().mode(0xbbbb).data("heartBeat", []).send(); + }, + + /** + * @param {number} wparam + * @param {number} lparam + */ + sendWinMsg: function (wparam, lparam) { + new CopyData().data("winmsg", [wparam, lparam]).send(); + }, + + /** + * Sends the raw window message sequence D2Bot# uses to detect that the client is in a game. + */ + 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)); + }, + + /** @param {string} profile */ + requestGame: function (profile) { + new CopyData().handle(profile).mode(3).send(); + }, + + /** + * Requests the current profile object from D2Bot#. + */ + getProfile: function () { + new CopyData().data("getProfile", []).send(); + }, + + /** + * @param {string} account + * @param {string} password + * @param {string} character + * @param {string} difficulty + * @param {string} realm + * @param {string} infoTag + * @param {string} gamePath + */ + setProfile: function (account, password, character, difficulty, realm, infoTag, gamePath) { + new CopyData().data( + "setProfile", + [account, password, character, difficulty, realm, infoTag, gamePath] + ).send(); + }, + + /** @param {string} tag */ + setTag: function (tag) { + new CopyData().data("setTag", [tag]).send(); + }, + + // Store info in d2bot# cache + /** @param {unknown} info Arbitrary payload cached by D2Bot# keyed to the current profile. */ + store: function (info) { + this.remove(); + + new CopyData().data("store", [me.profile, info]).send(); + }, + + // Get info from d2bot# cache + /** + * Requests the cached info for the current profile from D2Bot#. + */ + retrieve: function () { + new CopyData().data("retrieve", [me.profile]).send(); + }, + + // Delete info from d2bot# cache + /** + * Deletes the cached info for the current profile from D2Bot#. + */ + 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..fadae3bf9 --- /dev/null +++ b/d2bs/kolbot/libs/oog/DataFile.js @@ -0,0 +1,182 @@ +/** +* @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: "" + }, + + /** + * Creates the datafile if it does not already exist. + * @returns {boolean} True if a new datafile was created. + */ + init: function () { + if (!FileTools.exists(this._path)) { + this.create(); + + return true; + } + return false; + }, + + /** @returns {DataFileObj} The freshly written default datafile. */ + 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; + } + }, + + /** + * Reads the current datafile from disk, recreating it from defaults if the JSON is corrupted. + * @returns {DataFileObj} + */ + 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; + }, + + /** @returns {DataFileObj} A cloned copy of the current datafile contents. */ + getStats: function () { + let obj = this.getObj(); + return clone(obj); + }, + + /** + * @param {string | string[]} arg Stat name(s) to recompute/update. + * @param {string | number} [value] Value to assign for stats without a computed rule. + */ + 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)) { + break; + } + + 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..1c6c65c9f --- /dev/null +++ b/d2bs/kolbot/libs/oog/FileAction.js @@ -0,0 +1,118 @@ +/** +* @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 + */ + +/** @type {IFileAction} */ +const FileAction = { + /** + * Retries up to 30 times (incremental delay) since the file may be mid-write by another script. + * @param {string} path + * @returns {string} + */ + 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; + }, + + /** + * Retries up to 30 times (incremental delay) since the file may be locked by another script. + * @param {string} path + * @param {string} msg + * @returns {boolean} + */ + 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; + }, + + /** + * Retries up to 30 times (incremental delay) since the file may be locked by another script. + * @param {string} path + * @param {string} msg + * @returns {boolean} + */ + 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; + }, + + /** + * @param {string} path + * @returns {unknown} the parsed JSON contents; shape depends on which file is read + */ + 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..9776caaca --- /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.d.ts b/d2bs/kolbot/libs/oog/ShitList.d.ts new file mode 100644 index 000000000..083750218 --- /dev/null +++ b/d2bs/kolbot/libs/oog/ShitList.d.ts @@ -0,0 +1,24 @@ +export {}; +declare global { + interface ShitListData { + shitlist: string[]; + } + + // Value shape of the global `ShitList` const in ShitList.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + // Named `ShitListInstance` (not `ShitList`) because sdk/types/Config.d.ts already + // declares a boolean `ShitList` member on `Config`, and reusing the name would confuse readers. + interface ShitListInstance { + _default: ShitListData; + _path: string; + _list: Set; + + create(): ShitListData; + reset(): ShitListData; + getObj(): ShitListData; + read(): string[]; + add(name: string): void; + remove(name: string): boolean; + has(name: string): boolean; + } +} diff --git a/d2bs/kolbot/libs/oog/ShitList.js b/d2bs/kolbot/libs/oog/ShitList.js new file mode 100644 index 000000000..2742b927d --- /dev/null +++ b/d2bs/kolbot/libs/oog/ShitList.js @@ -0,0 +1,110 @@ +/** +* @filename ShitList.js +* @author kolton, D3STROY3R, theBGuy +* @desc Maintain shitlist of griefers +* +*/ + +!isIncluded("Polyfill.js") && include("Polyfill.js"); +includeIfNotIncluded("oog/FileAction.js"); + +/** @type {ShitListInstance} */ +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); + }, + + /** + * Overwrites the shitlist datafile with defaults and clears the in-memory set. + * @returns {{ shitlist: Array }} + */ + 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..b7270183c 100644 --- a/d2bs/kolbot/libs/require.js +++ b/d2bs/kolbot/libs/require.js @@ -1,3 +1,4 @@ +/* eslint-disable @stylistic/max-len */ /* eslint-disable dot-notation */ /** * @filename require.js @@ -10,127 +11,157 @@ // noinspection ThisExpressionReferencesGlobalObjectJS <-- definition of global here typeof global === "undefined" && (this["global"] = this); -global["module"] = {exports: undefined}; +global["module"] = { exports: undefined }; global["exports"] = {}; +/** + * Resolves "." and ".." segments out of a path, normalizing backslashes to forward slashes. + * @param {string} test path to normalize + * @returns {string} normalized path with relative segments collapsed + */ 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"); +/** + * Ensures the calling script is running as its own thread rather than inline, loading it as a + * separate script if it isn't already running. + * @returns {"thread" | "started" | "loaded"} "thread" if already running as this thread, "started" + * if it was just loaded as a new thread, "loaded" if it was already loaded under another thread + */ 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..206ef9b0c --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Andariel.js @@ -0,0 +1,48 @@ +/** +* @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"); + } + + /** @returns {boolean} True once Andariel is confirmed dead, stopping the move loop early. */ + 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..f7a16033e --- /dev/null +++ b/d2bs/kolbot/libs/scripts/AutoBaal.js @@ -0,0 +1,299 @@ +/** +* @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, + /** + * @param {number} area candidate leader's current area + * @returns {boolean} true to abort leader detection once the leader reaches Baal's chamber + */ + 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..5cda4883c --- /dev/null +++ b/d2bs/kolbot/libs/scripts/AutoChaos.js @@ -0,0 +1,727 @@ +/** +* @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; + } + + /** + * Updates the `taxi` target name from config, or auto-detects a candidate if unset/invalid. + */ + function setTaxi() { + if (Config.AutoChaos.Leader !== "" && getParty(Config.AutoChaos.Leader)) { + taxi = Config.AutoChaos.Leader; + } else if (taxi === "" || !getParty(taxi)) { + taxi = detectTaxi(); + } + } + + /** + * Scans the party for a leveled sorceress with a portal to the Chaos Sanctuary. + * @returns {string} name of the detected taxi candidate, or the current taxi if none found + */ + 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; + } + + /** @returns {boolean} true to keep the main loop running, false once the Diablo phase is reached */ + 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 (Object.values(classes).indexOf(true) >= 0) { + throw new Error(notify("Not enough players.")); + } + } + + /** + * Precasts while waiting for the taxi sorceress to reach the Chaos Sanctuary, then follows and returns to town. + */ + 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()); + } + + /** + * Blocks for `Config.AutoChaos.SealDelay` seconds, preattacking in place while not in town. + */ + 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); + } + } + } + + /** + * Picks up items, returns to town, increments the seal-completion counter, and moves to the portal spot. + */ + function clearOut() { + Pickit.pickItems(); + Town.goToTown(); + lastCall += 1; + Town.move("portalspot"); + } + + /** + * Kills the Grand Vizier of Chaos and clears the seal area. + */ + function vizier() { + slayBoss(sdk.locale.monsters.GrandVizierofChaos, Config.AutoChaos.PreAttack[0], 20); + clearOut(); + } + + /** + * Kills Lord De Seis and clears the seal area. + */ + function seis() { + slayBoss(sdk.locale.monsters.LordDeSeis, Config.AutoChaos.PreAttack[1], 30); + clearOut(); + } + + /** + * Kills the Infector of Souls and clears the seal area. + */ + 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(); + } + + /** + * Opens the Seis seal, waits out the seal delay, then kills Lord De Seis and picks up items. + */ + 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(); + } + + /** + * Engages (or leeches off) Diablo, then waits until the party's leveled member count drops + * or the non-taxi party leaves, up to a 180 second timeout. + */ + function diablo() { + let maxTime = 0; + let preCount = 1; + let postCount; + 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; + 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, + /** + * Removes the "diablospawned" listener and the Diablo lights event listener. + */ + 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..872b79576 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Baal.js @@ -0,0 +1,106 @@ +/** +* @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."); + } + + /** @returns {void} Throws a ScriptError to abort if a doll or soul quit condition (Config.Baal) is met. */ + 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..d13f83f39 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BaalAssistant.js @@ -0,0 +1,522 @@ +/** +* @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(); + + /** @returns {boolean} false once a leader is autodetected or tracking is cancelled, else true */ + 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, + /** + * @param {number} area + * @returns {boolean} + */ + quitIf: (area) => [sdk.areas.ThroneofDestruction, sdk.areas.WorldstoneChamber].includes(area) + })) + || (Leader = Misc.autoLeaderDetect({ + destination: sdk.areas.ThroneofDestruction, + /** + * @param {number} area + * @returns {boolean} + */ + 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."); + } + + /** @returns {void} Throws a ScriptError to abort if a DollQuit/SoulQuit trigger monster is found. */ + 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..ab83437b0 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BaalHelper.js @@ -0,0 +1,165 @@ +/** +* @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 = false; + + const chatEvent = function (nick, msg) { + if (nick === Config.Leader) { + if ((Config.BaalHelper.DollQuit && msg === "Dolls found! NG.") + || (Config.BaalHelper.SoulQuit && msg === "Souls found! NG.")) { + quitFlag = true; + } + } + }; + ctx.chatEvent = chatEvent; + + if (Config.BaalHelper.DollQuit || Config.BaalHelper.SoulQuit) { + addEventListener("chatmsg", 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(() => { + if (quitFlag) throw new ScriptError("Burning Souls or Dolls found, ending script."); + 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."); + } + /** + * moveToEx callback - aborts the approach (throws) if the DollQuit/SoulQuit trigger + * monsters appear en route. + * @returns {void} + */ + 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"); + + 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; + }, + { + /** + * Runs helper sub-scripts, town chores, and precast/positioning before the main action. + */ + 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", { + /** @returns {number} Harrogath, unless a fast Chaos-Sanctuary skip is configured */ + 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..818206bda --- /dev/null +++ b/d2bs/kolbot/libs/scripts/BattleOrders.js @@ -0,0 +1,314 @@ +/** +* @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 + }; + + /** @throws {Error} if the game is empty (player count <= 1) */ + function checkForPlayers () { + if (Misc.getPlayerCount() <= 1) throw new Error("Empty game"); + } + + /** @param {string} [msg] */ + function log (msg = "") { + console.log(msg); + me.overhead(msg); + } + + /** @returns {boolean} true if too late for this BO round (or game is empty), false to keep waiting */ + 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 + /** @returns {{ success: boolean, count: number }} whether anyone was bo-ed and how many were in range */ + function giveBO () { + // more players might be showing up, give a moment and lets wait until the nearby player count is static + let nearPlayers; + 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..aecc69329 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ChestMania.js @@ -0,0 +1,53 @@ +/** +* @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], + /** + * Restores Config.OpenChests.Enabled to its pre-run value. + */ + 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..a498e8617 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ClearAnyArea.js @@ -0,0 +1,29 @@ +/** +* @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", { + /** @returns {number} */ + 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..47bf8b543 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/ControlBot.js @@ -0,0 +1,2270 @@ +/** +* @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/RushConstants"); + 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 + }; + }, + + /** @returns {string} formatted "yes: X no: Y undecided: Z" summary */ + 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); + }; + + /** + * Restores the original global say function overridden at script start. + */ + 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; + } + + /** + * Resets the command counter and command-window start tick for this player. + */ + 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(); + }; + + /** + * Clears the ignored state (does not un-squelch the player in party) and resets their command count. + */ + PlayerTracker.prototype.unIgnore = function () { + this.ignored = false; + this.ignoredAt = 0; + this.commands = 0; + }; + + /** @returns {boolean} true if the player's Enchant buff is due to expire soon and should be recast */ + PlayerTracker.prototype.reChant = function () { + return getTickCount() - this.lastChant >= chantDuration - Time.minutes(1); + }; + + /** + * Records the current tick as the time this player was last enchanted. + */ + PlayerTracker.prototype.updateChantTracker = function () { + this.lastChant = getTickCount(); + }; + + /** @constructor */ + function WpTracker () { + this.timer = getTickCount(); + this.requests = 0; + this.singleWpRequests = 0; + } + + /** + * Records a full waypoint-set request, resetting the timer and incrementing the request count. + */ + WpTracker.prototype.update = function () { + this.timer = getTickCount(); + this.requests++; + }; + + /** + * Records a single-waypoint request, resetting the timer and incrementing the single-request count. + */ + WpTracker.prototype.updateSingle = function () { + this.timer = getTickCount(); + this.singleWpRequests++; + }; + + /** @returns {number} milliseconds elapsed since this player's last waypoint request */ + 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.singleWpRequests > 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); + } + } + } + } + } + + + /** + * @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, + /** + * No-op; matched specially by the cancel-command handler before dispatch. + */ + run: () => {} + }); + _actions.set("timeleft", { + desc: "Remaining time for this game", + hostileCheck: false, + /** + * Reports the remaining game time to party chat. + */ + 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, + /** @param {string} nick */ + 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, + /** @param {string} nick */ + 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, + /** @param {string} nick */ + 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..06ce5d9bf --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Crafting.js @@ -0,0 +1,524 @@ +/** +* @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; + 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; + } + + delay(1000); + } + + gameRequest = false; + } + + pickItems(); + Cubing.update(); + Runewords.buildLists(); + Cubing.doCubing(); + Runewords.makeRunewords(); + delay(2000); + } + }, + { + startArea: sdk.areas.RogueEncampment, + preAction: null + } +); + +/** + * @param {number[]} idList list of item classids to match against known crafting/repair NPCs + * @returns {string | boolean} lowercase NPC name to shop from, or false if none match + */ +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; +} + +/** + * @param {number[]} idList item classids to count + * @param {number} quality item quality (e.g. sdk.items.quality.*) to match + * @returns {number} count of stored items matching both classid and quality + */ +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; +} + +/** + * Enables/disables each crafting set in `info.Sets` based on available bases/ingredients in storage. + * @returns {boolean} true if `info` was present and sets were evaluated, false otherwise + */ +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; +} + +/** + * @param {ItemUnit} item item to test + * @returns {boolean} true if the item's gid is a known runeword base gid (validated or config-derived) + */ +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); +} + +/** + * Picks up ground items that match an enabled crafting set, gold, or the normal pickit, then stashes. + */ +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(); +} + +/** + * @param {ItemUnit} item item to test + * @returns {boolean} true if the item is a base or ingredient for any currently enabled crafting set + */ +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; +} + +/** + * Leads the given NPC to town, opens their trade menu, and buys matching items in a loop until + * `amount` magic items are acquired, a game request is pending, or maxgametime is about to expire. + * @param {string} npcId NPC key (case-insensitive) - one of fara, elzix, drognan, ormus, anya, malah + * @param {number[]} classids item classids to buy + * @param {number} amount number of magic-quality matching items to accumulate + * @returns {boolean} true when the buy target is met, a game request is pending, or timeout is reached + */ +function shopStuff (npcId, classids, amount) { + console.log("shopStuff: " + npcId + " " + amount); + + let wpArea, town, path, menuId, npc; + let leadTimeout = 30; + let leadRetry = 3; + + /** + * Walks the NPC along `path` waypoint pairs, retrying each leg up to `leadTimeout` seconds. + * @param {NPCUnit} npc NPC unit being led + * @param {number[]} path flat x,y pairs to lead the NPC through + * @returns {boolean} true if the NPC was led through the full path, false on timeout + */ + 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; + }; + + /** + * Trims `path` to start at the waypoint pair nearest the NPC's current position. + * @param {NPCUnit} npc NPC unit to measure distance from + * @param {number[]} path flat x,y pairs + * @returns {number[]} path slice starting at the closest waypoint pair + */ + 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); + }; + + /** + * Buys stock items matching `classids` from the currently interacted NPC until `amount` is reached. + * @param {number[]} classids item classids eligible to buy + * @param {number} amount target count of magic-quality matching items + * @returns {boolean} true once amount is reached, or the pending gameRequest flag otherwise + */ + 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 (me.maxgametime > 0 && 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..0b9f75485 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/DeveloperMode.js @@ -0,0 +1,338 @@ +/** +* @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..050e4b093 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/DiabloHelper.js @@ -0,0 +1,229 @@ +/** +* @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, + /** + * @param {number} area + * @returns {boolean} + */ + 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..f5c63c1f5 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Duriel.js @@ -0,0 +1,86 @@ +/** +* @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; + /** @returns {boolean} True once the lair entrance portal has been found within 20 distance. */ + 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..08ba0d647 --- /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..3ca3814d8 --- /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 && getTickCount() < timeout) { + 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); + /** @returns {boolean} True once within 10 units of the leader, stopping the move early. */ + 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)) { + /** @returns {boolean} True once refreshed Leader.unit is within 10 units, stopping the move early. */ + 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..6f69d1cf1 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Gamble.js @@ -0,0 +1,71 @@ +/** +* @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) { + if (Town.needGamble()) { + Town.gamble(); + } else { + needGold = true; + idleTick = getTickCount() + rand(1200, 1500) * 1000; + } + 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..91e32f33d --- /dev/null +++ b/d2bs/kolbot/libs/scripts/GemHunter.js @@ -0,0 +1,102 @@ +/** +* @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(); + + /** @returns {boolean} True once the shrine at `coords` is confirmed still present. */ + 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) && 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; + }, + { + /** @param {ScriptContext} ctx */ + preAction: function (ctx) { + ctx.preGidList = new Set(Town.dontStashGids); + }, + /** @param {ScriptContext} ctx */ + 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..359cead74 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Idle.js @@ -0,0 +1,96 @@ +/** +* @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 + */ + function gameEvent (mode, param1, param2, name1) { + 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; + } + } + + /** + * Removes the gameevent listener registered for the greeting/advertise feature. + */ + 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..b172096bf --- /dev/null +++ b/d2bs/kolbot/libs/scripts/MFHelper.js @@ -0,0 +1,310 @@ +/** +* @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 }); + } + } + + /** + * Break-callback for Attack.clearLevel: aborts the clear as soon as a new leader task arrives. + * @returns {boolean} + */ + 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, Time.minutes(2), 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..2bbc29895 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Nihlathak.js @@ -0,0 +1,45 @@ +/** +* @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 + /** @returns {void} Throws a ScriptError to abort if Tomb Vipers are found and ViperQuit is enabled. */ + 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..48e3ec4d6 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/OrgTorch.js @@ -0,0 +1,692 @@ +/** +* @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({ + /** + * @param {Monster} unit + * @returns {boolean} + */ + 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) { + /** @returns {boolean} True once any uber boss is within 10 distance. */ + 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); + + /** @returns {boolean} True once Uber Diablo has been killed. */ + 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)) { + /** @returns {boolean} True once Uber Baal is within 10 distance, dead, or already killed. */ + 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..00ed75fd9 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/OrgTorchHelper.js @@ -0,0 +1,435 @@ +/** +* @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 }); + } + } + + /** + * Waits out death, stops the profile on hardcore, otherwise revives and returns to town. + * @returns {void} + */ + 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..4b98cf289 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Pindleskin.js @@ -0,0 +1,59 @@ +/** +* @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 + /** @returns {void} Throws a ScriptError to abort as soon as a Tomb Viper is seen (if ViperQuit). */ + 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..65afff381 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Questing.js @@ -0,0 +1,413 @@ +/** +* @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); + + 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..ce44a5570 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/RaiseArmy.js @@ -0,0 +1,31 @@ +/** +* @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, + } +); 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..c294a38ea --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rushee.js @@ -0,0 +1,1254 @@ +/** +* @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); + } + + /** + * Installs Town.goToTown and Pather.getWP overrides tailored to rushee waypoint/portal behavior. + */ + 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) { + /** @returns {boolean} true once Tyrael has spawned, stopping the move early */ + 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 { + /** @returns {boolean} true if the Horadric Staff exists or was successfully cubed */ + Staff: function () { + log("Making staff", Config.LocalChat.Enabled); + return make(staff); + }, + /** @returns {boolean} true if Khalim's Will exists or was successfully cubed */ + 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..b6e59ccb6 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Rusher.js @@ -0,0 +1,306 @@ +/// +/** +* @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", + + /** + * Lazily resolves and caches the rush thread's Script handle. + * @returns {Script} + */ + 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 }); + }, + /** + * Pauses the rush thread. + */ + pause: function () { + say("Pausing"); + console.log("Pausing rush thread"); + this.thread.pause(); + }, + /** + * Resumes the rush thread. + */ + resume: function () { + say("Resuming"); + console.log("Resuming rush thread"); + this.thread.resume(); + }, + /** + * Loads the rush thread script and blocks until it's running. + */ + start: function () { + load(this.path); + nativeDelay(500); + + while (!this.thread) { + nativeDelay(500); + } + }, + /** + * Stops the rush thread. + */ + stop: function () { + this.thread.stop(); + }, + /** + * Stops the rush thread, waits for it to fully exit, then starts a fresh instance. + */ + reload: function () { + this.stop(); + + while (this.thread.running) { + nativeDelay(50); + } + this._thread = null; + this.start(); + }, + }; + + const getPartyAct = function () { + let party = getParty(); + if (!party) return sdk.areas.actOf(me.area); + 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 = ""; + + 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.includes(script)) { + 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..56b1de1b8 --- /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..1ebfb33b4 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Synch.js @@ -0,0 +1,63 @@ +/** +* @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"; + +/** + * @param {string} nick + * @param {string} msg + */ +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(); + } + } +} + +/** @returns {boolean} */ +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..c30b49a81 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Synch2.js @@ -0,0 +1,67 @@ +/** +* @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"; + +/** + * @param {string} nick + * @param {string} msg + */ +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(); + } + } +} + +/** @returns {boolean} */ +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 (!Synched2) { + 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..4998c19c2 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Test.js @@ -0,0 +1,43 @@ +/** +* @filename Test.js +* @author kolton +* @desc Unsure? Just testing addEventListener it looks like +* +*/ + +function Test() { + console.log("ÿc8TESTING"); + + let c; + + /** + * @param {number} key + * @returns {void} + */ + 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); + } +} + +/** @returns {void} */ +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..fd072e7ef --- /dev/null +++ b/d2bs/kolbot/libs/scripts/TravincalLeech.js @@ -0,0 +1,94 @@ +/** +* @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("TravincalLeech: Leader not partied"); + } + } + + !leader && (leader = Misc.autoLeaderDetect({ + destination: sdk.areas.Travincal, + /** + * @param {number} area + * @returns {boolean} True once the leader enters a next-script area, stopping the search. + */ + 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..bcc737a89 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/TristramLeech.js @@ -0,0 +1,133 @@ +/** +* @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, + /** + * @param {number} area + * @returns {boolean} + */ + 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); + if (!whereisleader) break; // leader left the party — stop leeching + 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..052107b88 --- /dev/null +++ b/d2bs/kolbot/libs/scripts/Wakka.js @@ -0,0 +1,454 @@ +/** +* @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, + /** + * @param {number} area + * @returns {boolean} + */ + 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..c3cc6fd86 --- /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 ? WS3.distance : Infinity; + let ws1ToWS3 = (WS1 && WS3) ? getDistance(WS1, WS3) : Infinity; + + 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..c575ddaeb --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/AutoMule.js @@ -0,0 +1,725 @@ +/** +* @filename AutoMule.js +* @author kolton, theBGuy +* @desc Main driver for Mule system +* For Mules setup @see MuleConfig.js +* For TorchAnniMules setup @see TorchAnniMules.js +* +*/ + +/** @type {AutoMule} */ +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; + }, + + /** @returns {boolean} true if stash/inventory usage or a triggering item warrants muling */ + 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; + }, + + /** + * Out-of-game half of the master flow: starts/pokes the mule profile, waits for it to + * report ready, then joins the mule game. Blocks until the mule game ends. + * @returns {boolean} + */ + outOfGameCheck: function () { + if (!this.check && !this.torchAnniCheck) { + return false; + } + + let muleObj = this.getMule(); + if (!muleObj) return false; + + /** + * @param {number} mode + * @param {string} msg + */ + 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; + }, + + /** + * In-game half of the mule flow: verifies we're in the mule/torch-mule game, drops the + * wanted items (or waits as the mule character), then quits. + * @returns {boolean} + */ + 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; + } + + /** + * @param {number} mode + * @param {string} msg + */ + 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; + } + } + } + + /** @param {string} msg */ + 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..a2ce02165 --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/Mule.js @@ -0,0 +1,535 @@ +/** +* @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 + * @type {MuleDataType} + */ +const MuleData = { + _default: { + account: "", + accNum: 0, + character: "", + charNum: 0, + fullChars: [], + torchChars: [] + }, + fileName: "", + /** + * Overwrites the mule datafile with a fresh copy of the default data shape. + */ + create: function () { + let string = JSON.stringify(this._default); + FileTools.writeText(this.fileName, string); + }, + + /** + * Reads and parses the mule datafile, recreating it with defaults if missing or corrupt. + * @returns {MuleDataObj} + */ + 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; + } + }, + + /** + * Writes a data object to the mule datafile. + * @param {Partial} obj + */ + write: function (obj) { + let string = JSON.stringify(obj); + FileTools.writeText(this.fileName, string); + }, + + /** + * Advances to the next account, resetting character/full/torch tracking in the datafile. + * @returns {string} the new account name + */ + 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; + }, + + /** + * Advances to the next character suffix on the current account in the datafile. + * @returns {string} the new character name + */ + nextChar: function () { + 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; + }, +}; + +/** @type {Mule} */ +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(), + + /** + * Blocks until the master signals "begin", stopping the profile if nobody joined. + */ + 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); + }, + + /** + * Logs the current mule character as full, advances to the next mule character, and quits. + */ + 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(); + }, + + /** + * Stashes any cursor item, quits the game, and blocks until fully out of game. + * @returns {boolean} + */ + 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; + }, + /** @returns {boolean} true if no other party members are present */ + foreverAlone: function () { + let party = getParty(); + + if (party) { + do { + if (party.name !== me.name) return false; + } while (party.getNext()); + } + + return true; + }, + + /** @returns {boolean} true if an Annihilus or Hellfire Torch is already in storage */ + checkAnniTorch: function () { + while (!me.gameReady) { + delay(500); + } + + return me.getItemsEx() + .some(i => i.isInStorage && (i.isAnni || i.isTorch)); + }, + + /** + * Moves all inventory items into the stash where they fit, largest first. + * @returns {boolean} + */ + 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; + }, + + /** + * Clears any item stuck on the cursor into inventory, dropping it if it won't fit. + * @returns {boolean} + */ + 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; + }, + + /** @returns {ItemUnit[]} nearby ground/dropping items that aren't pickit-ignored */ + getGroundItems: function () { + return getUnits(sdk.unittype.Item) + .filter(i => i.distance < 20 && i.onGroundOrDropping && !Town.ignoreType(i.itemType)); + }, + + /** + * Main mule item-pickup loop: waits for the dropper's report, then picks droppable items, + * stashing to make room as needed. + * @returns {string} "ready", "next", or "done" + */ + 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 > 0 + ? 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..bc560f1c8 --- /dev/null +++ b/d2bs/kolbot/libs/systems/automule/main.js @@ -0,0 +1,220 @@ +/** +* @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"); + +/** + * D2BS entry point: sets up the mule's background workers/listeners, then loops picking up + * items from the ground until muling is done or the character needs to move on. + * @returns {boolean} + */ +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..2863b0dbc --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/AutoRush.js @@ -0,0 +1,1452 @@ +/** +* @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) { + if (!nick) return Misc.checkPartyLevel(getBumperLvlReq()); + let player = Misc.findPlayer(nick); + return !!player && player.level >= 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)) { + !tree && (tree = Game.getObject(sdk.quest.chest.InifussTree)); + if (!!tree && 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"); + let player = Misc.findPlayer(nick); + if (!player || player.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"); + + /** @returns {boolean} False if `nick` doesn't show up in the area before {@link AutoRush.playerWaitTimeout}. */ + 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 }) && Common.Baal.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..71d8ff72f --- /dev/null +++ b/d2bs/kolbot/libs/systems/autorush/RushConstants.js @@ -0,0 +1,365 @@ +/** +* @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: { + /** @returns {boolean} Whether Deckard Cain has been rescued. */ + check: function () { + return me.cain; + } + }, + andariel: { + /** @returns {boolean} Whether Andariel has been killed. */ + check: function () { + return me.andariel; + } + }, + radament: { + /** @returns {boolean} Whether Radament has been killed. */ + check: function () { + return me.radament; + } + }, + cube: { + /** @returns {boolean} Whether the Horadric Cube has been picked up. */ + check: function () { + return me.normal && !!me.cube; + } + }, + amulet: { + /** @returns {boolean} Whether the Horadric amulet ingredient has been obtained. */ + check: function () { + return (me.horadricstaff || me.amulet || me.completestaff); + } + }, + staff: { + /** @returns {boolean} Whether the Horadric staff ingredient has been obtained. */ + check: function () { + return (me.horadricstaff || me.shaft || me.completestaff); + } + }, + summoner: { + /** @returns {boolean} Whether the Summoner has been killed. */ + check: function () { + return me.summoner; + } + }, + duriel: { + /** @returns {boolean} Whether Duriel has been killed. */ + check: function () { + return me.duriel; + } + }, + lamesen: { + /** @returns {boolean} Whether the Lam Esen's Tome quest has been completed. */ + check: function () { + return me.lamessen; + } + }, + travincal: { + /** @returns {boolean} Whether the Travincal (Compelling Orb) quest has been completed. */ + check: function () { + return me.travincal; + } + }, + mephisto: { + /** @returns {boolean} Whether Mephisto has been killed. */ + check: function () { + return me.mephisto; + } + }, + izual: { + /** @returns {boolean} Whether Izual has been killed. */ + check: function () { + return me.izual; + } + }, + diablo: { + /** @returns {boolean} Whether Diablo has been killed. */ + check: function () { + return me.diablo; + } + }, + shenk: { + /** @returns {boolean} Whether Shenk the Overseer has been killed. */ + check: function () { + return me.shenk; + } + }, + anya: { + /** @returns {boolean} Whether Anya has been rescued or the Prison of Ice scroll obtained. */ + check: function () { + return me.anya || Misc.checkQuest(sdk.quest.id.PrisonofIce, 8/** Recieved the scroll */); + } + }, + ancients: { + /** @returns {boolean} Whether the Ancients' Way quest has been completed. */ + check: function () { + return me.ancients; + } + }, + baal: { + /** @returns {boolean} Whether Baal has been killed. */ + 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..3510673de --- /dev/null +++ b/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.d.ts @@ -0,0 +1,23 @@ +export {}; +declare global { + // Value shape of the global `CharRefresher` const in CharRefresher.js (bound there via JSDoc @type). + // Named CharRefresherType (not the bare "CharRefresher") - reusing the const's own name here + // reproducibly triggers TS2451 "cannot redeclare block-scoped variable" in this program. + 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; + } +} diff --git a/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.js b/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.js new file mode 100644 index 000000000..78270c192 --- /dev/null +++ b/d2bs/kolbot/libs/systems/charrefresher/CharRefresher.js @@ -0,0 +1,39 @@ +/** +* @filename CharRefresher.js +* @author theBGuy +* @desc Refresh characters so they don't expire, for setup @see RefresherConfig.js +* +* @typedef {import("../../../sdk/globals")} +*/ + +/** @type {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); + }, + + /** + * Deletes the CharRefresher run-state log file. + */ + remove: function () { + FileTools.remove("logs/CharRefresher.json"); + }, +}; diff --git a/d2bs/kolbot/libs/systems/crafting/CraftingSystem.d.ts b/d2bs/kolbot/libs/systems/crafting/CraftingSystem.d.ts new file mode 100644 index 000000000..632b7db5e --- /dev/null +++ b/d2bs/kolbot/libs/systems/crafting/CraftingSystem.d.ts @@ -0,0 +1,66 @@ +export {}; +declare global { + interface CraftingSystemSet { + /** Base item class ids to craft onto */ + BaseItems: number[]; + /** Recipe ingredient class ids */ + Ingredients: number[]; + /** Number of full sets to gather before transferring */ + SetAmount: number; + Type: "crafting" | "runewords" | "cubing"; + /** Runtime flag toggled by CraftingSystem.init from worker set-info */ + Enabled?: boolean; + } + + interface CraftingSystemTeam { + /** Profiles that collect ingredients */ + Collectors: string[]; + /** Profiles that craft/reroll items */ + Workers: string[]; + /** Worker game names (without the numbers) */ + CraftingGames: string[]; + Sets: CraftingSystemSet[]; + } + + /** getInfo() return: the matched team plus which role the current profile plays */ + interface CraftingSystemTeamInfo extends CraftingSystemTeam { + collector: boolean; + worker: boolean; + } + + interface CraftingSystemWorker { + /** [gameName, password] once discovered via copydata, else false */ + game: string[] | false; + name: string | false; + } + + // Value shape of the global `CraftingSystem` const in CraftingSystem.js (bound there via + // JSDoc @type). Declaring the const here as well would collide: both files are global scripts. + interface ICraftingSystem { + Teams: Record; + check: boolean; + inGame: boolean; + neededItems: number[]; + validGids: number[]; + itemList: ItemUnit[]; + /** Completed ingredient sets as gid lists, consumed by dropItems */ + fullSets: number[][]; + getInfo(): CraftingSystemTeamInfo | false; + outOfGameCheck(): boolean; + getWorker(): CraftingSystemWorker | false; + inGameCheck(): boolean; + validItem(item: ItemUnit): boolean; + checkItem(item: ItemUnit): boolean; + keepItem(item: ItemUnit): boolean; + /** Per-set 0/1 enabled flags fetched from the worker over copydata, or false on timeout */ + getSetInfoFromWorker(workerName: string): number[] | false; + init(name: string): void; + buildLists(onlyNeeded?: boolean): boolean; + checkSet(set: CraftingSystemSet): object; + update(item: ItemUnit): boolean; + checkSubrecipes(): boolean; + checkFullSets(): boolean; + dropItems(): boolean; + dropGold(): void; + } +} diff --git a/d2bs/kolbot/libs/systems/crafting/CraftingSystem.js b/d2bs/kolbot/libs/systems/crafting/CraftingSystem.js new file mode 100644 index 000000000..a04eb1d8c --- /dev/null +++ b/d2bs/kolbot/libs/systems/crafting/CraftingSystem.js @@ -0,0 +1,516 @@ +/** +* @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 +* +*/ + +/** @type {ICraftingSystem} */ +const CraftingSystem = {}; + +// load configuration file +CraftingSystem.Teams = Object.assign({}, require("./TeamsConfig", null, false)); + +// ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## + +// Get the Crafting System information for current profile +/** @returns {CraftingSystemTeamInfo | false} */ +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; + +/** @returns {boolean} */ +CraftingSystem.outOfGameCheck = function () { + if (!CraftingSystem.check) return false; + + let info = CraftingSystem.getInfo(); + /** @type {ReturnType} */ + let worker; + + /** + * @param {string | object | number} msg + * @returns {boolean} + */ + 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) { + 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; +}; + +/** @returns {CraftingSystemWorker | false} */ +CraftingSystem.getWorker = function () { + let rval = { + game: false, + name: false + }; + let info = CraftingSystem.getInfo(); + + /** + * @param {number} mode + * @param {string} msg + */ + 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 # +// ############################################# + +/** @returns {boolean} */ +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 +/** + * @param {ItemUnit} item + * @returns {boolean} + */ +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 +/** + * @param {ItemUnit} item + * @returns {boolean} + */ +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 +/** + * @param {ItemUnit} item + * @returns {boolean} + */ +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 +/** + * @param {string} workerName + * @returns {number[] | false} + */ +CraftingSystem.getSetInfoFromWorker = function (workerName) { + let setInfo = false; + let info = CraftingSystem.getInfo(); + + /** + * @param {number} mode + * @param {string} msg + * @returns {boolean} + */ + 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 })); + Misc.poll(function () { + return setInfo !== false; + }, Time.seconds(5), 100); + + if (setInfo !== false) { + removeEventListener("copydata", copyData); + + return setInfo; + } + + removeEventListener("copydata", copyData); + } + + return false; +}; + +/** @param {string} name */ +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 +/** + * @param {boolean} [onlyNeeded] + * @returns {boolean} + */ +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 +/** + * @param {CraftingSystemSet} set + * @returns {object} + */ +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 +/** + * @param {ItemUnit} item + * @returns {boolean} + */ +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 +/** @returns {boolean} */ +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 +/** @returns {boolean} */ +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 +/** @returns {boolean} */ +CraftingSystem.dropItems = function () { + Town.goToTown(1); + Town.move("stash"); + Town.openStash(); + + let worker; + + /** + * @param {string | object | number} msg + * @returns {boolean} + */ + 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; +}; + +/** @returns {void} */ +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.d.ts b/d2bs/kolbot/libs/systems/gambling/Gambling.d.ts new file mode 100644 index 000000000..2b768f5b3 --- /dev/null +++ b/d2bs/kolbot/libs/systems/gambling/Gambling.d.ts @@ -0,0 +1,29 @@ +export {}; + +declare global { + /** Team entry shape from TeamsConfig.js, plus role flags set at runtime by Gambling.getInfo */ + interface GamblingTeam { + goldFinders: string[]; + gamblers: string[]; + gambleGames: string[]; + goldTrigger: number; + goldReserve: number; + /** Set by getInfo() when the current profile matches a goldFinders entry */ + goldFinder?: boolean; + /** Set by getInfo() when the current profile matches a gamblers entry */ + gambler?: boolean; + } + + // Value shape of the global `Gambling` const in Gambling.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface IGambling { + Teams: Record; + inGame: boolean; + getInfo(profile?: string): GamblingTeam | false; + inGameCheck(): boolean; + dropGold(): void; + outOfGameCheck(): boolean; + /** Game [name, password] discovered via copydata from a gambler, else null before/without a reply */ + getGame(): string[] | null | false; + } +} diff --git a/d2bs/kolbot/libs/systems/gambling/Gambling.js b/d2bs/kolbot/libs/systems/gambling/Gambling.js new file mode 100644 index 000000000..986afce5b --- /dev/null +++ b/d2bs/kolbot/libs/systems/gambling/Gambling.js @@ -0,0 +1,180 @@ +/** +* @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 +* +*/ + +/** @type {IGambling} */ +const Gambling = { + // load configuration file + Teams: Object.assign({}, require("./TeamsConfig", null, false)), + + inGame: false, + + /** + * @param {string} [profile] profile name to look up; defaults to `me.profile` + * @returns {GamblingTeam | false} the matching team (with goldFinder/gambler flags set), or 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; + }, + + /** @returns {boolean} true if a gold-drop game was matched, gold dropped, and the script quit */ + 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; + }, + + /** + * Drops carried gold and withdraws stashed gold down to `goldReserve` for other profiles to pick up. + * @returns {void} + */ + 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); + } + } + }, + + /** @returns {boolean} true if a gold-drop game was found and joined (and has since ended) */ + 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; + }, + + /** + * Requests the gold-drop game name/password from each configured gambler via copydata. + * @returns {string[] | null | false} [name, password] once a gambler replies, null if none replied, + * or false if the current profile isn't a gold finder + */ + getGame: function () { + let game; + let info = this.getInfo(); + + if (!info || !info.goldFinder) { + return false; + } + + /** + * copydata handler: captures the gambler's game name/password reply into the outer `game`. + * @param {number} mode copydata mode (4 = string message) + * @param {string} msg raw copydata message, expected as "gamename/gamepassword" + */ + 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..ec776ba46 --- /dev/null +++ b/d2bs/kolbot/libs/systems/gameaction/GameAction.d.ts @@ -0,0 +1,60 @@ +export {}; +declare global { + type GameActionDropItem = { + title: string; + character: string; + realm: string; + account: string; + itemid: string; + }; + + type DoDropGameActionData = { + items: GameActionDropItem[]; + gameName: string; + gamePass: string; + realm: string; + account: string; + chars: string[]; + }; + + type GameActionLadderConvertEntry = { + realm: string; + account: string; + character: string; + }; + + // Matches the shapes `GameAction.task.data` actually takes across the different task actions + // (doDrop/mule-log fields, doConvertNL's array form, or the raw JSON string before init() parses it). + type GameActionData = Partial | GameActionLadderConvertEntry[] | string; + + type GameActionTask = { + hash: string; + profile: string; + action: string; + data: GameActionData; + }; + + // Value shape of the global `GameAction` const in GameAction.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface GameAction { + LogNames: boolean; + LogItemLevel: boolean; + LogEquipped: boolean; + LogMerc: boolean; + SaveScreenShot: boolean; + IngameTime: number; + task: GameActionTask | null; + + // Methods + init(task: string): boolean; + update(action: string, data: string | object): void; + gameInfo(): { gameName: string; gamePass: string } | null; + getLogin(): { realm: string | null; account: string | null; password: string | null }; + getCharacters(): string[]; + inGameCheck(): boolean; + load(hash: string): string; + save(hash: string, data: string): void; + dropItems(droplist: GameActionDropItem[]): void; + convertLadderFiles(): void; + } +} diff --git a/d2bs/kolbot/libs/systems/gameaction/GameAction.js b/d2bs/kolbot/libs/systems/gameaction/GameAction.js new file mode 100644 index 000000000..76c51a775 --- /dev/null +++ b/d2bs/kolbot/libs/systems/gameaction/GameAction.js @@ -0,0 +1,308 @@ +/* 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 {GameAction} */ +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); + }, + + /** @returns {{ gameName: string, gamePass: string } | null} */ + 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; + }, + + /** @returns {{ realm: string | null, account: string | null, password: string | null }} */ + getLogin: function () { + let li = { realm: null, account: null, password: null }; + + (this.task && this.task.data) && (li.password = this.load(this.task.hash)); + + if (this.task && this.task.data) { + // 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; + }, + + /** @returns {string[]} */ + getCharacters: function () { + let chars = []; + + if (this.task && this.task.data) { + // 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; + }, + + /** @returns {boolean} */ + 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; + }, + + /** + * @param {string} hash + * @returns {string} + */ + 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); + }, + + /** + * @param {string} hash + * @param {string} data + */ + save: function (hash, data) { + let filename = "data/secure/" + hash + ".txt"; + FileTools.writeText(filename, data); + }, + + /** @param {GameActionDropItem[]} droplist */ + 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(); + } + } + } + }, + + /** + * Converts a batch of ladder mule data files to their non-ladder filename/path equivalents. + */ + 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..854543bb8 --- /dev/null +++ b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.d.ts @@ -0,0 +1,81 @@ +// House pattern: MuleLoggerType is a GLOBAL interface, not a module export, so SoloPlay can +// merge its own additions into it from libs/SoloPlay/globals.d.ts (a module-scoped interface +// cannot be augmented from another file). Consumers bind the bare global name in JSDoc. +// The `MuleLogger` const is declared here; the js binds it via JSDoc @type. +export {}; + +declare global { + 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; + invTrans: 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; + } + + // Ambient value declaration (Skill/Attack/Misc precedent - see those d.ts comments): + // restores checker resolution of the @type-bound js const. + 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..9088225c3 --- /dev/null +++ b/d2bs/kolbot/libs/systems/mulelogger/MuleLogger.js @@ -0,0 +1,406 @@ +/** +* @filename MuleLogger.js +* @author kolton, theBGuy +* @desc Log items and perm accounts/characters, for setup @see LoggerConfig.js +* +* @typedef {import("../../../sdk/globals")} +*/ + +/** @type {MuleLoggerType} */ +const MuleLogger = { + /** + * If this is a configured MuleLog game, logs the character's items and stays in game for + * `IngameTime` (or the perm-status override) before quitting. + * @returns {boolean} True if logging ran (and the game was quit), false if this game didn't match. + */ + 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); + }, + + /** @returns {void} */ + 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 + // external dropper plugin: this branch only runs when the untracked ItemDB.js was + // actually include()d, which defines the global + // eslint-disable-next-line no-undef + 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..552315152 --- /dev/null +++ b/d2bs/kolbot/libs/systems/torch/OrgTorchData.js @@ -0,0 +1,71 @@ +/** +* @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: [] }, + + /** @returns {boolean} True if this profile's OrgTorch data file exists. */ + exists: function () { + return FileTools.exists(this._path); + }, + + /** @returns {OrgTorchDataObject} A freshly-written default data object for the current game. */ + 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)); + }, + + /** @returns {boolean} True if this profile's OrgTorch data file was deleted. */ + 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..e8e8b6c7e --- /dev/null +++ b/d2bs/kolbot/libs/systems/torch/TorchSystem.d.ts @@ -0,0 +1,22 @@ +export {}; +declare global { + interface TorchSystemFarmerProfile { + KeyFinderProfiles: string[]; + FarmGame: string; + profile?: string; + } + + // Value shape of the global `TorchSystem` const in TorchSystem.js (bound there via JSDoc @type). + // Declaring the const here as well would collide: both files are global scripts. + interface ITorchSystem { + FarmerProfiles: { [key: string]: TorchSystemFarmerProfile }; + inGame: boolean; + check: boolean; + getFarmers(): TorchSystemFarmerProfile[] | false; + isFarmer(): TorchSystemFarmerProfile | false; + inGameCheck(): boolean; + keyCheck(): number[]; + outOfGameCheck(): boolean; + waitForKeys(): void; + } +} diff --git a/d2bs/kolbot/libs/systems/torch/TorchSystem.js b/d2bs/kolbot/libs/systems/torch/TorchSystem.js new file mode 100644 index 000000000..713bd435e --- /dev/null +++ b/d2bs/kolbot/libs/systems/torch/TorchSystem.js @@ -0,0 +1,414 @@ +/** +* @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 +* +*/ + +/** @type {ITorchSystem} */ +const TorchSystem = { + // load configuration file + FarmerProfiles: Object.assign({}, require("./FarmerConfig", null, false)), + + // Don't touch + inGame: false, + check: false, + + /** + * @returns {({KeyFinderProfiles: string[], FarmGame: string, profile: string})[]|false} farmer profiles this + * character is registered as a key finder for, or false if none. + */ + 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; + }, + + /** + * @returns {({KeyFinderProfiles: string[], FarmGame: string, profile: string})|false} this profile's farmer + * config if the current character is a configured farmer, else false. + */ + isFarmer: function () { + if (this.FarmerProfiles.hasOwnProperty(me.profile)) { + this.FarmerProfiles[me.profile].profile = me.profile; + + return this.FarmerProfiles[me.profile]; + } + + return false; + }, + + /** + * When the current game matches a configured farmer's FarmGame name, drops the requested keys/gold to the + * stash for pickup, then quits the game. + * @returns {boolean} true if a matching farm game was found and handled. + */ + 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; + }, + + /** + * Asks each configured farmer over copydata for its needed key/juv counts, then copies matching items + * out of this character's inventory to satisfy the first farmer that responds. + * @returns {Object|false} items keyed by code (pk1/pk2/pk3/rv) ready to drop, or false. + */ + keyCheck: function () { + let neededItems = {}; + let farmers = this.getFarmers(); + if (!farmers) return false; + + /** + * copydata listener; on a "neededItems" reply, copies matching inventory items into `neededItems`. + * @param {number} mode + * @param {string} msg + * @returns {void} + */ + 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") + || neededItems.hasOwnProperty("rv") + ) { + removeEventListener("copydata", keyCheckEvent); + + return neededItems; + } + } + + removeEventListener("copydata", keyCheckEvent); + + return false; + }, + + /** + * Queries configured farmers for their farm game name/password over copydata and, if found, joins that + * game (blocking mouse input) until the character leaves it. + * @returns {boolean} true if a farm game was joined, false if not checked or none found. + */ + outOfGameCheck: function () { + if (!this.check) return false; + TorchSystem.check = false; + + let game; + + /** + * copydata listener; on a "gameName" reply matching a known farmer, captures its game/password. + * @param {number} mode + * @param {string} msg + * @returns {boolean} always true. + */ + 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; + }, + + /** + * Farmer-side loop: registers a copydata listener answering key-hunter game/count requests, then waits + * in Act 1 by the stash until enough keys/juvs are collected or the configured wait timeout elapses. + * @returns {void} + */ + 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..d904813f7 100644 --- a/d2bs/kolbot/sdk/globals.d.ts +++ b/d2bs/kolbot/sdk/globals.d.ts @@ -1,262 +1,1720 @@ -declare type PathNode = { x: number, y: number } +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +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: T[]) => boolean, thisArg?: unknown): T | undefined; + first(): T | undefined; + last(): T | undefined; + at(index: number): T | undefined; + findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: unknown): 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[]) => U, thisArg?: unknown): NonNullable[]; + filterNull(): T[]; + filterHighDistance(step: number): T[]; + 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: T, b: T): 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: [string, number | string | boolean][]): 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: object[]): object; + values(source: T): T[keyof T][]; + entries(source: T): [Extract, T[keyof T]][]; + is(o1: unknown, o2: unknown): boolean; + // hasOwn(obj: object, prop: string): boolean; + hasOwn(obj: T, prop: keyof T): boolean; + hasOwn(obj: T, prop: K): prop is K & keyof T; + } + + interface Object { + readonly distance: number; + path: IPathNode[] | undefined; + + setPrototypeOf(obj: object, proto: object): 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]: unknown; + } + + /** + * 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): File; + seek(n: number, isLines: boolean, fromStart: boolean): File; + flush(): void; + reset(): void; + end(): void; + } + const FILE_READ: 0; + const FILE_WRITE: 1; + const FILE_APPEND: 2; + + const FileTools: { + readText(filename: string): string; + writeText(filename: string, data: string): boolean; + appendText(filename: string, data: string): boolean; + exists(filename: string): boolean; + remove(filename: string): boolean; + }; + + function getCollision(area: number, x: number, y: number, x2: number, y2: number): number; + + /** + * Shape of walk-path nodes. Named IPathNode, NOT PathNode: the runtime constructor is the + * top-level `function PathNode (x, y)` in libs/core/Pather.js:15 (this-assignments + + * prototype methods), and a same-named ambient interface merges with that JS constructor + * symbol and crashes TS 5.9's getConstructorDefinedThisAssignmentTypes (Debug Failure). + * The distinct name also keeps this resolvable in the SoloPlay program, which includes + * this file but not libs/core/. + */ + interface IPathNode { + x: number; + y: number; + /** Distance from 'me' to this node */ + readonly distance: number; + distanceTo(unit: Unit): number; + getWalkDistance(): number; + getWalkDistanceTo(node: IPathNode, area?: number): number; + mobCount(givenSettings: { range?: number; coll?: number; type?: number; ignoreClassids?: number[] }): number; + update({ x, y }: { x?: number; y?: number }): void; + } + function getDistance(unit: IPathNode, other: IPathNode): number; + function getDistance(unit: IPathNode, 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 direction: number; + readonly idle: boolean; + readonly isPlayer: boolean; + readonly isNPC: boolean; + readonly isMonster: 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(classId?: number, mode?: number, unitId?: number): ItemUnit[]; + getItems(name?: string, mode?: number, unitId?: number): ItemUnit[]; + 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: string): boolean; + } + + class MercUnit extends Monster { + equip(destination: number | undefined, item: ItemUnit): boolean; + } + + 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: IPathNode): 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; + } + + // Authority for ItemInfo lives HERE, not as a @typedef in Prototypes.js: the SoloPlay + // program includes this file but no core .js, so a js-typedef authority is invisible there. + 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 mapid: number; + nopickup: boolean; + readonly unsupported: boolean; + readonly screensize: number; + readonly windowtitle: string; + readonly ingame: boolean; + quitonerror: boolean; + maxgametime: number; + readonly gamepassword: string; + readonly gamename: string; + readonly gameserverip: string; + readonly itemcount: number; + readonly classid: 0 | 1 | 2 | 3 | 4 | 5 | 6; + readonly weaponswitch: 0 | 1; + blockMouse: boolean; + blockKeys: boolean; + runwalk: number; + automap: boolean; + + readonly expansion: boolean; + readonly classic: boolean; + readonly softcore: boolean; + readonly hardcore: boolean; + /** Hardcore character has permanently died; also readable out of game (char select/lobby) */ + readonly diedHardcore: 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 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 | { + itemType?: number; + classid?: number; + mode?: number; + quality?: number; + sockets?: number; + location?: number; + ethereal?: boolean; + cb?: (item: ItemUnit) => boolean; + }): 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(itemInfo: ItemInfo[]): boolean; + 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): boolean; + cancelUIFlags(): boolean; + fieldID(): boolean; + castChargedSkill(skillId: number, target?: Unit): boolean; + castChargedSkill(skillId: number, x: number, y: number): boolean; + clearBelt(): boolean; + } + + const me: MeType; + + 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, + ): IPathNode[] | false; + function getCollision(area: number, x: number, y: number): 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): [number, 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: IPathNode): 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: boolean; -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: number; + selectstart: number; + selectend: number; + 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): string | number | null; + } + + 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(): { text: string; handler(): void }[] | false; + function print(what: string): void; + function stringToEUC(arg: string): number[]; + function utf8ToEuc(arg: string): number[]; + function delay(ms: number): void; + // Published by libs/modules/Worker.js before it overrides `delay`: both are the engine's + // original sleep, kept reachable for code that must not yield to background work. + var _delay: typeof delay; + var nativeSleep: typeof delay; + // Published by libs/core/Pather.js: the engine's getWaypoint captured before the cache wrapper. + var _getWaypoint: typeof getWaypoint; + // Published by libs/modules/Worker.js: cooperative block-until-settled on the Promise polyfill. + var await: (promise: Promise) => T; + // Published by libs/require.js. Only the fallback contract for dynamic call shapes - a literal + // require("./x") is still resolved per-module by TS itself. + var require: (field: string, path?: string) => unknown; + 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(mode: number, server: string, topic: string, item: string, data: string): string | void; + function keystate(vKey: number): boolean; + + 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: string, arg2: string, arg3: string, arg4: string) => void, + ): void; + function addEventListener(eventType: eventName, callback: (...args: unknown[]) => 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: unknown[]) => void): void; + + function clearEvent(event: string): void; + function clearAllEvents(): void; + function js_strict(enabled?: boolean): boolean; + function version(): number; + function scriptBroadcast(what: string | object): void; + function sqlite_version(): string; + function sqlite_memusage(): number; + + type directory = { + getFiles(): string[]; + getFolders(): string[]; + create(what?: string): boolean; + }; + // Returns the plain shape, not `directory | false`: the engine does return false on a bad + // path, but every call site chains directly (dopen(x).getFiles()) and a `| false` union + // types the whole chain `any` under unchecked js - poisoning the idiom the codebase uses. + function dopen(what?: string): directory; + function debugLog(text: string): void; + function showConsole(): void; + function hideConsole(): void; + + // out of game functions + function login(name?: string): void; + function selectCharacter(): boolean; + function createGame(name: string, pass?: string, difficulty?: number): void; + function joinGame(name: string, password?: string): void; + function addProfile(name: string, mode: string, gateway: string, username: string, password: string, character: string): void; + function getLocation(): number; + function loadMpq(path: string): void; + + // game functions that don't have anything to do with gathering data + function submitItem(): void; + function getMouseCoords(mapCoords?: boolean, returnObject?: boolean): [number, number] | { x: number; y: number }; + function copyUnit(unit: S): S; + function clickMap(type: 0 | 1 | 2 | 3, shift: 0 | 1, x: number, y: number): boolean; + function acceptTrade(mode?: number): number; + function tradeOk(): void; + function beep(id?: number): void; + + function clickItem(where: 0 | 1 | 2, bodyLocation: number): void; + function clickItem(where: 0 | 1 | 2, item: ItemUnit): void; + function clickItem(where: 0 | 1 | 2, x: number, y: number): void; + function clickItem(where: 0 | 1 | 2, x: number, y: number, location: number): void; + + 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): void; + 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; + + /** + * The thread's global scope object, derived in Polyfill.js via the Function-constructor trick + * (`[].filter.constructor("return this")()`) since SM24 has no native globalThis. Typed as the + * ambient scope itself so `global.X` gets real member typing everywhere. + */ + var global: typeof globalThis; + + /** + * Per-class config entry point: every libs/config/[.].js declares + * `function LoadConfig () {...}`; core/Config.js include()s the selected file, then calls it + * (Config.js:86, mirrored in manualplay ConfigOverrides.js:84). + */ + function LoadConfig(): void; + + /** + * Value shape of the `const AutoBuildTemplate` each libs/config/Builds/.